From c2765cbde8574227afb2c688bae88b07695dc1fa Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 17 Jan 2023 12:27:50 +0100 Subject: [PATCH 01/86] Fix for world transformation of text --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 0071e34504..083ff744b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -403,7 +403,7 @@ Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model) if (!fix.has_value()) return res; - return res * (*fix); + return res * fix->inverse(); } Transform3d priv::world_matrix(const Selection &selection) From 82c0913ea86aa508f2b125024e933ef83ad93c44 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 17 Jan 2023 12:29:19 +0100 Subject: [PATCH 02/86] Add Search fot font face names --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 83 ++++++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 ++ 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 083ff744b9..96bd4a9eb9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -47,7 +47,6 @@ #define ALLOW_ADD_FONT_BY_OS_SELECTOR #define SHOW_WX_FONT_DESCRIPTOR // OS specific descriptor | file path --> in edit style #define SHOW_FONT_FILE_PROPERTY // ascent, descent, line gap, cache --> in advanced -#define SHOW_FONT_COUNT // count of enumerated font --> in font combo box #define SHOW_CONTAIN_3MF_FIX // when contain fix matrix --> show gray '3mf' next to close button #define SHOW_OFFSET_DURING_DRAGGING // when drag with text over surface visualize used center #define SHOW_IMGUI_ATLAS @@ -1830,6 +1829,9 @@ void GLGizmoEmboss::init_font_name_texture() { face.cancel = nullptr; face.is_created = nullptr; } + + // Prepare filtration cache + m_face_names.hide = std::vector(m_face_names.faces.size(), {false}); } void GLGizmoEmboss::draw_font_preview(FaceName& face, bool is_visible) @@ -1960,13 +1962,54 @@ void GLGizmoEmboss::draw_font_list() }; } + // Code + const char *popup_id = "##font_list_popup"; + const char *input_id = "##font_list_input"; ImGui::SetNextItemWidth(m_gui_cfg->input_width); - if (ImGui::BeginCombo("##font_selector", selected)) { - if (!m_face_names.is_init) init_face_names(); - if (m_face_names.texture_id == 0) init_font_name_texture(); + ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_CharsUppercase; + + // change color of hint to normal text + ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + if (ImGui::InputTextWithHint(input_id, selected, &m_face_names.search, input_flags)) { + // update filtration result + m_face_names.hide = std::vector(m_face_names.faces.size(), {false}); + for (FaceName &face : m_face_names.faces) { + size_t index = &face - &m_face_names.faces.front(); + std::string name(face.wx_name.ToUTF8().data()); + std::transform(name.begin(), name.end(), name.begin(), ::toupper); + m_face_names.hide[index] = !name._Starts_with(m_face_names.search); + } + } + ImGui::PopStyleColor(); // revert changes for hint color + + const bool is_input_text_active = ImGui::IsItemActive(); + + // is_input_text_activated + if (ImGui::IsItemActivated()) + ImGui::OpenPopup(popup_id); + + ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMax().y)); + ImGui::SetNextWindowSize({2*m_gui_cfg->input_width, ImGui::GetTextLineHeight()*10}); + ImGuiWindowFlags popup_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_ChildWindow; + if (ImGui::BeginPopup(popup_id, popup_flags)) + { + bool set_selection_focus = false; + if (!m_face_names.is_init) { + init_face_names(); + set_selection_focus = true; + } + if (m_face_names.texture_id == 0) + init_font_name_texture(); + for (FaceName &face : m_face_names.faces) { const wxString &wx_face_name = face.wx_name; size_t index = &face - &m_face_names.faces.front(); + + // Filter for face names + if (m_face_names.hide[index]) + continue; + ImGui::PushID(index); ScopeGuard sg([]() { ImGui::PopID(); }); bool is_selected = (actual_face_name == wx_face_name); @@ -1978,24 +2021,32 @@ void GLGizmoEmboss::draw_font_list() wxMessageBox(GUI::format(_L("Font face \"%1%\" can't be selected."), wx_face_name)); } } + // tooltip as full name of font face if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", wx_face_name.ToUTF8().data()); - if (is_selected) ImGui::SetItemDefaultFocus(); - draw_font_preview(face, ImGui::IsItemVisible()); - } -#ifdef SHOW_FONT_COUNT - ImGui::TextColored(ImGuiWrapper::COL_GREY_LIGHT, "Count %d", - static_cast(m_face_names.names.size())); -#endif // SHOW_FONT_COUNT - ImGui::EndCombo(); - } else if (m_face_names.is_init) { - // Just one after close combo box - // free texture and set id to zero + // on first draw set focus on selected font + if (set_selection_focus && is_selected) + ImGui::SetScrollHereY(); + draw_font_preview(face, ImGui::IsItemVisible()); + } + + if (!ImGui::IsWindowFocused() || + !is_input_text_active && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape))) { + // closing of popup + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else if (m_face_names.is_init) { + // Just one after close combo box + // free texture and set id to zero m_face_names.is_init = false; + m_face_names.search.clear(); + m_face_names.hide.clear(); // cancel all process for generation of texture for (FaceName &face : m_face_names.faces) - if (face.cancel != nullptr) face.cancel->store(true); + if (face.cancel != nullptr) + face.cancel->store(true); glsafe(::glDeleteTextures(1, &m_face_names.texture_id)); m_face_names.texture_id = 0; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 6340b6faeb..2fabf0566f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -270,6 +270,10 @@ private: // hash created from enumerated font from OS // check when new font was installed size_t hash = 0; + + // filtration pattern + std::string search = ""; + std::vector hide; // result of filtration } m_face_names; static bool store(const Facenames &facenames); static bool load(Facenames &facenames); From 402affb03521d0dbc159f98c24a5964edc23715b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Jan 2023 14:58:07 +0100 Subject: [PATCH 03/86] Fix of #9399 Thick layers + thick raft + lightning infill = crash FillLightning::Filler infill wrapper was not aware of the fact that Layer::id() returns layer index that includes raft layers. This is newly handled by the FillLightning::Filler wrapper. --- src/libslic3r/Fill/Fill.cpp | 7 +++++-- src/libslic3r/Fill/FillLightning.cpp | 2 +- src/libslic3r/Fill/FillLightning.hpp | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 01c551fb63..e0e3bdc5dc 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -445,8 +445,11 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->angle = surface_fill.params.angle; f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; - if (surface_fill.params.pattern == ipLightning) - dynamic_cast(f.get())->generator = lightning_generator; + if (surface_fill.params.pattern == ipLightning) { + auto *lf = dynamic_cast(f.get()); + lf->generator = lightning_generator; + lf->num_raft_layers = this->object()->slicing_parameters().raft_layers(); + } if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) { FillConcentric *fill_concentric = dynamic_cast(f.get()); diff --git a/src/libslic3r/Fill/FillLightning.cpp b/src/libslic3r/Fill/FillLightning.cpp index 36a48e5547..8c3ac8f1ac 100644 --- a/src/libslic3r/Fill/FillLightning.cpp +++ b/src/libslic3r/Fill/FillLightning.cpp @@ -13,7 +13,7 @@ void Filler::_fill_surface_single( ExPolygon expolygon, Polylines &polylines_out) { - const Layer &layer = generator->getTreesForLayer(this->layer_id); + const Layer &layer = generator->getTreesForLayer(this->layer_id - this->num_raft_layers); Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled(0.5 * this->spacing - this->overlap)); if (params.dont_connect() || fill_lines.size() <= 1) { diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index 341399508f..0705dc657f 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -22,6 +22,7 @@ public: ~Filler() override = default; Generator *generator { nullptr }; + size_t num_raft_layers { 0 }; protected: Fill* clone() const override { return new Filler(*this); } From 70ec644ad4fa03c965643154cb76e0741b1fada2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 17 Jan 2023 15:02:24 +0100 Subject: [PATCH 04/86] Simple mode: Emboss related improvements: * Deleted "Add text part/negative volume" items from context menu * Disable menu item "Split to parts" for object's context menu * "T"-key doesn't cause an adding of the text-part * Load 3mf by DnD: Add info dialog for a case of "Load geometry" + Change No button to Cancel --- src/slic3r/GUI/GUI_Factories.cpp | 4 ++-- src/slic3r/GUI/GUI_ObjectList.cpp | 3 +++ src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 3 +++ src/slic3r/GUI/Plater.cpp | 8 +++----- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 3a51ac4254..d71a128b0b 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -555,8 +555,8 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu) menu->Destroy(range_id); if (wxGetApp().get_mode() == comSimple) { - append_menu_item_add_text(menu, ModelVolumeType::MODEL_PART, false); - append_menu_item_add_text(menu, ModelVolumeType::NEGATIVE_VOLUME, false); + //append_menu_item_add_text(menu, ModelVolumeType::MODEL_PART, false); + //append_menu_item_add_text(menu, ModelVolumeType::NEGATIVE_VOLUME, false); append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "", [](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); }, diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 6de339a7fc..954f61813b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2480,6 +2480,9 @@ bool ObjectList::is_splittable(bool to_objects) return false; } + if (wxGetApp().get_mode() == comSimple) + return false; // suppress to split to parts for simple mode + ModelVolume* volume; if (!get_volume_by_item(item, volume) || !volume) return false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 0071e34504..82b5747ccd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -861,6 +861,9 @@ void GLGizmoEmboss::on_set_state() if (m_volume == nullptr) { // reopen gizmo when new object is created GLGizmoBase::m_state = GLGizmoBase::Off; + if (wxGetApp().get_mode() == comSimple) + // It's impossible to add a part in simple mode + return; // start creating new object create_volume(ModelVolumeType::MODEL_PART); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 79ce8e4b15..1ab23b3442 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2641,7 +2641,6 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if (model.looks_like_multipart_object()) { - //wxMessageDialog msg_dlg(q, _L( MessageDialog msg_dlg(q, _L( "This file contains several objects positioned at multiple heights.\n" "Instead of considering them as multiple objects, should \n" @@ -2652,10 +2651,9 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } } - else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { - //wxMessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n", + if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { MessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n", - _L("Detected advanced data"), wxICON_WARNING | wxYES | wxNO); + _L("Detected advanced data"), wxICON_WARNING | wxYES | wxCANCEL); if (msg_dlg.ShowModal() == wxID_YES) { Slic3r::GUI::wxGetApp().save_mode(comAdvanced); view3D->set_as_dirty(); @@ -6156,7 +6154,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/* break; } case ProjectDropDialog::LoadType::LoadGeometry: { - Plater::TakeSnapshot snapshot(this, _L("Import Object")); +// Plater::TakeSnapshot snapshot(this, _L("Import Object")); load_files({ *it }, true, false); break; } From 6da3c5799c449c7e067f74039da886012c200c9e Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 17 Jan 2023 15:25:00 +0100 Subject: [PATCH 05/86] Grayed hint text after open font list --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 96bd4a9eb9..62a519fc64 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1969,7 +1969,9 @@ void GLGizmoEmboss::draw_font_list() ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_CharsUppercase; // change color of hint to normal text - ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + bool is_popup_open = ImGui::IsPopupOpen(popup_id); + if (!is_popup_open) + ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyleColorVec4(ImGuiCol_Text)); if (ImGui::InputTextWithHint(input_id, selected, &m_face_names.search, input_flags)) { // update filtration result m_face_names.hide = std::vector(m_face_names.faces.size(), {false}); @@ -1980,7 +1982,8 @@ void GLGizmoEmboss::draw_font_list() m_face_names.hide[index] = !name._Starts_with(m_face_names.search); } } - ImGui::PopStyleColor(); // revert changes for hint color + if (!is_popup_open) + ImGui::PopStyleColor(); // revert changes for hint color const bool is_input_text_active = ImGui::IsItemActive(); From 2f0cde9e9e7c5e92f2c10e36997e5cc93c1ee61a Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 17 Jan 2023 12:04:02 +0100 Subject: [PATCH 06/86] make exception_ptr static --- src/libslic3r/PrintConfig.cpp | 3 ++- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1c2947099a..c8e0ae2706 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -841,7 +841,8 @@ void PrintConfigDef::init_fff_params() def = this->add("extra_perimeters_on_overhangs", coBool); def->label = L("Extra perimeters on overhangs (Experimental)"); def->category = L("Layers and Perimeters"); - def->tooltip = L("Create additional perimeter paths over steep overhangs and areas where bridges cannot be anchored."); + def->tooltip = L("Detect overhang areas where bridges cannot be anchored, and fill them with " + "extra perimeter paths. These paths are anchored to the nearby non-overhang area when possible."); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 678761924c..31fb0ba552 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -235,7 +235,7 @@ void BackgroundSlicingProcess::thread_proc() // Process the background slicing task. m_state = STATE_RUNNING; lck.unlock(); - std::exception_ptr exception; + static std::exception_ptr exception; #ifdef _WIN32 this->call_process_seh_throw(exception); #else From 363618beb823b6dce3668045cd0d733c9b950770 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 17 Jan 2023 13:30:04 +0100 Subject: [PATCH 07/86] Linux crash fixes --- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 31fb0ba552..678761924c 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -235,7 +235,7 @@ void BackgroundSlicingProcess::thread_proc() // Process the background slicing task. m_state = STATE_RUNNING; lck.unlock(); - static std::exception_ptr exception; + std::exception_ptr exception; #ifdef _WIN32 this->call_process_seh_throw(exception); #else From 823eb1a2510febcde89e61fd5f60ddfacd7ec171 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 17 Jan 2023 14:05:09 +0100 Subject: [PATCH 08/86] disable dynamic overhang speed by default. We can enable them only in our profiles, and it is risky to set speed for unknown printers --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c8e0ae2706..bc6d81b2d7 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -534,7 +534,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Speed"); def->tooltip = L("This setting enables dynamic speed control on overhangs."); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("overhang_overlap_levels", coPercents); def->full_label = L("Overhang overlap levels"); From ee5f4f8573fd553a7a5685f90ccf246d1c6eea4c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 17 Jan 2023 16:23:01 +0100 Subject: [PATCH 09/86] fix FDM supports autogenerate button sometimes not working correctly --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 14 ++++---------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 1 - 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 9b06e53408..6e12527d58 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -517,14 +517,6 @@ bool GLGizmoFdmSupports::has_backend_supports() return done; } -void GLGizmoFdmSupports::reslice_FDM_supports(bool postpone_error_messages) const { - wxGetApp().CallAfter( - [this, postpone_error_messages]() { - wxGetApp().plater()->reslice_FFF_until_step(posSupportSpotsSearch, - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - void GLGizmoFdmSupports::auto_generate() { ModelObject *mo = m_c->selection_info()->model_object(); @@ -549,8 +541,10 @@ void GLGizmoFdmSupports::auto_generate() } } - this->waiting_for_autogenerated_supports = true; - wxGetApp().CallAfter([this]() { reslice_FDM_supports(); }); + wxGetApp().CallAfter([this]() { + wxGetApp().plater()->reslice_FFF_until_step(posSupportSpotsSearch, *m_c->selection_info()->model_object(), false); + this->waiting_for_autogenerated_supports = true; + }); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index fb20181d87..b79e1dda7e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -44,7 +44,6 @@ private: bool waiting_for_autogenerated_supports = false; bool has_backend_supports(); - void reslice_FDM_supports(bool postpone_error_messages = false) const; void auto_generate(); void apply_data_from_backend(); }; From 88d1718a5af543472fc19ed666ba982daa41ec96 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 17 Jan 2023 16:45:39 +0100 Subject: [PATCH 10/86] revisit SupportSpotSearch step invalidation and fix issues --- src/libslic3r/PrintApply.cpp | 1 - src/libslic3r/PrintObject.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 4f586a17c9..cc0a790d33 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1201,7 +1201,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } // Invalidate just the supports step. for (const PrintObjectStatus &print_object_status : print_objects_range) { - update_apply_status(print_object_status.print_object->invalidate_step(posSupportSpotsSearch)); update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial)); } if (supports_differ) { diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 42bb6bf31d..abbf0fb936 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -789,10 +789,10 @@ bool PrintObject::invalidate_step(PrintObjectStep step) // propagate to dependent steps if (step == posPerimeters) { - invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posEstimateCurledExtrusions }); + invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posPrepareInfill) { - invalidated |= this->invalidate_steps({ posInfill, posIroning }); + invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch }); } else if (step == posInfill) { invalidated |= this->invalidate_steps({ posIroning, posSupportSpotsSearch }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); From 4d108d84a2326cb2e4f1920fb794856867d248e6 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 18 Jan 2023 09:18:48 +0100 Subject: [PATCH 11/86] Fix for start with feature on linux platform --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 7f9befc9df..df22dde101 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1982,7 +1982,10 @@ void GLGizmoEmboss::draw_font_list() size_t index = &face - &m_face_names.faces.front(); std::string name(face.wx_name.ToUTF8().data()); std::transform(name.begin(), name.end(), name.begin(), ::toupper); - m_face_names.hide[index] = !name._Starts_with(m_face_names.search); + + // It should use C++ 20 feature https://en.cppreference.com/w/cpp/string/basic_string/starts_with + bool start_with = boost::starts_with(name, m_face_names.search); + m_face_names.hide[index] = !start_with; } } if (!is_popup_open) From bef64ecec083a2c2e59bd18e373652c597bd6c35 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 15:20:45 +0200 Subject: [PATCH 12/86] Don't call search_ground_route for every ground point candidate search_ground_route already tries to find a suitable ground point globally from the source. Different destination arguments will not help much but will cause a lot of redundant cpu burning SPE-1303 --- src/libslic3r/BranchingTree/BranchingTree.cpp | 2 +- src/libslic3r/BranchingTree/BranchingTree.hpp | 1 - src/libslic3r/SLA/BranchingTreeSLA.cpp | 14 +++++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 6463a30de9..3e5e0a6862 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -5,7 +5,7 @@ #include #include -#include "libslic3r/SLA/SupportTreeUtils.hpp" +#include "libslic3r/TriangleMesh.hpp" namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index c88410b3ab..b54d47ca27 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -5,7 +5,6 @@ #include #include "libslic3r/ExPolygon.hpp" -#include "libslic3r/BoundingBox.hpp" namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 72314c9d83..59ec051138 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -18,6 +18,8 @@ class BranchingTreeBuilder: public branchingtree::Builder { const SupportableMesh &m_sm; const branchingtree::PointCloud &m_cloud; + std::set m_ground_mem; + // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour static constexpr double WIDENING_SCALE = 0.02; @@ -156,11 +158,21 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node, bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, const branchingtree::Node &to) { - bool ret = search_ground_route(ex_tbb, m_builder, m_sm, + bool ret = false; + + auto it = m_ground_mem.find(from.id); + if (it == m_ground_mem.end()) { + ret = search_ground_route(ex_tbb, m_builder, m_sm, sla::Junction{from.pos.cast(), get_radius(from)}, get_radius(to)).first; + // Remember that this node was tested if can go to ground, don't + // test it with any other destination ground point because + // it is unlikely that search_ground_route would find a better solution + m_ground_mem.insert(from.id); + } + if (ret) { build_subtree(from.id); } From 31fb0ae04902acd0083e09baca0d3b632fa60b6e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 16:58:06 +0200 Subject: [PATCH 13/86] Do not add junctions for elongated bed connections It prevents search_ground_route from finding good solutions This should at least improve on SPE-1311 --- src/libslic3r/BranchingTree/BranchingTree.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 3e5e0a6862..72ecf8c86c 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,18 +76,7 @@ void build_tree(PointCloud &nodes, Builder &builder) switch (type) { case BED: { closest_node.weight = w; - if (closest_it->dst_branching > nodes.properties().max_branch_length()) { - auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; - Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; - new_node.id = int(nodes.next_junction_id()); - new_node.weight = nodes.get(node_id).weight + hl_br_len; - new_node.left = node.id; - if ((routed = builder.add_bridge(node, new_node))) { - size_t new_idx = nodes.insert_junction(new_node); - ptsqueue.push(new_idx); - } - } - else if ((routed = builder.add_ground_bridge(node, closest_node))) { + if ((routed = builder.add_ground_bridge(node, closest_node))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; nodes.mark_unreachable(closest_node_id); From f5c16236421d7ae29674fc7cafea9bb5299ec24c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 17:33:58 +0200 Subject: [PATCH 14/86] Try routing unsuccessful branches to ground recursively This helps to avoid huge branches being discarded where there is an obvious route to ground for a sub-branch. Should improve SPE-1311 --- src/libslic3r/BranchingTree/PointCloud.hpp | 6 +++--- src/libslic3r/SLA/BranchingTreeSLA.cpp | 23 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index f99b179902..143ab72ce8 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -259,9 +259,9 @@ template void traverse(PC &&pc, size_t root, Fn &&fn) { if (auto nodeptr = pc.find(root); nodeptr != nullptr) { auto &nroot = *nodeptr; - fn(nroot); - if (nroot.left >= 0) traverse(pc, nroot.left, fn); - if (nroot.right >= 0) traverse(pc, nroot.right, fn); + bool r = fn(nroot); + if (r && nroot.left >= 0) traverse(pc, nroot.left, fn); + if (r && nroot.right >= 0) traverse(pc, nroot.right, fn); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 59ec051138..2660b8deaf 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -61,22 +61,41 @@ class BranchingTreeBuilder: public branchingtree::Builder { toR); m_builder.add_junction(tod, toR); } + + return true; }); } void discard_subtree(size_t root) { // Discard all the support points connecting to this branch. + // As a last resort, try to route child nodes to ground and stop + // traversing if any child branch succeeds. traverse(m_cloud, root, [this](const branchingtree::Node &node) { + bool ret = true; + int suppid_parent = m_cloud.get_leaf_id(node.id); - int suppid_left = m_cloud.get_leaf_id(node.left); - int suppid_right = m_cloud.get_leaf_id(node.right); + int suppid_left = branchingtree::Node::ID_NONE; + int suppid_right = branchingtree::Node::ID_NONE; + + if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), node)) + ret = false; + else + suppid_left = m_cloud.get_leaf_id(node.left); + + if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), node)) + ret = false; + else + suppid_right = m_cloud.get_leaf_id(node.right); + if (suppid_parent >= 0) m_unroutable_pinheads.emplace_back(suppid_parent); if (suppid_left >= 0) m_unroutable_pinheads.emplace_back(suppid_left); if (suppid_right >= 0) m_unroutable_pinheads.emplace_back(suppid_right); + + return ret; }); } From 1b54235d67ae8c1d49b14a821ff10a1c7e009c2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:34:56 +0200 Subject: [PATCH 15/86] connect_to_ground() now handles widening correctly --- src/libslic3r/SLA/SupportTreeUtils.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 38515f8793..983380d12b 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -558,8 +558,7 @@ bool optimize_pinhead_placement(Ex policy, m.cfg.head_back_radius_mm; // check available distance - Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, - sd); + Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd); if (t.distance() < w) { // Let's try to optimize this angle, there might be a @@ -663,11 +662,13 @@ std::pair connect_to_ground(Ex policy, return {false, SupportTreeNode::ID_UNSET}; Vec3d endp = hjp + d * dir; - auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r); + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); + auto ret = create_ground_pillar(policy, builder, sm, endp, dir, pill_r, end_r); if (ret.second >= 0) { - builder.add_bridge(hjp, endp, r); - builder.add_junction(endp, r); + builder.add_diffbridge(hjp, endp, r, pill_r); + builder.add_junction(endp, pill_r); } return ret; @@ -687,9 +688,9 @@ std::pair search_ground_route(Ex policy, if (res.first) return res; - // Optimize bridge direction: - // Straight path failed so we will try to search for a suitable - // direction out of the cavity. + // Optimize bridge direction: + // Straight path failed so we will try to search for a suitable + // direction out of the cavity. auto [polar, azimuth] = dir_to_spheric(init_dir); Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); From ba6b202aa41f30ccc924921b3ff06db7bb2f487a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:05 +0200 Subject: [PATCH 16/86] Fix weight calculation for fallback ground routing in branching tree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 2660b8deaf..633b18324d 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -78,12 +78,16 @@ class BranchingTreeBuilder: public branchingtree::Builder { int suppid_left = branchingtree::Node::ID_NONE; int suppid_right = branchingtree::Node::ID_NONE; - if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), node)) + branchingtree::Node dst = node; + dst.weight += node.pos.z(); + dst.Rmin = std::max(node.Rmin, dst.Rmin); + + if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst)) ret = false; else suppid_left = m_cloud.get_leaf_id(node.left); - if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), node)) + if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), dst)) ret = false; else suppid_right = m_cloud.get_leaf_id(node.right); @@ -143,7 +147,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, double fromR = get_radius(from), toR = get_radius(to); Beam beam{Ball{fromd, fromR}, Ball{tod, toR}}; auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam, - m_sm.cfg.safety_distance_mm); + 0.9 * m_sm.cfg.safety_distance_mm); bool ret = hit.distance() > (tod - fromd).norm(); From ccab4b3dc9c32479ab0298ee1df4e243a7b4304f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:19 +0200 Subject: [PATCH 17/86] Improve optimization accuracy --- src/libslic3r/SLA/SupportTree.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 0ac29ff7ab..66262fb346 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -94,8 +94,8 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; - static const double constexpr optimizer_rel_score_diff = 1e-6; - static const unsigned constexpr optimizer_max_iterations = 1000; + static const double constexpr optimizer_rel_score_diff = 1e-10; + static const unsigned constexpr optimizer_max_iterations = 2000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; From 6448f36c2bbf1304b4ed9896fc5082cba08428da Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:32 +0200 Subject: [PATCH 18/86] Remove redundant headers --- src/libslic3r/BranchingTree/PointCloud.cpp | 1 - src/libslic3r/BranchingTree/PointCloud.hpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index f1d7ae5212..5f07d91c72 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -1,6 +1,5 @@ #include "PointCloud.hpp" -#include "libslic3r/Geometry.hpp" #include "libslic3r/Tesselate.hpp" #include diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index 143ab72ce8..08a1eb8e02 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -5,7 +5,7 @@ #include "BranchingTree.hpp" -#include "libslic3r/Execution/Execution.hpp" +//#include "libslic3r/Execution/Execution.hpp" #include "libslic3r/MutablePriorityQueue.hpp" #include "libslic3r/BoostAdapter.hpp" From 15a1d9a50a0185e4dbaacf89228d6c8da0012425 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Oct 2022 16:10:06 +0200 Subject: [PATCH 19/86] Keep track of avoidance paths and merge them if possible --- src/libslic3r/BranchingTree/BranchingTree.hpp | 5 + src/libslic3r/BranchingTree/PointCloud.hpp | 41 ++++-- src/libslic3r/SLA/BranchingTreeSLA.cpp | 86 ++++++++++-- src/libslic3r/SLA/SupportTreeBuilder.hpp | 4 + src/libslic3r/SLA/SupportTreeUtils.hpp | 129 +++++++++++++----- 5 files changed, 208 insertions(+), 57 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index b54d47ca27..fd69fe4cd1 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -76,6 +76,11 @@ struct Node {} }; +inline bool is_occupied(const Node &n) +{ + return n.left != Node::ID_NONE && n.right != Node::ID_NONE; +} + // An output interface for the branching tree generator function. Consider each // method as a callback and implement the actions that need to be done. class Builder diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index 08a1eb8e02..cbd01a465d 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -78,14 +78,6 @@ private: rtree /* ? */> m_ktree; - bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const - { - Vec3d D = (pt - supp).cast(); - double dot_sq = -D.z() * std::abs(-D.z()); - - return dot_sq < D.squaredNorm() * cos2bridge_slope; - } - template static auto *get_node(PC &&pc, size_t id) { @@ -104,6 +96,14 @@ private: public: + bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const + { + Vec3d D = (pt - supp).cast(); + double dot_sq = -D.z() * std::abs(-D.z()); + + return dot_sq < D.squaredNorm() * cos2bridge_slope; + } + static constexpr auto Unqueued = size_t(-1); struct ZCompareFn @@ -255,13 +255,32 @@ public: } }; +template constexpr bool IsTraverseFn = std::is_invocable_v; + +struct TraverseReturnT +{ + bool to_left : 1; // if true, continue traversing to the left + bool to_right : 1; // if true, continue traversing to the right +}; + template void traverse(PC &&pc, size_t root, Fn &&fn) { if (auto nodeptr = pc.find(root); nodeptr != nullptr) { auto &nroot = *nodeptr; - bool r = fn(nroot); - if (r && nroot.left >= 0) traverse(pc, nroot.left, fn); - if (r && nroot.right >= 0) traverse(pc, nroot.right, fn); + TraverseReturnT ret{true, true}; + + if constexpr (std::is_same_v, void>) { + // Our fn has no return value + fn(nroot); + } else { + // Fn returns instructions about how to continue traversing + ret = fn(nroot); + } + + if (ret.to_left && nroot.left >= 0) + traverse(pc, nroot.left, fn); + if (ret.to_right && nroot.right >= 0) + traverse(pc, nroot.right, fn); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 633b18324d..b8c34399f0 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,6 +20,14 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::set m_ground_mem; + // Establish an index of + using PointIndexEl = std::pair; + boost::geometry::index:: + rtree /* ? */> + m_pillar_index; + + std::vector m_pillars; + // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour static constexpr double WIDENING_SCALE = 0.02; @@ -61,8 +69,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { toR); m_builder.add_junction(tod, toR); } - - return true; }); } @@ -72,7 +78,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // As a last resort, try to route child nodes to ground and stop // traversing if any child branch succeeds. traverse(m_cloud, root, [this](const branchingtree::Node &node) { - bool ret = true; + branchingtree::TraverseReturnT ret{true, true}; int suppid_parent = m_cloud.get_leaf_id(node.id); int suppid_left = branchingtree::Node::ID_NONE; @@ -83,12 +89,12 @@ class BranchingTreeBuilder: public branchingtree::Builder { dst.Rmin = std::max(node.Rmin, dst.Rmin); if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst)) - ret = false; + ret.to_left = false; else suppid_left = m_cloud.get_leaf_id(node.left); if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), dst)) - ret = false; + ret.to_right = false; else suppid_right = m_cloud.get_leaf_id(node.right); @@ -141,7 +147,7 @@ public: }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, - const branchingtree::Node &to) + const branchingtree::Node &to) { Vec3d fromd = from.pos.cast(), tod = to.pos.cast(); double fromR = get_radius(from), toR = get_radius(to); @@ -183,12 +189,72 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, { bool ret = false; + namespace bgi = boost::geometry::index; + + struct Output { + std::optional &res; + + Output& operator *() { return *this; } + void operator=(const PointIndexEl &el) { res = el; } + Output& operator++() { return *this; } + }; + auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - ret = search_ground_route(ex_tbb, m_builder, m_sm, - sla::Junction{from.pos.cast(), - get_radius(from)}, - get_radius(to)).first; + std::optional result; + auto filter = bgi::satisfies( + [this, &from](const PointIndexEl &e) { + auto len = (from.pos - e.first).norm(); + return !branchingtree::is_occupied(m_pillars[e.second]) + && len < m_sm.cfg.max_bridge_length_mm + && !m_cloud.is_outside_support_cone(from.pos, e.first) + && beam_mesh_hit(ex_tbb, + m_sm.emesh, + Beam{Ball{from.pos.cast(), + get_radius(from)}, + Ball{e.first.cast(), + get_radius( + m_pillars[e.second])}}, + 0.9 * m_sm.cfg.safety_distance_mm) + .distance() + > len; + }); + m_pillar_index.query(filter && bgi::nearest(from.pos, 1), Output{result}); + + sla::Junction j{from.pos.cast(), get_radius(from)}; + if (!result) { + auto [found_conn, cjunc] = optimize_ground_connection( + ex_tbb, + m_builder, + m_sm, + j, + get_radius(to)); + + if (found_conn) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; + auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); + + if (plr.second >= 0) { + m_builder.add_junction(endp, R); + if (cjunc) { + m_builder.add_diffbridge(j.pos, endp, j.r, R); + branchingtree::Node n{cjunc->pos.cast(), float(R)}; + n.left = from.id; + m_pillars.emplace_back(n); + m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + } + + ret = true; + } + } + } else { + const auto &resnode = m_pillars[result->second]; + m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); + m_pillars[result->second].right = from.id; + ret = true; + } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 29d34ab8e3..87e250f659 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -189,6 +189,10 @@ struct DiffBridge: public Bridge { DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e) : Bridge{p_s, p_e, r_s}, end_r{r_e} {} + + DiffBridge(const Junction &j_s, const Junction &j_e) + : Bridge{j_s.pos, j_e.pos, j_s.r}, end_r{j_e.r} + {} }; // This class will hold the support tree parts (not meshes, but logical parts) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 983380d12b..eed5e7938d 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -283,17 +283,17 @@ Hit pinhead_mesh_hit(Ex ex, template Hit pinhead_mesh_hit(Ex ex, - const AABBMesh &mesh, - const Head &head, - double safety_d) + const AABBMesh &mesh, + const Head &head, + double safety_d) { return pinhead_mesh_hit(ex, mesh, head.pos, head.dir, head.r_pin_mm, - head.r_back_mm, head.width_mm, safety_d); + head.r_back_mm, head.width_mm, safety_d); } template -std::optional search_widening_path(Ex policy, - const SupportableMesh &sm, +std::optional search_widening_path(Ex policy, + const SupportableMesh &sm, const Vec3d &jp, const Vec3d &dir, double radius, @@ -635,56 +635,61 @@ std::optional calculate_pinhead_placement(Ex policy, } template -std::pair connect_to_ground(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) +std::pair> find_ground_connection( + Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + const Vec3d &dir, + double end_r) { auto hjp = j.pos; double r = j.r; auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); - double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); - double d = 0, tdown = 0; - t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm); + double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd) + .distance(); + double d = 0, tdown = 0; + t = std::min(t, + sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm); - while (d < t && - !std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, - Beam{hjp + d * dir, DOWN, r, r2}, sd) - .distance())) { + while ( + d < t && + !std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, + Beam{hjp + d * dir, DOWN, r, r2}, sd) + .distance())) { d += r; } - if(!std::isinf(tdown)) - return {false, SupportTreeNode::ID_UNSET}; + std::pair> ret; - Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); - double pill_r = r + bridge_ratio * (end_r - r); - auto ret = create_ground_pillar(policy, builder, sm, endp, dir, pill_r, end_r); + if (std::isinf(tdown)) { + ret.first = true; + if (d > 0) { + Vec3d endp = hjp + d * dir; + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); - if (ret.second >= 0) { - builder.add_diffbridge(hjp, endp, r, pill_r); - builder.add_junction(endp, pill_r); + ret.second = Junction{endp, pill_r}; + } } return ret; } template -std::pair search_ground_route(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - double end_radius, - const Vec3d &init_dir = DOWN) +std::pair> optimize_ground_connection( + Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) { double downdst = j.pos.z() - ground_level(sm); - auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius); + auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius); if (res.first) return res; @@ -710,7 +715,59 @@ std::pair search_ground_route(Ex policy, Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); - return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius); + return find_ground_connection(policy, builder, sm, j, bridgedir, end_radius); +} + +template +std::pair connect_to_ground(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + const Vec3d &dir, + double end_r) +{ + std::pair ret = {false, SupportTreeNode::ID_UNSET}; + + auto [found_c, cjunc] = find_ground_connection(policy, builder, sm, j, dir, end_r); + + if (found_c) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + + if (ret.second >= 0) { + builder.add_diffbridge(j.pos, endp, j.r, R); + builder.add_junction(endp, R); + } + } + + return ret; +} + +template +std::pair search_ground_route(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + double end_r, + const Vec3d &init_dir = DOWN) +{ + std::pair ret = {false, SupportTreeNode::ID_UNSET}; + + auto [found_c, cjunc] = optimize_ground_connection(policy, builder, sm, j, end_r, init_dir); + if (found_c) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; + ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + + if (ret.second >= 0) { + builder.add_diffbridge(j.pos, endp, j.r, R); + builder.add_junction(endp, R); + } + } + + return ret; } template From 84784259ba6deeb742209034c7bcd2c7629704b9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Oct 2022 17:17:59 +0200 Subject: [PATCH 20/86] Add max_weight_on_model parameter Limiting the weight of subtrees going to the model body --- src/libslic3r/Preset.cpp | 1 + src/libslic3r/PrintConfig.cpp | 11 +++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 3 +++ src/libslic3r/SLA/SupportTree.hpp | 4 +++- src/libslic3r/SLAPrint.cpp | 2 ++ src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 206844b7b4..ce3c27bf64 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -501,6 +501,7 @@ static std::vector s_Preset_sla_print_options { "support_pillar_diameter", "support_small_pillar_diameter_percent", "support_max_bridges_on_pillar", + "support_max_weight_on_model", "support_pillar_connection_mode", "support_buildplate_only", "support_pillar_widening_factor", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index bc6d81b2d7..482e36e432 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3676,6 +3676,17 @@ void PrintConfigDef::init_sla_params() def->mode = comExpert; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("support_max_weight_on_model", coFloat); + def->label = L("Max weight on model"); + def->category = L("Supports"); + def->tooltip = L( + "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " + "branches emanating from the endpoint."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(10.)); + def = this->add("support_pillar_connection_mode", coEnum); def->label = L("Pillar connection mode"); def->tooltip = L("Controls the bridge type between two neighboring pillars." diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a36125a07e..47c43148a3 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -861,6 +861,8 @@ PRINT_CONFIG_CLASS_DEFINE( // Generate only ground facing supports ((ConfigOptionBool, support_buildplate_only)) + ((ConfigOptionFloat, support_max_weight_on_model)) + // TODO: unimplemented at the moment. This coefficient will have an impact // when bridges and pillars are merged. The resulting pillar should be a bit // thicker than the ones merging into it. How much thicker? I don't know diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index b8c34399f0..62431a347b 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -272,6 +272,9 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, const branchingtree::Node &to) { + if (from.weight > m_sm.cfg.max_weight_on_model_support) + return false; + sla::Junction fromj = {from.pos.cast(), get_radius(from)}; auto anchor = m_sm.cfg.ground_facing_only ? diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 66262fb346..051d569266 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -76,7 +76,9 @@ struct SupportTreeConfig double pillar_base_safety_distance_mm = 0.5; unsigned max_bridges_on_pillar = 3; - + + double max_weight_on_model_support = 10.f; + double head_fullwidth() const { return 2 * head_front_radius_mm + head_width_mm + 2 * head_back_radius_mm - head_penetration_mm; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d9b8e33df0..21895f9431 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -66,6 +66,7 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt()); + scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat(); return scfg; } @@ -843,6 +844,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vectorappend_single_option_line("support_pillar_connection_mode"); optgroup->append_single_option_line("support_buildplate_only"); optgroup->append_single_option_line("support_pillar_widening_factor"); + optgroup->append_single_option_line("support_max_weight_on_model"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); optgroup->append_single_option_line("support_base_safety_distance"); From a20cf5521df9b118351988edb2967c992f30bcd4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 2 Nov 2022 18:11:01 +0100 Subject: [PATCH 21/86] ground route merges wip on separating ground route search and actual creation wip Some fixes but still problems with pedestals --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 104 ++++++---- src/libslic3r/SLA/DefaultSupportTree.cpp | 11 +- src/libslic3r/SLA/DefaultSupportTree.hpp | 3 +- src/libslic3r/SLA/SupportTreeBuilder.hpp | 29 +-- src/libslic3r/SLA/SupportTreeUtils.hpp | 234 +++++++++++++++++++---- 5 files changed, 291 insertions(+), 90 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 62431a347b..96675288d4 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -32,7 +32,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // widening behaviour static constexpr double WIDENING_SCALE = 0.02; - double get_radius(const branchingtree::Node &j) + double get_radius(const branchingtree::Node &j) const { double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight; @@ -109,6 +109,44 @@ class BranchingTreeBuilder: public branchingtree::Builder { }); } + std::optional + search_for_existing_pillar(const branchingtree::Node &from) const + { + namespace bgi = boost::geometry::index; + + struct Output + { + std::optional &res; + + Output &operator*() { return *this; } + void operator=(const PointIndexEl &el) { res = el; } + Output &operator++() { return *this; } + }; + + std::optional result; + + auto filter = bgi::satisfies([this, &from](const PointIndexEl &e) { + assert(e.second < m_pillars.size()); + + auto len = (from.pos - e.first).norm(); + const branchingtree::Node &to = m_pillars[e.second]; + double sd = m_sm.cfg.safety_distance_mm; + + Beam beam{Ball{from.pos.cast(), get_radius(from)}, + Ball{e.first.cast(), get_radius(to)}}; + + return !branchingtree::is_occupied(to) && + len < m_sm.cfg.max_bridge_length_mm && + !m_cloud.is_outside_support_cone(from.pos, e.first) && + beam_mesh_hit(ex_tbb, m_sm.emesh, beam, sd).distance() > len; + }); + + m_pillar_index.query(filter && bgi::nearest(from.pos, 1), + Output{result}); + + return result; + } + public: BranchingTreeBuilder(SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -153,7 +191,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, double fromR = get_radius(from), toR = get_radius(to); Beam beam{Ball{fromd, fromR}, Ball{tod, toR}}; auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam, - 0.9 * m_sm.cfg.safety_distance_mm); + m_sm.cfg.safety_distance_mm); bool ret = hit.distance() > (tod - fromd).norm(); @@ -201,53 +239,43 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - std::optional result; - auto filter = bgi::satisfies( - [this, &from](const PointIndexEl &e) { - auto len = (from.pos - e.first).norm(); - return !branchingtree::is_occupied(m_pillars[e.second]) - && len < m_sm.cfg.max_bridge_length_mm - && !m_cloud.is_outside_support_cone(from.pos, e.first) - && beam_mesh_hit(ex_tbb, - m_sm.emesh, - Beam{Ball{from.pos.cast(), - get_radius(from)}, - Ball{e.first.cast(), - get_radius( - m_pillars[e.second])}}, - 0.9 * m_sm.cfg.safety_distance_mm) - .distance() - > len; - }); - m_pillar_index.query(filter && bgi::nearest(from.pos, 1), Output{result}); + std::optional result = search_for_existing_pillar(from); sla::Junction j{from.pos.cast(), get_radius(from)}; if (!result) { - auto [found_conn, cjunc] = optimize_ground_connection( + auto conn = optimize_ground_connection( ex_tbb, m_builder, m_sm, j, get_radius(to)); - if (found_conn) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; - auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); + if (conn) { + build_ground_connection(m_builder, m_sm, conn); + Junction connlast = conn.path.back(); + branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; + n.left = from.id; + m_pillars.emplace_back(n); + m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + ret = true; - if (plr.second >= 0) { - m_builder.add_junction(endp, R); - if (cjunc) { - m_builder.add_diffbridge(j.pos, endp, j.r, R); - branchingtree::Node n{cjunc->pos.cast(), float(R)}; - n.left = from.id; - m_pillars.emplace_back(n); - m_pillar_index.insert({n.pos, m_pillars.size() - 1}); - } +// Vec3d endp = cjunc? cjunc->pos : j.pos; +// double R = cjunc? cjunc->r : j.r; +// Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; +// auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); - ret = true; - } +// if (plr.second >= 0) { +// m_builder.add_junction(endp, R); +// if (cjunc) { +// m_builder.add_diffbridge(j.pos, endp, j.r, R); +// branchingtree::Node n{cjunc->pos.cast(), float(R)}; +// n.left = from.id; +// m_pillars.emplace_back(n); +// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); +// } + +// ret = true; +// } } } else { const auto &resnode = m_pillars[result->second]; diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 9e21fca454..63280b9df7 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -340,15 +340,13 @@ bool DefaultSupportTree::connect_to_nearpillar(const Head &head, return true; } -bool DefaultSupportTree::create_ground_pillar(const Vec3d &hjp, +bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, const Vec3d &sourcedir, - double radius, long head_id) { auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, m_builder, m_sm, hjp, - sourcedir, radius, radius, - head_id); + sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, @@ -587,7 +585,7 @@ void DefaultSupportTree::routing_to_ground() Head &h = m_builder.head(hid); - if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { + if (!create_ground_pillar(h.junction(), h.dir, h.id)) { BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; m_iheads_onmodel.emplace_back(h.id); @@ -615,10 +613,9 @@ void DefaultSupportTree::routing_to_ground() if (!connect_to_nearpillar(sidehead, centerpillarID) && !search_pillar_and_connect(sidehead)) { - Vec3d pstart = sidehead.junction_point(); // Vec3d pend = Vec3d{pstart.x(), pstart.y(), gndlvl}; // Could not find a pillar, create one - create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); + create_ground_pillar(sidehead.junction(), sidehead.dir, sidehead.id); } } } diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index ff0269978d..cea4b65e17 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -176,9 +176,8 @@ class DefaultSupportTree { // jp is the starting junction point which needs to be routed down. // sourcedir is the allowed direction of an optional bridge between the // jp junction and the final pillar. - bool create_ground_pillar(const Vec3d &jp, + bool create_ground_pillar(const Junction &jp, const Vec3d &sourcedir, - double radius, long head_id = SupportTreeNode::ID_UNSET); void add_pillar_base(long pid) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 87e250f659..e4567e405a 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -67,6 +67,14 @@ struct SupportTreeNode long id = ID_UNSET; // For identification withing a tree. }; +// A junction connecting bridges and pillars +struct Junction: public SupportTreeNode { + double r = 1; + Vec3d pos; + + Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} +}; + // A pinhead originating from a support point struct Head: public SupportTreeNode { Vec3d dir = DOWN; @@ -77,7 +85,6 @@ struct Head: public SupportTreeNode { double width_mm = 2; double penetration_mm = 0.5; - // If there is a pillar connecting to this head, then the id will be set. long pillar_id = ID_UNSET; @@ -103,21 +110,21 @@ struct Head: public SupportTreeNode { { return real_width() - penetration_mm; } - + + inline Junction junction() const + { + Junction j{pos + (fullwidth() - r_back_mm) * dir, r_back_mm}; + j.id = -this->id; // Remember that this junction is from a head + + return j; + } + inline Vec3d junction_point() const { - return pos + (fullwidth() - r_back_mm) * dir; + return junction().pos; } }; -// A junction connecting bridges and pillars -struct Junction: public SupportTreeNode { - double r = 1; - Vec3d pos; - - Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} -}; - // A straight pillar. Only has an endpoint and a height. No explicit starting // point is given, as it would allow the pillar to be angled. // Some connection info with other primitives can also be tracked. diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index eed5e7938d..e7bf9f018b 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include namespace Slic3r { namespace sla { @@ -358,19 +360,19 @@ std::pair create_ground_pillar( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, - const Vec3d &pinhead_junctionpt, + const Junction &pinhead_junctionpt, const Vec3d &sourcedir, - double radius, double end_radius, long head_id = SupportTreeNode::ID_UNSET) { - Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir; + Vec3d jp = pinhead_junctionpt.pos, endp = jp, dir = sourcedir; long pillar_id = SupportTreeNode::ID_UNSET; bool can_add_base = false, non_head = false; double gndlvl = 0.; // The Z level where pedestals should be double jp_gnd = 0.; // The lowest Z where a junction center can be double gap_dist = 0.; // The gap distance between the model and the pad + double radius = pinhead_junctionpt.r; double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); @@ -634,8 +636,185 @@ std::optional calculate_pinhead_placement(Ex policy, return {}; } +struct GroundConnection { + // Currently, a ground connection will contain at most 2 additional junctions + // which will not require any allocations. If I come up with an algo that + // can produce a route to ground with more junctions, this design will be + // able to handle that. + static constexpr size_t MaxExpectedJunctions = 3; + + boost::container::small_vector path; + double end_radius; + std::optional pillar_base; + + operator bool() const { return !path.empty(); } +}; + template -std::pair> find_ground_connection( +GroundConnection find_pillar_route(Ex policy, +// SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &source, + const Vec3d &sourcedir, + double end_radius) +{ + GroundConnection ret; + + Vec3d jp = source.pos, endp = jp, dir = sourcedir; +// long pillar_id = SupportTreeNode::ID_UNSET; + bool can_add_base = false/*, non_head = false*/; + + double gndlvl = 0.; // The Z level where pedestals should be + double jp_gnd = 0.; // The lowest Z where a junction center can be + double gap_dist = 0.; // The gap distance between the model and the pad + double radius = source.r; + + double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); + + auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + + auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] + (bool base_en = true) + { + can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; + double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.; + gndlvl = ground_level(sm); + if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; + jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); + gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; + }; + + eval_limits(); + + // We are dealing with a mini pillar that's potentially too long + if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) + { + std::optional diffbr = + search_widening_path(policy, sm, jp, dir, radius, + sm.cfg.head_back_radius_mm); + + if (diffbr && diffbr->endp.z() > jp_gnd) { +// auto &br = builder.add_diffbridge(*diffbr); +// if (head_id >= 0) +// builder.head(head_id).bridge_id = br.id; + ret.path.emplace_back(source); + endp = diffbr->endp; + radius = diffbr->end_r; +// builder.add_junction(endp, radius); + ret.path.emplace_back(endp, radius); +// non_head = true; + dir = diffbr->get_dir(); + eval_limits(); + } else return ret;//return {false, pillar_id}; + } + + if (sm.cfg.object_elevation_mm < EPSILON) + { + // get a suitable direction for the corrector bridge. It is the + // original sourcedir's azimuth but the polar angle is saturated to the + // configured bridge slope. + auto [polar, azimuth] = dir_to_spheric(dir); + polar = PI - sm.cfg.bridge_slope; + Vec3d d = spheric_to_dir(polar, azimuth).normalized(); + auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); + double tmax = std::min(sm.cfg.max_bridge_length_mm, t); + t = 0.; + + double zd = endp.z() - jp_gnd; + double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + Vec3d nexp = endp; + double dlast = 0.; + while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) && + t < tmax) + { + t += radius; + nexp = endp + t * d; + } + + if (dlast < gap_dist && can_add_base) { + nexp = endp; + t = 0.; + can_add_base = false; + eval_limits(can_add_base); + + zd = endp.z() - jp_gnd; + tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) { + t += radius; + nexp = endp + t * d; + } + } + + // Could not find a path to avoid the pad gap + if (dlast < gap_dist) { + ret.path.clear(); + return ret; + //return {false, pillar_id}; + } + + if (t > 0.) { // Need to make additional bridge +// const Bridge& br = builder.add_bridge(endp, nexp, radius); +// if (head_id >= 0) +// builder.head(head_id).bridge_id = br.id; + +// builder.add_junction(nexp, radius); + ret.path.emplace_back(nexp, radius); + endp = nexp; +// non_head = true; + } + } + + Vec3d gp = to_floor(endp); + double h = endp.z() - gp.z(); + +// pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) : +// builder.add_pillar(gp, h, radius, end_radius); + ret.end_radius = end_radius; + + if (can_add_base) { + ret.pillar_base = Pedestal{gp, h, sm.cfg.base_height_mm, sm.cfg.base_radius_mm}; +// builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, +// sm.cfg.base_radius_mm); + } + + return ret; //{true, pillar_id}; +} + +inline long build_ground_connection(SupportTreeBuilder &builder, + const SupportableMesh &sm, + const GroundConnection &conn) +{ + long ret = SupportTreeNode::ID_UNSET; + + if (!conn) + return ret; + + auto it = conn.path.begin(); + auto itnx = std::next(it); + + while (itnx != conn.path.end()) { + builder.add_diffbridge(*it, *itnx); + } + + auto gp = conn.path.back().pos; + gp.z() = ground_level(sm); + double h = conn.path.back().pos.z() - gp.z(); + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + if (conn.pillar_base) + builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); + + return ret; +} + +template +GroundConnection find_ground_connection( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -662,16 +841,23 @@ std::pair> find_ground_connection( d += r; } - std::pair> ret; + GroundConnection ret; + ret.end_radius = end_r; if (std::isinf(tdown)) { - ret.first = true; + ret.path.emplace_back(j); if (d > 0) { Vec3d endp = hjp + d * dir; double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); double pill_r = r + bridge_ratio * (end_r - r); - ret.second = Junction{endp, pill_r}; +// ret.path.emplace_back(endp, pill_r); + auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); + for (auto &j : route.path) + ret.path.emplace_back(j); + + ret.pillar_base = route.pillar_base; + ret.end_radius = end_r; } } @@ -679,7 +865,7 @@ std::pair> find_ground_connection( } template -std::pair> optimize_ground_connection( +GroundConnection optimize_ground_connection( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -690,7 +876,7 @@ std::pair> optimize_ground_connection( double downdst = j.pos.z() - ground_level(sm); auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius); - if (res.first) + if (!res) return res; // Optimize bridge direction: @@ -728,18 +914,9 @@ std::pair connect_to_ground(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto [found_c, cjunc] = find_ground_connection(policy, builder, sm, j, dir, end_r); - - if (found_c) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); - - if (ret.second >= 0) { - builder.add_diffbridge(j.pos, endp, j.r, R); - builder.add_junction(endp, R); - } - } + auto conn = find_ground_connection(policy, builder, sm, j, dir, end_r); + ret.first = bool(conn); + ret.second = build_ground_connection(builder, sm, conn); return ret; } @@ -754,18 +931,11 @@ std::pair search_ground_route(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto [found_c, cjunc] = optimize_ground_connection(policy, builder, sm, j, end_r, init_dir); - if (found_c) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; - ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + auto conn = optimize_ground_connection(policy, builder, sm, j, + end_r, init_dir); - if (ret.second >= 0) { - builder.add_diffbridge(j.pos, endp, j.r, R); - builder.add_junction(endp, R); - } - } + ret.first = bool(conn); + ret.second = build_ground_connection(builder, sm, conn); return ret; } From f028bfe680c905b52f8050fd9310269ff64f3eee Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 3 Nov 2022 18:16:15 +0100 Subject: [PATCH 22/86] Pillar creation restored but only in branchingtree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 36 ++++++++-------- src/libslic3r/SLA/DefaultSupportTree.cpp | 14 +++++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 52 +++++++++--------------- 3 files changed, 46 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 96675288d4..fcd546ae02 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -26,7 +26,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { rtree /* ? */> m_pillar_index; - std::vector m_pillars; + std::vector m_pillars; // to put an index over them + + // cache succesfull ground connections + std::map m_gnd_connections; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -182,6 +185,13 @@ public: } bool is_valid() const override { return !m_builder.ctl().stopcondition(); } + + + void group_pillars() + { + + } + }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, @@ -251,31 +261,15 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, get_radius(to)); if (conn) { - build_ground_connection(m_builder, m_sm, conn); +// build_ground_connection(m_builder, m_sm, conn); Junction connlast = conn.path.back(); branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; n.left = from.id; m_pillars.emplace_back(n); m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + m_gnd_connections[m_pillars.size() - 1] = conn; + ret = true; - -// Vec3d endp = cjunc? cjunc->pos : j.pos; -// double R = cjunc? cjunc->r : j.r; -// Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; -// auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); - -// if (plr.second >= 0) { -// m_builder.add_junction(endp, R); -// if (cjunc) { -// m_builder.add_diffbridge(j.pos, endp, j.r, R); -// branchingtree::Node n{cjunc->pos.cast(), float(R)}; -// n.left = from.id; -// m_pillars.emplace_back(n); -// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); -// } - -// ret = true; -// } } } else { const auto &resnode = m_pillars[result->second]; @@ -381,6 +375,8 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); + vbuilder.group_pillars(); + for (size_t id : vbuilder.unroutable_pinheads()) builder.head(id).invalidate(); diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 63280b9df7..06477c40cc 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -344,15 +344,21 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, const Vec3d &sourcedir, long head_id) { - auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, - m_builder, m_sm, hjp, - sourcedir, hjp.r, head_id); + long pillar_id = SupportTreeNode::ID_UNSET; + + auto conn = sla::find_pillar_route(suptree_ex_policy, m_sm, hjp, sourcedir, hjp.r); + if (conn) + pillar_id = build_ground_connection(m_builder, m_sm, conn); + +// auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, +// m_builder, m_sm, hjp, +// sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, unsigned(pillar_id)); - return ret; + return bool(conn); } void DefaultSupportTree::add_pinheads() diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index e7bf9f018b..ecc9fdbf7b 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -652,7 +652,6 @@ struct GroundConnection { template GroundConnection find_pillar_route(Ex policy, -// SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &source, const Vec3d &sourcedir, @@ -661,7 +660,6 @@ GroundConnection find_pillar_route(Ex policy, GroundConnection ret; Vec3d jp = source.pos, endp = jp, dir = sourcedir; -// long pillar_id = SupportTreeNode::ID_UNSET; bool can_add_base = false/*, non_head = false*/; double gndlvl = 0.; // The Z level where pedestals should be @@ -694,18 +692,13 @@ GroundConnection find_pillar_route(Ex policy, sm.cfg.head_back_radius_mm); if (diffbr && diffbr->endp.z() > jp_gnd) { -// auto &br = builder.add_diffbridge(*diffbr); -// if (head_id >= 0) -// builder.head(head_id).bridge_id = br.id; ret.path.emplace_back(source); endp = diffbr->endp; radius = diffbr->end_r; -// builder.add_junction(endp, radius); ret.path.emplace_back(endp, radius); -// non_head = true; dir = diffbr->get_dir(); eval_limits(); - } else return ret;//return {false, pillar_id}; + } else return ret; } if (sm.cfg.object_elevation_mm < EPSILON) @@ -760,28 +753,18 @@ GroundConnection find_pillar_route(Ex policy, } if (t > 0.) { // Need to make additional bridge -// const Bridge& br = builder.add_bridge(endp, nexp, radius); -// if (head_id >= 0) -// builder.head(head_id).bridge_id = br.id; - -// builder.add_junction(nexp, radius); ret.path.emplace_back(nexp, radius); endp = nexp; -// non_head = true; } } Vec3d gp = to_floor(endp); - double h = endp.z() - gp.z(); -// pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) : -// builder.add_pillar(gp, h, radius, end_radius); ret.end_radius = end_radius; if (can_add_base) { - ret.pillar_base = Pedestal{gp, h, sm.cfg.base_height_mm, sm.cfg.base_radius_mm}; -// builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, -// sm.cfg.base_radius_mm); + ret.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, sm.cfg.base_radius_mm, end_radius}; } return ret; //{true, pillar_id}; @@ -806,7 +789,15 @@ inline long build_ground_connection(SupportTreeBuilder &builder, auto gp = conn.path.back().pos; gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); - ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + +// TODO: does not work yet +// if (conn.path.back().id < 0) { +// // this is a head +// long head_id = std::abs(conn.path.back().id); +// ret = builder.add_pillar(head_id, h); +// } else + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + if (conn.pillar_base) builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); @@ -846,19 +837,16 @@ GroundConnection find_ground_connection( if (std::isinf(tdown)) { ret.path.emplace_back(j); - if (d > 0) { - Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); - double pill_r = r + bridge_ratio * (end_r - r); + Vec3d endp = hjp + d * dir; + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); -// ret.path.emplace_back(endp, pill_r); - auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - for (auto &j : route.path) - ret.path.emplace_back(j); + auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); + for (auto &j : route.path) + ret.path.emplace_back(j); - ret.pillar_base = route.pillar_base; - ret.end_radius = end_r; - } + ret.pillar_base = route.pillar_base; + ret.end_radius = end_r; } return ret; From 834f428ba01a6f8721ed7803430a866b3ec77e84 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Nov 2022 16:22:23 +0100 Subject: [PATCH 23/86] WIP on grouping ground pillars for branching tree supports --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 28 +++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index fcd546ae02..ce440b2272 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -186,12 +186,18 @@ public: bool is_valid() const override { return !m_builder.ctl().stopcondition(); } + const std::vector & pillars() const { return m_pillars; } - void group_pillars() + const GroundConnection *ground_conn(size_t pillar) const { + const GroundConnection *ret = nullptr; + auto it = m_gnd_connections.find(pillar); + if (it != m_gnd_connections.end()) + ret = &it->second; + + return ret; } - }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, @@ -375,7 +381,23 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); - vbuilder.group_pillars(); + std::vector bedleafs; + for (auto n : vbuilder.pillars()) { + n.left = branchingtree::Node::ID_NONE; + n.right = branchingtree::Node::ID_NONE; + bedleafs.emplace_back(n); + } + + props.max_branch_length(20.f); + branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; + BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; + branchingtree::build_tree(gndnodes, gndbuilder); + + for (size_t pill_id = 0; pill_id < gndbuilder.pillars().size(); ++pill_id) { + auto * conn = gndbuilder.ground_conn(pill_id); + if (conn) + build_ground_connection(builder, sm, *conn); + } for (size_t id : vbuilder.unroutable_pinheads()) builder.head(id).invalidate(); From e62873ff1341544083d5727ae0235f2ddb8085d2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 08:29:42 +0100 Subject: [PATCH 24/86] Prevent uninitialized value in nlopt optimizer --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index f5d3140463..cc7edb97aa 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -68,7 +68,7 @@ template class NLoptOpt {}; template class NLoptOpt> { protected: StopCriteria m_stopcr; - OptDir m_dir; + OptDir m_dir = OptDir::MIN; template using TOptData = std::tuple*, NLoptOpt*, nlopt_opt>; From b4b5e8eb8e6f0ef939b4e7b600ec123f74bdbce9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 08:30:02 +0100 Subject: [PATCH 25/86] WIP on pillar grouping for sla branching supports --- src/libslic3r/BranchingTree/PointCloud.hpp | 5 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 54 ++++++++++++---------- src/libslic3r/SLA/SupportTree.hpp | 4 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index cbd01a465d..03b935f765 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -286,6 +286,11 @@ template void traverse(PC &&pc, size_t root, Fn &&fn) void build_tree(PointCloud &pcloud, Builder &builder); +inline void build_tree(PointCloud &&pc, Builder &builder) +{ + build_tree(pc, builder); +} + }} // namespace Slic3r::branchingtree #endif // POINTCLOUD_HPP diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index ce440b2272..a4605bbfd9 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -122,7 +122,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::optional &res; Output &operator*() { return *this; } - void operator=(const PointIndexEl &el) { res = el; } + Output &operator=(const PointIndexEl &el) { res = el; return *this; } Output &operator++() { return *this; } }; @@ -245,20 +245,12 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, namespace bgi = boost::geometry::index; - struct Output { - std::optional &res; - - Output& operator *() { return *this; } - void operator=(const PointIndexEl &el) { res = el; } - Output& operator++() { return *this; } - }; - auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - std::optional result = search_for_existing_pillar(from); +// std::optional result = search_for_existing_pillar(from); sla::Junction j{from.pos.cast(), get_radius(from)}; - if (!result) { +// if (!result) { auto conn = optimize_ground_connection( ex_tbb, m_builder, @@ -268,21 +260,21 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, if (conn) { // build_ground_connection(m_builder, m_sm, conn); - Junction connlast = conn.path.back(); - branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; - n.left = from.id; - m_pillars.emplace_back(n); - m_pillar_index.insert({n.pos, m_pillars.size() - 1}); +// Junction connlast = conn.path.back(); +// branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; +// n.left = from.id; + m_pillars.emplace_back(from); +// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); m_gnd_connections[m_pillars.size() - 1] = conn; ret = true; } - } else { - const auto &resnode = m_pillars[result->second]; - m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); - m_pillars[result->second].right = from.id; - ret = true; - } +// } else { +// const auto &resnode = m_pillars[result->second]; +// m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); +// m_pillars[result->second].right = from.id; +// ret = true; +// } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because @@ -353,7 +345,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s for (auto &h : heads) if (h && h->is_valid()) { leafs.emplace_back(h->junction_point().cast(), h->r_back_mm); - h->id = leafs.size() - 1; + h->id = long(leafs.size() - 1); builder.add_head(h->id, *h); } @@ -372,7 +364,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s props.sampling_radius()); auto bedpts = branchingtree::sample_bed(props.bed_shape(), - props.ground_level(), + float(props.ground_level()), props.sampling_radius()); branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts), @@ -388,11 +380,23 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s bedleafs.emplace_back(n); } - props.max_branch_length(20.f); + props.max_branch_length(50.f); + auto gndsm = sm; branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; branchingtree::build_tree(gndnodes, gndbuilder); + // All leafs of gndbuilder are nodes that already proved to be routable + // to the ground. gndbuilder should not encounter any unroutable nodes +// assert(gndbuilder.unroutable_pinheads().empty()); + + +// for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) { +// auto * conn = vbuilder.ground_conn(pill_id); +// if (conn) +// build_ground_connection(builder, sm, *conn); +// } + for (size_t pill_id = 0; pill_id < gndbuilder.pillars().size(); ++pill_id) { auto * conn = gndbuilder.ground_conn(pill_id); if (conn) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 051d569266..b70f773192 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -96,8 +96,8 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; - static const double constexpr optimizer_rel_score_diff = 1e-10; - static const unsigned constexpr optimizer_max_iterations = 2000; + static const double constexpr optimizer_rel_score_diff = 1e-16; + static const unsigned constexpr optimizer_max_iterations = 20000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index ecc9fdbf7b..834ade3581 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -875,7 +875,7 @@ GroundConnection optimize_ground_connection( Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = /*j.r **/ sm.cfg.safety_distance_mm /*/ sm.cfg.head_back_radius_mm*/; auto oresult = solver.to_max().optimize( [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { auto &[plr, azm] = input; From 823d28ec4b415c447367fdf3011f5d4f08972bbd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 10:03:55 +0100 Subject: [PATCH 26/86] Fix infinite loop in build_ground_connection --- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 834ade3581..ef132c969b 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -784,6 +784,8 @@ inline long build_ground_connection(SupportTreeBuilder &builder, while (itnx != conn.path.end()) { builder.add_diffbridge(*it, *itnx); + builder.add_junction(*itnx); + ++it; ++itnx; } auto gp = conn.path.back().pos; From 4dc0741766d1adf526b87fb8e8d87d928cc81c67 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 10:07:33 +0100 Subject: [PATCH 27/86] set strict safety distance Don't change with pillar radius --- src/libslic3r/SLA/SupportTreeUtils.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index ef132c969b..1cf9dfdc53 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -709,7 +709,7 @@ GroundConnection find_pillar_route(Ex policy, auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - sm.cfg.bridge_slope; Vec3d d = spheric_to_dir(polar, azimuth).normalized(); - auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance_mm; //radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); double tmax = std::min(sm.cfg.max_bridge_length_mm, t); t = 0.; @@ -817,7 +817,7 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; double r = j.r; - auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance_mm; //r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd) From 1e9bd28714d936277cb925fddcaa3d835bb2ab85 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 15:12:53 +0100 Subject: [PATCH 28/86] Upgrade support tree route search functions, add tests --- src/libslic3r/BranchingTree/BranchingTree.hpp | 3 + src/libslic3r/CMakeLists.txt | 2 + .../Optimize/BruteforceOptimizer.hpp | 4 +- src/libslic3r/Optimize/NLoptOptimizer.hpp | 4 +- src/libslic3r/OrganicTree/OrganicTree.hpp | 95 ++++++++++++++ src/libslic3r/OrganicTree/OrganicTreeImpl.hpp | 11 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 51 ++++---- src/libslic3r/SLA/SupportTree.hpp | 4 +- src/libslic3r/SLA/SupportTreeMesher.cpp | 3 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 84 ++++++------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 + tests/data/U_overhang.obj | 76 +++++++++++ tests/sla_print/CMakeLists.txt | 1 + tests/sla_print/sla_supptreeutils_tests.cpp | 119 ++++++++++++++++++ 14 files changed, 385 insertions(+), 76 deletions(-) create mode 100644 src/libslic3r/OrganicTree/OrganicTree.hpp create mode 100644 src/libslic3r/OrganicTree/OrganicTreeImpl.hpp create mode 100644 tests/data/U_overhang.obj create mode 100644 tests/sla_print/sla_supptreeutils_tests.cpp diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index fd69fe4cd1..3f7fd7b803 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -20,6 +20,9 @@ class Properties ExPolygons m_bed_shape; public: + + constexpr bool group_pillars() const noexcept { return false; } + // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6217a11df4..3740d4e015 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -356,6 +356,8 @@ set(SLIC3R_SOURCES BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp BranchingTree/PointCloud.hpp + OrganicTree/OrganicTree.hpp + OrganicTree/OrganicTreeImpl.hpp Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp diff --git a/src/libslic3r/Optimize/BruteforceOptimizer.hpp b/src/libslic3r/Optimize/BruteforceOptimizer.hpp index 2daef538e7..2f6b422245 100644 --- a/src/libslic3r/Optimize/BruteforceOptimizer.hpp +++ b/src/libslic3r/Optimize/BruteforceOptimizer.hpp @@ -13,7 +13,9 @@ template long num_iter(const std::array &idx, size_t gridsz) { long ret = 0; - for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i); + for (size_t i = 0; i < N; ++i) + ret += idx[i] * std::pow(gridsz, i); + return ret; } diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index cc7edb97aa..3859217da8 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -158,7 +158,7 @@ public: return optimize(nl, std::forward(func), initvals); } - explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {} + explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {} void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } const StopCriteria &get_criteria() const noexcept { return m_stopcr; } @@ -226,7 +226,7 @@ using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlg; +using AlgNLoptMLSL = detail::NLoptAlgComb; }} // namespace Slic3r::opt diff --git a/src/libslic3r/OrganicTree/OrganicTree.hpp b/src/libslic3r/OrganicTree/OrganicTree.hpp new file mode 100644 index 0000000000..cf34c9eb32 --- /dev/null +++ b/src/libslic3r/OrganicTree/OrganicTree.hpp @@ -0,0 +1,95 @@ +#ifndef ORGANICTREE_HPP +#define ORGANICTREE_HPP + +#include +#include +#include + +namespace Slic3r { namespace organictree { + +enum class NodeType { Bed, Mesh, Junction }; + +template struct DomainTraits_ { + using Node = typename T::Node; + + static void push(const T &dom, const Node &n) + { + dom.push_junction(n); + } + + static Node pop(T &dom) { return dom.pop(); } + + static bool empty(const T &dom) { return dom.empty(); } + + static std::optional> + closest(const T &dom, const Node &n) + { + return dom.closest(n); + } + + static Node merge_node(const T &dom, const Node &a, const Node &b) + { + return dom.merge_node(a, b); + } + + static void bridge(T &dom, const Node &from, const Node &to) + { + dom.bridge(from, to); + } + + static void anchor(T &dom, const Node &from, const Node &to) + { + dom.anchor(from, to); + } + + static void pillar(T &dom, const Node &from, const Node &to) + { + dom.pillar(from, to); + } + + static void merge (T &dom, const Node &n1, const Node &n2, const Node &mrg) + { + dom.merge(n1, n2, mrg); + } + + static void report_fail(T &dom, const Node &n) { dom.report_fail(n); } +}; + +template +void build_tree(Domain &&D) +{ + using Dom = DomainTraits_>>; + using Node = typename Dom::Node; + + while (! Dom::empty(D)) { + Node n = Dom::pop(D); + + std::optional> C = Dom::closest(D, n); + + if (!C) { + Dom::report_fail(D, n); + } else switch (C->second) { + case NodeType::Bed: + Dom::pillar(D, n, C->first); + break; + case NodeType::Mesh: + Dom::anchor(D, n, C->first); + break; + case NodeType::Junction: { + Node M = Dom::merge_node(D, n, C->first); + + if (M == C->first) { + Dom::bridge(D, n, C->first); + } else { + Dom::push(D, M); + Dom::merge(D, n, M, C->first); + } + break; + } + } + } +} + +}} // namespace Slic3r::organictree + +#endif // ORGANICTREE_HPP diff --git a/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp b/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp new file mode 100644 index 0000000000..6e18550dac --- /dev/null +++ b/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp @@ -0,0 +1,11 @@ +#ifndef ORGANICTREEIMPL_HPP +#define ORGANICTREEIMPL_HPP + +namespace Slic3r { namespace organictree { + + + + +}} + +#endif // ORGANICTREEIMPL_HPP diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index a4605bbfd9..fc5dc29826 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -253,13 +253,11 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, // if (!result) { auto conn = optimize_ground_connection( ex_tbb, - m_builder, m_sm, j, get_radius(to)); if (conn) { -// build_ground_connection(m_builder, m_sm, conn); // Junction connlast = conn.path.back(); // branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; // n.left = from.id; @@ -321,6 +319,17 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, return bool(anchor); } +inline void build_pillars(SupportTreeBuilder &builder, + BranchingTreeBuilder &vbuilder, + const SupportableMesh &sm) +{ + for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) { + auto * conn = vbuilder.ground_conn(pill_id); + if (conn) + build_ground_connection(builder, sm, *conn); + } +} + void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &sm) { auto coordfn = [&sm](size_t id, size_t dim) { return sm.pts[id].pos(dim); }; @@ -373,34 +382,22 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); - std::vector bedleafs; - for (auto n : vbuilder.pillars()) { - n.left = branchingtree::Node::ID_NONE; - n.right = branchingtree::Node::ID_NONE; - bedleafs.emplace_back(n); - } + if constexpr (props.group_pillars()) { - props.max_branch_length(50.f); - auto gndsm = sm; - branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; - BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; - branchingtree::build_tree(gndnodes, gndbuilder); + std::vector bedleafs; + for (auto n : vbuilder.pillars()) { + n.left = branchingtree::Node::ID_NONE; + n.right = branchingtree::Node::ID_NONE; + bedleafs.emplace_back(n); + } - // All leafs of gndbuilder are nodes that already proved to be routable - // to the ground. gndbuilder should not encounter any unroutable nodes -// assert(gndbuilder.unroutable_pinheads().empty()); + branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; + BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; + branchingtree::build_tree(gndnodes, gndbuilder); - -// for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) { -// auto * conn = vbuilder.ground_conn(pill_id); -// if (conn) -// build_ground_connection(builder, sm, *conn); -// } - - for (size_t pill_id = 0; pill_id < gndbuilder.pillars().size(); ++pill_id) { - auto * conn = gndbuilder.ground_conn(pill_id); - if (conn) - build_ground_connection(builder, sm, *conn); + build_pillars(builder, gndbuilder, sm); + } else { + build_pillars(builder, vbuilder, sm); } for (size_t id : vbuilder.unroutable_pinheads()) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index b70f773192..051d569266 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -96,8 +96,8 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; - static const double constexpr optimizer_rel_score_diff = 1e-16; - static const unsigned constexpr optimizer_max_iterations = 20000; + static const double constexpr optimizer_rel_score_diff = 1e-10; + static const unsigned constexpr optimizer_max_iterations = 2000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp index 3f0b6c8413..6d91de7e62 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.cpp +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -165,7 +165,8 @@ indexed_triangle_set halfcone(double baseheight, { assert(steps > 0); - if (baseheight <= 0 || steps <= 0) return {}; + if (baseheight <= 0 || steps <= 0 || (r_bottom <= 0. && r_top <= 0.)) + return {}; indexed_triangle_set base; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 1cf9dfdc53..070ffbf0d4 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -178,7 +179,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) [&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) { Hit &hit = hits[i]; - // Point on the circle on the pin sphere + // Point on the circle on the pin sphere Vec3d p_src = ring.get(i, src, r_src + sd); Vec3d p_dst = ring.get(i, dst, r_dst + sd); Vec3d raydir = (p_dst - p_src).normalized(); @@ -644,10 +645,9 @@ struct GroundConnection { static constexpr size_t MaxExpectedJunctions = 3; boost::container::small_vector path; - double end_radius; std::optional pillar_base; - operator bool() const { return !path.empty(); } + operator bool() const { return pillar_base.has_value() && !path.empty(); } }; template @@ -659,6 +659,8 @@ GroundConnection find_pillar_route(Ex policy, { GroundConnection ret; + ret.path.emplace_back(source); + Vec3d jp = source.pos, endp = jp, dir = sourcedir; bool can_add_base = false/*, non_head = false*/; @@ -666,6 +668,7 @@ GroundConnection find_pillar_route(Ex policy, double jp_gnd = 0.; // The lowest Z where a junction center can be double gap_dist = 0.; // The gap distance between the model and the pad double radius = source.r; + double sd = sm.cfg.safety_distance_mm; double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); @@ -692,7 +695,6 @@ GroundConnection find_pillar_route(Ex policy, sm.cfg.head_back_radius_mm); if (diffbr && diffbr->endp.z() > jp_gnd) { - ret.path.emplace_back(source); endp = diffbr->endp; radius = diffbr->end_r; ret.path.emplace_back(endp, radius); @@ -709,7 +711,6 @@ GroundConnection find_pillar_route(Ex policy, auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - sm.cfg.bridge_slope; Vec3d d = spheric_to_dir(polar, azimuth).normalized(); - auto sd = sm.cfg.safety_distance_mm; //radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); double tmax = std::min(sm.cfg.max_bridge_length_mm, t); t = 0.; @@ -759,15 +760,19 @@ GroundConnection find_pillar_route(Ex policy, } Vec3d gp = to_floor(endp); + auto hit = beam_mesh_hit(policy, sm.emesh, + Beam{{endp, radius}, {gp, end_radius}}, sd); - ret.end_radius = end_radius; + if (std::isinf(hit.distance())) { + double base_radius = can_add_base ? + std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; - if (can_add_base) { + Vec3d gp = to_floor(endp); ret.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, sm.cfg.base_radius_mm, end_radius}; + Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; } - return ret; //{true, pillar_id}; + return ret; } inline long build_ground_connection(SupportTreeBuilder &builder, @@ -798,10 +803,9 @@ inline long build_ground_connection(SupportTreeBuilder &builder, // long head_id = std::abs(conn.path.back().id); // ret = builder.add_pillar(head_id, h); // } else - ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); - if (conn.pillar_base) - builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); + builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); return ret; } @@ -809,7 +813,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder, template GroundConnection find_ground_connection( Ex policy, - SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &j, const Vec3d &dir, @@ -817,39 +820,36 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; double r = j.r; - auto sd = sm.cfg.safety_distance_mm; //r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance_mm; double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); - double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd) - .distance(); - double d = 0, tdown = 0; - t = std::min(t, - sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm); + double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); + t = std::min(t, sm.cfg.max_bridge_length_mm); + double d = 0.; + + GroundConnection gnd_route; + + while (!gnd_route && d < t) { + Vec3d endp = hjp + d * dir; + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); + gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - while ( - d < t && - !std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, - Beam{hjp + d * dir, DOWN, r, r2}, sd) - .distance())) { d += r; } GroundConnection ret; - ret.end_radius = end_r; - if (std::isinf(tdown)) { + if (d > 0.) ret.path.emplace_back(j); - Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); - double pill_r = r + bridge_ratio * (end_r - r); - auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - for (auto &j : route.path) - ret.path.emplace_back(j); + for (auto &p : gnd_route.path) + ret.path.emplace_back(p); - ret.pillar_base = route.pillar_base; - ret.end_radius = end_r; - } + // This will ultimately determine if the route is valid or not + // but the path junctions will be provided anyways, so invalid paths + // can be debugged + ret.pillar_base = gnd_route.pillar_base; return ret; } @@ -857,7 +857,6 @@ GroundConnection find_ground_connection( template GroundConnection optimize_ground_connection( Ex policy, - SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &j, double end_radius, @@ -865,8 +864,8 @@ GroundConnection optimize_ground_connection( { double downdst = j.pos.z() - ground_level(sm); - auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius); - if (!res) + auto res = find_ground_connection(policy, sm, j, init_dir, end_radius); + if (res) return res; // Optimize bridge direction: @@ -874,7 +873,7 @@ GroundConnection optimize_ground_connection( // direction out of the cavity. auto [polar, azimuth] = dir_to_spheric(init_dir); - Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); + Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior auto sd = /*j.r **/ sm.cfg.safety_distance_mm /*/ sm.cfg.head_back_radius_mm*/; @@ -891,7 +890,7 @@ GroundConnection optimize_ground_connection( Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); - return find_ground_connection(policy, builder, sm, j, bridgedir, end_radius); + return find_ground_connection(policy, sm, j, bridgedir, end_radius); } template @@ -904,7 +903,7 @@ std::pair connect_to_ground(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto conn = find_ground_connection(policy, builder, sm, j, dir, end_r); + auto conn = find_ground_connection(policy, sm, j, dir, end_r); ret.first = bool(conn); ret.second = build_ground_connection(builder, sm, conn); @@ -921,8 +920,7 @@ std::pair search_ground_route(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto conn = optimize_ground_connection(policy, builder, sm, j, - end_r, init_dir); + auto conn = optimize_ground_connection(policy, sm, j, end_r, init_dir); ret.first = bool(conn); ret.second = build_ground_connection(builder, sm, conn); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index c22cdf606a..e0f3acb701 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp" @@ -1019,6 +1021,8 @@ void GLGizmoSlaSupports::select_point(int i) m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; } else { + if (!m_editing_cache[i].selected) + BOOST_LOG_TRIVIAL(debug) << "Support point selected [" << i << "]: " << m_editing_cache[i].support_point.pos.transpose() << " \tnormal: " << m_editing_cache[i].normal.transpose(); m_editing_cache[i].selected = true; m_selection_empty = false; m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; diff --git a/tests/data/U_overhang.obj b/tests/data/U_overhang.obj new file mode 100644 index 0000000000..70453e58f7 --- /dev/null +++ b/tests/data/U_overhang.obj @@ -0,0 +1,76 @@ +#### +# +# OBJ File Generated by Meshlab +# +#### +# Object U_overhang.obj +# +# Vertices: 16 +# Faces: 28 +# +#### +vn 1.570797 1.570796 1.570796 +v 10.000000 10.000000 11.000000 +vn 4.712389 1.570796 -1.570796 +v 10.000000 1.000000 10.000000 +vn 1.570796 1.570796 -1.570796 +v 10.000000 10.000000 10.000000 +vn 1.570796 -1.570796 1.570796 +v 10.000000 0.000000 11.000000 +vn 4.712389 1.570796 1.570796 +v 10.000000 1.000000 1.000000 +vn 1.570797 1.570796 -1.570796 +v 10.000000 10.000000 0.000000 +vn 1.570796 1.570796 1.570796 +v 10.000000 10.000000 1.000000 +vn 1.570796 -1.570796 -1.570796 +v 10.000000 0.000000 0.000000 +vn -1.570796 1.570796 1.570796 +v 0.000000 10.000000 1.000000 +vn -4.712389 1.570796 1.570796 +v 0.000000 1.000000 1.000000 +vn -1.570796 -1.570796 -1.570796 +v 0.000000 0.000000 0.000000 +vn -1.570797 1.570796 -1.570796 +v 0.000000 10.000000 0.000000 +vn -4.712389 1.570796 -1.570796 +v 0.000000 1.000000 10.000000 +vn -1.570797 1.570796 1.570796 +v 0.000000 10.000000 11.000000 +vn -1.570796 1.570796 -1.570796 +v 0.000000 10.000000 10.000000 +vn -1.570796 -1.570796 1.570796 +v 0.000000 0.000000 11.000000 +# 16 vertices, 0 vertices normals + +f 1//1 2//2 3//3 +f 2//2 4//4 5//5 +f 4//4 2//2 1//1 +f 5//5 6//6 7//7 +f 5//5 8//8 6//6 +f 8//8 5//5 4//4 +f 9//9 5//5 7//7 +f 5//5 9//9 10//10 +f 11//11 6//6 8//8 +f 6//6 11//11 12//12 +f 12//12 10//10 9//9 +f 10//10 11//11 13//13 +f 11//11 10//10 12//12 +f 13//13 14//14 15//15 +f 13//13 16//16 14//14 +f 16//16 13//13 11//11 +f 6//6 9//9 7//7 +f 9//9 6//6 12//12 +f 11//11 4//4 16//16 +f 4//4 11//11 8//8 +f 13//13 3//3 2//2 +f 3//3 13//13 15//15 +f 5//5 13//13 2//2 +f 13//13 5//5 10//10 +f 14//14 4//4 1//1 +f 4//4 14//14 16//16 +f 3//3 14//14 1//1 +f 14//14 3//3 15//15 +# 28 faces, 0 coords texture + +# End of File diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index 24e9552c5b..2a800cc50e 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_test_utils.hpp sla_test_utils.cpp sla_supptgen_tests.cpp sla_raycast_tests.cpp + sla_supptreeutils_tests.cpp sla_archive_readwrite_tests.cpp) # mold linker for successful linking needs also to link TBB library and link it before libslic3r. diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp new file mode 100644 index 0000000000..b433e80eed --- /dev/null +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include "libslic3r/Execution/ExecutionSeq.hpp" +#include "libslic3r/SLA/SupportTreeUtils.hpp" + +TEST_CASE("Avoid disk below junction", "[suptreeutils]") +{ + // In this test there will be a disk mesh with some radius, centered at + // (0, 0, 0) and above the disk, a junction from which the support pillar + // should be routed. The algorithm needs to find an avoidance route. + + using namespace Slic3r; + + constexpr double FromRadius = .5; + constexpr double EndRadius = 1.; + constexpr double CylRadius = 4.; + constexpr double CylHeight = 1.; + + sla::SupportTreeConfig cfg; + + indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight); + + // 2.5 * CyRadius height should be enough to be able to insert a bridge + // with 45 degree tilt above the disk. + sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius}; + + sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; + + sla::GroundConnection conn = + sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + +#ifndef NDEBUG + + sla::SupportTreeBuilder builder; + + if (!conn) + builder.add_junction(j); + + sla::build_ground_connection(builder, sm, conn); + + its_merge(disk, builder.merged_mesh()); + + its_write_stl_ascii("output_disk.stl", "disk", disk); +#endif + + REQUIRE(bool(conn)); + + // The route should include the source and one avoidance junction. + REQUIRE(conn.path.size() == 2); + + // The end radius end the pillar base's upper radius should match + REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); + + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); +} + +TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]") +{ + // In this test there will be a disk mesh with some radius, centered at + // (0, 0, 0) and above the disk, a junction from which the support pillar + // should be routed. The algorithm needs to find an avoidance route. + + using namespace Slic3r; + + constexpr double FromRadius = .5; + constexpr double EndRadius = 1.; + constexpr double CylRadius = 4.; + constexpr double CylHeight = 1.; + constexpr double JElevX = 2.5; + + sla::SupportTreeConfig cfg; + + indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight); + indexed_triangle_set wall = its_make_cube(1., 2 * CylRadius, JElevX * CylRadius); + its_translate(wall, Vec3f{float(FromRadius), -float(CylRadius), 0.f}); + its_merge(disk, wall); + + // 2.5 * CyRadius height should be enough to be able to insert a bridge + // with 45 degree tilt above the disk. + sla::Junction j{Vec3d{0., 0., JElevX * CylRadius}, FromRadius}; + + sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; + + sla::GroundConnection conn = + sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + +#ifndef NDEBUG + + sla::SupportTreeBuilder builder; + + if (!conn) + builder.add_junction(j); + + sla::build_ground_connection(builder, sm, conn); + + its_merge(disk, builder.merged_mesh()); + + its_write_stl_ascii("output_disk_wall.stl", "disk_wall", disk); +#endif + + REQUIRE(bool(conn)); + + // The route should include the source and one avoidance junction. + REQUIRE(conn.path.size() == 2); + + // The end radius end the pillar base's upper radius should match + REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); + + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); +} From d3a2f11e2929c7726fb8cf0745da6febfd528060 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 16:53:56 +0100 Subject: [PATCH 29/86] Use old pillar creation functions for default support tree --- src/libslic3r/SLA/DefaultSupportTree.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 06477c40cc..63280b9df7 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -344,21 +344,15 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, const Vec3d &sourcedir, long head_id) { - long pillar_id = SupportTreeNode::ID_UNSET; - - auto conn = sla::find_pillar_route(suptree_ex_policy, m_sm, hjp, sourcedir, hjp.r); - if (conn) - pillar_id = build_ground_connection(m_builder, m_sm, conn); - -// auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, -// m_builder, m_sm, hjp, -// sourcedir, hjp.r, head_id); + auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, + m_builder, m_sm, hjp, + sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, unsigned(pillar_id)); - return bool(conn); + return ret; } void DefaultSupportTree::add_pinheads() From 5f63b4496d7c3da51005fbb13e647c5d0e5e4c7f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 16:54:53 +0100 Subject: [PATCH 30/86] WIP on pillar grouping --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/BranchingTree/PointCloud.cpp | 5 ++++- src/libslic3r/SLA/BranchingTreeSLA.cpp | 10 +++++----- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 3f7fd7b803..d30499c110 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,7 +21,7 @@ class Properties public: - constexpr bool group_pillars() const noexcept { return false; } + constexpr bool group_pillars() const noexcept { return true; } // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 5f07d91c72..319f334ffa 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -197,7 +197,10 @@ PointCloud::PointCloud(std::vector meshpts, for (size_t i = 0; i < m_leafs.size(); ++i) { Node &n = m_leafs[i]; - n.id = int(LEAFS_BEGIN + i); + n.id = int(LEAFS_BEGIN + i); + n.left = Node::ID_NONE; + n.right = Node::ID_NONE; + m_ktree.insert({n.pos, n.id}); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index fc5dc29826..2edc0fe7b9 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -382,20 +382,20 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); + std::cout << "Original pillar count: " << vbuilder.pillars().size() << std::endl; + if constexpr (props.group_pillars()) { std::vector bedleafs; - for (auto n : vbuilder.pillars()) { - n.left = branchingtree::Node::ID_NONE; - n.right = branchingtree::Node::ID_NONE; - bedleafs.emplace_back(n); - } + std::copy(vbuilder.pillars().begin(), vbuilder.pillars().end(), std::back_inserter(bedleafs)); branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; branchingtree::build_tree(gndnodes, gndbuilder); + std::cout << "Grouped pillar count: " << gndbuilder.pillars().size() << std::endl; build_pillars(builder, gndbuilder, sm); + } else { build_pillars(builder, vbuilder, sm); } diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 070ffbf0d4..243152189b 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -629,7 +629,7 @@ std::optional calculate_pinhead_placement(Ex policy, }; if (optimize_pinhead_placement(policy, sm, head)) { - head.id = suppt_idx; + head.id = long(suppt_idx); return head; } From 963e8e6585ae79c4a927ef8f41d5dca5affa6cad Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 14 Nov 2022 12:37:34 +0100 Subject: [PATCH 31/86] Revert util functions of DefaultSupportTree to original To not break DefautlSupportTree --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/SLA/DefaultSupportTree.cpp | 9 +- src/libslic3r/SLA/DefaultSupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 190 +--------------- src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp | 220 +++++++++++++++++++ tests/sla_print/sla_supptreeutils_tests.cpp | 10 +- 7 files changed, 251 insertions(+), 183 deletions(-) create mode 100644 src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3740d4e015..232285cee9 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -323,6 +323,7 @@ set(SLIC3R_SOURCES SLA/SupportTreeMesher.hpp SLA/SupportTreeMesher.cpp SLA/SupportTreeUtils.hpp + SLA/SupportTreeUtilsLegacy.hpp SLA/SupportTreeBuilder.cpp SLA/SupportTree.hpp SLA/SupportTree.cpp diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 63280b9df7..53475542ac 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -345,8 +345,13 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, long head_id) { auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, - m_builder, m_sm, hjp, - sourcedir, hjp.r, head_id); + m_builder, + m_sm, + hjp.pos, + sourcedir, + hjp.r, + hjp.r, + head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index cea4b65e17..ee58e9dede 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -1,7 +1,7 @@ #ifndef LEGACYSUPPORTTREE_HPP #define LEGACYSUPPORTTREE_HPP -#include "SupportTreeUtils.hpp" +#include "SupportTreeUtilsLegacy.hpp" #include #include diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 051d569266..b7aaf8aee8 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -50,7 +50,7 @@ struct SupportTreeConfig // when bridges and pillars are merged. The resulting pillar should be a bit // thicker than the ones merging into it. How much thicker? I don't know // but it will be derived from this value. - double pillar_widening_factor = .05; + double pillar_widening_factor = .5; // Radius in mm of the pillar base. double base_radius_mm = 2.0; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 243152189b..3ee72436ae 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -351,136 +351,6 @@ std::optional search_widening_path(Ex policy, return {}; } -// This is a proxy function for pillar creation which will mind the gap -// between the pad and the model bottom in zero elevation mode. -// 'pinhead_junctionpt' is the starting junction point which needs to be -// routed down. sourcedir is the allowed direction of an optional bridge -// between the jp junction and the final pillar. -template -std::pair create_ground_pillar( - Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &pinhead_junctionpt, - const Vec3d &sourcedir, - double end_radius, - long head_id = SupportTreeNode::ID_UNSET) -{ - Vec3d jp = pinhead_junctionpt.pos, endp = jp, dir = sourcedir; - long pillar_id = SupportTreeNode::ID_UNSET; - bool can_add_base = false, non_head = false; - - double gndlvl = 0.; // The Z level where pedestals should be - double jp_gnd = 0.; // The lowest Z where a junction center can be - double gap_dist = 0.; // The gap distance between the model and the pad - double radius = pinhead_junctionpt.r; - - double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); - - auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; - - auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] - (bool base_en = true) - { - can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; - double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.; - gndlvl = ground_level(sm); - if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; - jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); - gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; - }; - - eval_limits(); - - // We are dealing with a mini pillar that's potentially too long - if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) - { - std::optional diffbr = - search_widening_path(policy, sm, jp, dir, radius, - sm.cfg.head_back_radius_mm); - - if (diffbr && diffbr->endp.z() > jp_gnd) { - auto &br = builder.add_diffbridge(*diffbr); - if (head_id >= 0) builder.head(head_id).bridge_id = br.id; - endp = diffbr->endp; - radius = diffbr->end_r; - builder.add_junction(endp, radius); - non_head = true; - dir = diffbr->get_dir(); - eval_limits(); - } else return {false, pillar_id}; - } - - if (sm.cfg.object_elevation_mm < EPSILON) - { - // get a suitable direction for the corrector bridge. It is the - // original sourcedir's azimuth but the polar angle is saturated to the - // configured bridge slope. - auto [polar, azimuth] = dir_to_spheric(dir); - polar = PI - sm.cfg.bridge_slope; - Vec3d d = spheric_to_dir(polar, azimuth).normalized(); - auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; - double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); - double tmax = std::min(sm.cfg.max_bridge_length_mm, t); - t = 0.; - - double zd = endp.z() - jp_gnd; - double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); - tmax = std::min(tmax, tmax2); - - Vec3d nexp = endp; - double dlast = 0.; - while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || - !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) && - t < tmax) - { - t += radius; - nexp = endp + t * d; - } - - if (dlast < gap_dist && can_add_base) { - nexp = endp; - t = 0.; - can_add_base = false; - eval_limits(can_add_base); - - zd = endp.z() - jp_gnd; - tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); - tmax = std::min(tmax, tmax2); - - while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || - !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) { - t += radius; - nexp = endp + t * d; - } - } - - // Could not find a path to avoid the pad gap - if (dlast < gap_dist) return {false, pillar_id}; - - if (t > 0.) { // Need to make additional bridge - const Bridge& br = builder.add_bridge(endp, nexp, radius); - if (head_id >= 0) builder.head(head_id).bridge_id = br.id; - - builder.add_junction(nexp, radius); - endp = nexp; - non_head = true; - } - } - - Vec3d gp = to_floor(endp); - double h = endp.z() - gp.z(); - - pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) : - builder.add_pillar(gp, h, radius, end_radius); - - if (can_add_base) - builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, - sm.cfg.base_radius_mm); - - return {true, pillar_id}; -} - inline double distance(const SupportPoint &a, const SupportPoint &b) { return (a.pos - b.pos).norm(); @@ -533,13 +403,13 @@ bool optimize_pinhead_placement(Ex policy, double back_r = head.r_back_mm; - // skip if the tilt is not sane + // skip if the tilt is not sane if (polar < PI - m.cfg.normal_cutoff_angle) return false; - // We saturate the polar angle to 3pi/4 + // We saturate the polar angle to 3pi/4 polar = std::max(polar, PI - m.cfg.bridge_slope); - // save the head (pinpoint) position + // save the head (pinpoint) position Vec3d hp = head.pos; double lmin = m.cfg.head_width_mm, lmax = lmin; @@ -548,19 +418,18 @@ bool optimize_pinhead_placement(Ex policy, lmin = 0., lmax = m.cfg.head_penetration_mm; } - // The distance needed for a pinhead to not collide with model. + // The distance needed for a pinhead to not collide with model. double w = lmin + 2 * back_r + 2 * m.cfg.head_front_radius_mm - m.cfg.head_penetration_mm; double pin_r = head.r_pin_mm; - // Reassemble the now corrected normal + // Reassemble the now corrected normal auto nn = spheric_to_dir(polar, azimuth).normalized(); - double sd = back_r * m.cfg.safety_distance_mm / - m.cfg.head_back_radius_mm; + double sd = m.cfg.safety_distance_mm; - // check available distance + // check available distance Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd); if (t.distance() < w) { @@ -568,7 +437,7 @@ bool optimize_pinhead_placement(Ex policy, // viable normal that doesn't collide with the model // geometry and its very close to the default. - Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); + Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); solver.seed(0); // we want deterministic behavior auto oresult = solver.to_max().optimize( @@ -602,10 +471,10 @@ bool optimize_pinhead_placement(Ex policy, head.r_back_mm = back_r; ret = true; - } else if (back_r > m.cfg.head_fallback_radius_mm) { + } /*else if (back_r > m.cfg.head_fallback_radius_mm) { head.r_back_mm = m.cfg.head_fallback_radius_mm; ret = optimize_pinhead_placement(policy, m, head); - } + }*/ return ret; } @@ -848,7 +717,7 @@ GroundConnection find_ground_connection( // This will ultimately determine if the route is valid or not // but the path junctions will be provided anyways, so invalid paths - // can be debugged + // can be inspected ret.pillar_base = gnd_route.pillar_base; return ret; @@ -876,7 +745,7 @@ GroundConnection optimize_ground_connection( Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - auto sd = /*j.r **/ sm.cfg.safety_distance_mm /*/ sm.cfg.head_back_radius_mm*/; + auto sd = sm.cfg.safety_distance_mm; auto oresult = solver.to_max().optimize( [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { auto &[plr, azm] = input; @@ -893,41 +762,6 @@ GroundConnection optimize_ground_connection( return find_ground_connection(policy, sm, j, bridgedir, end_radius); } -template -std::pair connect_to_ground(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) -{ - std::pair ret = {false, SupportTreeNode::ID_UNSET}; - - auto conn = find_ground_connection(policy, sm, j, dir, end_r); - ret.first = bool(conn); - ret.second = build_ground_connection(builder, sm, conn); - - return ret; -} - -template -std::pair search_ground_route(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - double end_r, - const Vec3d &init_dir = DOWN) -{ - std::pair ret = {false, SupportTreeNode::ID_UNSET}; - - auto conn = optimize_ground_connection(policy, sm, j, end_r, init_dir); - - ret.first = bool(conn); - ret.second = build_ground_connection(builder, sm, conn); - - return ret; -} - template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, diff --git a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp new file mode 100644 index 0000000000..43a8ddb59d --- /dev/null +++ b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp @@ -0,0 +1,220 @@ +#ifndef SUPPORTTREEUTILSLEGACY_HPP +#define SUPPORTTREEUTILSLEGACY_HPP + +#include "SupportTreeUtils.hpp" + +// Old functions are gathered here that are used in DefaultSupportTree +// to maintain functionality that was well tested. + +namespace Slic3r { namespace sla { + +// This is a proxy function for pillar creation which will mind the gap +// between the pad and the model bottom in zero elevation mode. +// 'pinhead_junctionpt' is the starting junction point which needs to be +// routed down. sourcedir is the allowed direction of an optional bridge +// between the jp junction and the final pillar. +template +std::pair create_ground_pillar( + Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Vec3d &pinhead_junctionpt, + const Vec3d &sourcedir, + double radius, + double end_radius, + long head_id = SupportTreeNode::ID_UNSET) +{ + Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir; + long pillar_id = SupportTreeNode::ID_UNSET; + bool can_add_base = false, non_head = false; + + double gndlvl = 0.; // The Z level where pedestals should be + double jp_gnd = 0.; // The lowest Z where a junction center can be + double gap_dist = 0.; // The gap distance between the model and the pad + + double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); + + auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + + auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] + (bool base_en = true) + { + can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; + double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.; + gndlvl = ground_level(sm); + if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; + jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); + gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; + }; + + eval_limits(); + + // We are dealing with a mini pillar that's potentially too long + if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) + { + std::optional diffbr = + search_widening_path(policy, sm, jp, dir, radius, + sm.cfg.head_back_radius_mm); + + if (diffbr && diffbr->endp.z() > jp_gnd) { + auto &br = builder.add_diffbridge(*diffbr); + if (head_id >= 0) builder.head(head_id).bridge_id = br.id; + endp = diffbr->endp; + radius = diffbr->end_r; + builder.add_junction(endp, radius); + non_head = true; + dir = diffbr->get_dir(); + eval_limits(); + } else return {false, pillar_id}; + } + + if (sm.cfg.object_elevation_mm < EPSILON) + { + // get a suitable direction for the corrector bridge. It is the + // original sourcedir's azimuth but the polar angle is saturated to the + // configured bridge slope. + auto [polar, azimuth] = dir_to_spheric(dir); + polar = PI - sm.cfg.bridge_slope; + Vec3d d = spheric_to_dir(polar, azimuth).normalized(); + auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); + double tmax = std::min(sm.cfg.max_bridge_length_mm, t); + t = 0.; + + double zd = endp.z() - jp_gnd; + double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + Vec3d nexp = endp; + double dlast = 0.; + while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) && + t < tmax) + { + t += radius; + nexp = endp + t * d; + } + + if (dlast < gap_dist && can_add_base) { + nexp = endp; + t = 0.; + can_add_base = false; + eval_limits(can_add_base); + + zd = endp.z() - jp_gnd; + tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) { + t += radius; + nexp = endp + t * d; + } + } + + // Could not find a path to avoid the pad gap + if (dlast < gap_dist) return {false, pillar_id}; + + if (t > 0.) { // Need to make additional bridge + const Bridge& br = builder.add_bridge(endp, nexp, radius); + if (head_id >= 0) builder.head(head_id).bridge_id = br.id; + + builder.add_junction(nexp, radius); + endp = nexp; + non_head = true; + } + } + + Vec3d gp = to_floor(endp); + double h = endp.z() - gp.z(); + + pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) : + builder.add_pillar(gp, h, radius, end_radius); + + if (can_add_base) + builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, + sm.cfg.base_radius_mm); + + return {true, pillar_id}; +} + +template +std::pair connect_to_ground(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + const Vec3d &dir, + double end_r) +{ + auto hjp = j.pos; + double r = j.r; + auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); + + double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); + double d = 0, tdown = 0; + t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm); + + while (d < t && + !std::isinf(tdown = beam_mesh_hit(policy, sm.emesh, + Beam{hjp + d * dir, DOWN, r, r2}, sd) + .distance())) { + d += r; + } + + if(!std::isinf(tdown)) + return {false, SupportTreeNode::ID_UNSET}; + + Vec3d endp = hjp + d * dir; + auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r); + + if (ret.second >= 0) { + builder.add_bridge(hjp, endp, r); + builder.add_junction(endp, r); + } + + return ret; +} + +template +std::pair search_ground_route(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) +{ + double downdst = j.pos.z() - ground_level(sm); + + auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius); + if (res.first) + return res; + + // Optimize bridge direction: + // Straight path failed so we will try to search for a suitable + // direction out of the cavity. + auto [polar, azimuth] = dir_to_spheric(init_dir); + + Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); + solver.seed(0); // we want deterministic behavior + + auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto oresult = solver.to_max().optimize( + [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { + auto &[plr, azm] = input; + Vec3d n = spheric_to_dir(plr, azm).normalized(); + Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}}; + return beam_mesh_hit(policy, sm.emesh, beam, sd).distance(); + }, + initvals({polar, azimuth}), // let's start with what we have + bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} }) + ); + + Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); + + return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius); +} + +}} // namespace Slic3r::sla + +#endif // SUPPORTTREEUTILSLEGACY_HPP diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index b433e80eed..aca193a1b3 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -49,7 +49,11 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") // The route should include the source and one avoidance junction. REQUIRE(conn.path.size() == 2); - // The end radius end the pillar base's upper radius should match + // Check if the radius increases with each node + REQUIRE(conn.path.front().r < conn.path.back().r); + REQUIRE(conn.path.back().r < conn.pillar_base->r_top); + + // The end radius and the pillar base's upper radius should match REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); // Check if the avoidance junction is indeed outside of the disk barrier's @@ -108,6 +112,10 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" // The route should include the source and one avoidance junction. REQUIRE(conn.path.size() == 2); + // Check if the radius increases with each node + REQUIRE(conn.path.front().r < conn.path.back().r); + REQUIRE(conn.path.back().r < conn.pillar_base->r_top); + // The end radius end the pillar base's upper radius should match REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); From a20659fc2d3f8b497cd4b0659fe378184452801d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Nov 2022 15:08:28 +0100 Subject: [PATCH 32/86] New ground route search implemented Working gap avoidance for zero elevation --- src/libslic3r/AABBMesh.hpp | 6 +- src/libslic3r/SLA/BranchingTreeSLA.cpp | 34 +- src/libslic3r/SLA/SupportTree.hpp | 20 +- src/libslic3r/SLA/SupportTreeBuilder.hpp | 8 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 376 ++++++++++++++------ tests/sla_print/sla_supptreeutils_tests.cpp | 64 +++- tests/sla_print/sla_test_utils.cpp | 11 +- 7 files changed, 360 insertions(+), 159 deletions(-) diff --git a/src/libslic3r/AABBMesh.hpp b/src/libslic3r/AABBMesh.hpp index 6a08c4303b..312d8926db 100644 --- a/src/libslic3r/AABBMesh.hpp +++ b/src/libslic3r/AABBMesh.hpp @@ -72,9 +72,9 @@ public: double m_t = infty(); int m_face_id = -1; const AABBMesh *m_mesh = nullptr; - Vec3d m_dir; - Vec3d m_source; - Vec3d m_normal; + Vec3d m_dir = Vec3d::Zero(); + Vec3d m_source = Vec3d::Zero(); + Vec3d m_normal = Vec3d::Zero(); friend class AABBMesh; // A valid object of this class can only be obtained from diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 2edc0fe7b9..16236f7cdf 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -39,7 +39,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { { double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight; - return std::min(m_sm.cfg.base_radius_mm, double(j.Rmin) + w); + return double(j.Rmin) + w; } std::vector m_unroutable_pinheads; @@ -247,32 +247,18 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { -// std::optional result = search_for_existing_pillar(from); - sla::Junction j{from.pos.cast(), get_radius(from)}; -// if (!result) { - auto conn = optimize_ground_connection( - ex_tbb, - m_sm, - j, - get_radius(to)); + Vec3d init_dir = (to.pos - from.pos).cast().normalized(); - if (conn) { -// Junction connlast = conn.path.back(); -// branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; -// n.left = from.id; - m_pillars.emplace_back(from); -// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); - m_gnd_connections[m_pillars.size() - 1] = conn; + auto conn = deepsearch_ground_connection(ex_tbb, m_sm, j, + get_radius(to), init_dir); - ret = true; - } -// } else { -// const auto &resnode = m_pillars[result->second]; -// m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); -// m_pillars[result->second].right = from.id; -// ret = true; -// } + if (conn) { + m_pillars.emplace_back(from); + m_gnd_connections[m_pillars.size() - 1] = conn; + + ret = true; + } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index b7aaf8aee8..e0d7db97de 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -84,6 +84,12 @@ struct SupportTreeConfig 2 * head_back_radius_mm - head_penetration_mm; } + double safety_distance() const { return safety_distance_mm; } + double safety_distance(double r) const + { + return std::min(safety_distance_mm, r * safety_distance_mm / head_back_radius_mm); + } + // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) // ///////////////////////////////////////////////////////////////////////// @@ -91,7 +97,9 @@ struct SupportTreeConfig // The max Z angle for a normal at which it will get completely ignored. static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0; - // The shortest distance of any support structure from the model surface + // The safety gap between a support structure and model body. For support + // struts smaller than head_back_radius, the safety distance is scaled + // down accordingly. see method safety_distance() static const double constexpr safety_distance_mm = 0.5; static const double constexpr max_solo_pillar_height_mm = 15.0; @@ -117,11 +125,11 @@ struct SupportableMesh : emesh{trmsh}, pts{sp}, cfg{c} {} - explicit SupportableMesh(const AABBMesh &em, - const SupportPoints &sp, - const SupportTreeConfig &c) - : emesh{em}, pts{sp}, cfg{c} - {} +// explicit SupportableMesh(const AABBMesh &em, +// const SupportPoints &sp, +// const SupportTreeConfig &c) +// : emesh{em}, pts{sp}, cfg{c} +// {} }; inline double ground_level(const SupportableMesh &sm) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index e4567e405a..93fbead997 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -79,7 +79,7 @@ struct Junction: public SupportTreeNode { struct Head: public SupportTreeNode { Vec3d dir = DOWN; Vec3d pos = {0, 0, 0}; - + double r_back_mm = 1; double r_pin_mm = 0.5; double width_mm = 2; @@ -87,12 +87,12 @@ struct Head: public SupportTreeNode { // If there is a pillar connecting to this head, then the id will be set. long pillar_id = ID_UNSET; - + long bridge_id = ID_UNSET; - + inline void invalidate() { id = ID_UNSET; } inline bool is_valid() const { return id >= 0; } - + Head(double r_big_mm, double r_small_mm, double length_mm, diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 3ee72436ae..08e410431c 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -316,8 +316,7 @@ std::optional search_widening_path(Ex policy, auto d = spheric_to_dir(plr, azm).normalized(); - auto sd = new_radius * sm.cfg.safety_distance_mm / - sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance(new_radius); double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius, new_radius, t, sd) @@ -427,7 +426,7 @@ bool optimize_pinhead_placement(Ex policy, // Reassemble the now corrected normal auto nn = spheric_to_dir(polar, azimuth).normalized(); - double sd = m.cfg.safety_distance_mm; + double sd = m.cfg.safety_distance(back_r); // check available distance Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd); @@ -471,10 +470,10 @@ bool optimize_pinhead_placement(Ex policy, head.r_back_mm = back_r; ret = true; - } /*else if (back_r > m.cfg.head_fallback_radius_mm) { + } else if (back_r > m.cfg.head_fallback_radius_mm) { head.r_back_mm = m.cfg.head_fallback_radius_mm; ret = optimize_pinhead_placement(policy, m, head); - }*/ + } return ret; } @@ -527,116 +526,19 @@ GroundConnection find_pillar_route(Ex policy, double end_radius) { GroundConnection ret; - ret.path.emplace_back(source); - Vec3d jp = source.pos, endp = jp, dir = sourcedir; - bool can_add_base = false/*, non_head = false*/; + double sd = sm.cfg.safety_distance(source.r); + auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)}; - double gndlvl = 0.; // The Z level where pedestals should be - double jp_gnd = 0.; // The lowest Z where a junction center can be - double gap_dist = 0.; // The gap distance between the model and the pad - double radius = source.r; - double sd = sm.cfg.safety_distance_mm; - - double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); - - auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; - - auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] - (bool base_en = true) - { - can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; - double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.; - gndlvl = ground_level(sm); - if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; - jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); - gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; - }; - - eval_limits(); - - // We are dealing with a mini pillar that's potentially too long - if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) - { - std::optional diffbr = - search_widening_path(policy, sm, jp, dir, radius, - sm.cfg.head_back_radius_mm); - - if (diffbr && diffbr->endp.z() > jp_gnd) { - endp = diffbr->endp; - radius = diffbr->end_r; - ret.path.emplace_back(endp, radius); - dir = diffbr->get_dir(); - eval_limits(); - } else return ret; - } - - if (sm.cfg.object_elevation_mm < EPSILON) - { - // get a suitable direction for the corrector bridge. It is the - // original sourcedir's azimuth but the polar angle is saturated to the - // configured bridge slope. - auto [polar, azimuth] = dir_to_spheric(dir); - polar = PI - sm.cfg.bridge_slope; - Vec3d d = spheric_to_dir(polar, azimuth).normalized(); - double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); - double tmax = std::min(sm.cfg.max_bridge_length_mm, t); - t = 0.; - - double zd = endp.z() - jp_gnd; - double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); - tmax = std::min(tmax, tmax2); - - Vec3d nexp = endp; - double dlast = 0.; - while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || - !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) && - t < tmax) - { - t += radius; - nexp = endp + t * d; - } - - if (dlast < gap_dist && can_add_base) { - nexp = endp; - t = 0.; - can_add_base = false; - eval_limits(can_add_base); - - zd = endp.z() - jp_gnd; - tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); - tmax = std::min(tmax, tmax2); - - while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || - !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) { - t += radius; - nexp = endp + t * d; - } - } - - // Could not find a path to avoid the pad gap - if (dlast < gap_dist) { - ret.path.clear(); - return ret; - //return {false, pillar_id}; - } - - if (t > 0.) { // Need to make additional bridge - ret.path.emplace_back(nexp, radius); - endp = nexp; - } - } - - Vec3d gp = to_floor(endp); - auto hit = beam_mesh_hit(policy, sm.emesh, - Beam{{endp, radius}, {gp, end_radius}}, sd); + auto hit = beam_mesh_hit(policy, + sm.emesh, + Beam{{source.pos, source.r}, {gp, end_radius}}, + sd); if (std::isinf(hit.distance())) { - double base_radius = can_add_base ? - std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; + double base_radius = std::max(sm.cfg.base_radius_mm, end_radius); - Vec3d gp = to_floor(endp); ret.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; } @@ -644,6 +546,139 @@ GroundConnection find_pillar_route(Ex policy, return ret; } +//template +//GroundConnection find_pillar_route(Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// const Vec3d &sourcedir, +// double end_radius) +//{ +// GroundConnection ret; + +// ret.path.emplace_back(source); + +// Vec3d jp = source.pos, endp = jp, dir = sourcedir; +// bool can_add_base = false/*, non_head = false*/; + +// double gndlvl = 0.; // The Z level where pedestals should be +// double jp_gnd = 0.; // The lowest Z where a junction center can be +// double gap_dist = 0.; // The gap distance between the model and the pad +// double radius = source.r; +// double sd = sm.cfg.safety_distance(radius); + +// double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); + +// auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + +// auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd, end_radius] +// (bool base_en = true) +// { +// can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; +// double base_r = can_add_base ? std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; +// gndlvl = ground_level(sm); +// if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; +// jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); +// gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; +// }; + +// eval_limits(); + +// // We are dealing with a mini pillar that's potentially too long +// if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) +// { +// std::optional diffbr = +// search_widening_path(policy, sm, jp, dir, radius, +// sm.cfg.head_back_radius_mm); + +// if (diffbr && diffbr->endp.z() > jp_gnd) { +// endp = diffbr->endp; +// radius = diffbr->end_r; +// ret.path.emplace_back(endp, radius); +// dir = diffbr->get_dir(); +// eval_limits(); +// } else return ret; +// } + +// if (sm.cfg.object_elevation_mm < EPSILON) +// { +// // get a suitable direction for the corrector bridge. It is the +// // original sourcedir's azimuth but the polar angle is saturated to the +// // configured bridge slope. +// auto [polar, azimuth] = dir_to_spheric(dir); +// polar = PI - sm.cfg.bridge_slope; +// Vec3d d = spheric_to_dir(polar, azimuth).normalized(); +// double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); +// double tmax = std::min(sm.cfg.max_bridge_length_mm, t); +// t = 0.; + +// double zd = endp.z() - jp_gnd; +// double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); +// tmax = std::min(tmax, tmax2); + +// Vec3d nexp = endp; +// double dlast = 0.; +// double rnext = radius; +// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || +// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && +// t < tmax) +// { +// t += radius; +// nexp = endp + t * d; +// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); +// rnext = rnext + bridge_ratio * (end_radius - rnext); +// } + +// // If could not find avoidance bridge for the pad gap, try again +// // without the pillar base +// if (dlast < gap_dist && can_add_base) { +// nexp = endp; +// t = 0.; +// rnext = radius; +// can_add_base = false; +// eval_limits(can_add_base); + +// zd = endp.z() - jp_gnd; +// tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); +// tmax = std::min(tmax, tmax2); + +// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || +// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && t < tmax) { +// t += radius; +// nexp = endp + t * d; + +// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); +// rnext = rnext + bridge_ratio * (end_radius - rnext); +// } +// } + +// // Could not find a path to avoid the pad gap +// if (dlast < gap_dist) { +// ret.path.clear(); +// return ret; +// } + +// if (t > 0.) { // Need to make additional bridge +// ret.path.emplace_back(nexp, rnext); +// endp = nexp; +// } +// } + +// Vec3d gp = to_floor(endp); +// auto hit = beam_mesh_hit(policy, sm.emesh, +// Beam{{endp, radius}, {gp, end_radius}}, sd); + +// if (std::isinf(hit.distance())) { +// double base_radius = can_add_base ? +// std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; + +// Vec3d gp = to_floor(endp); +// ret.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; +// } + +// return ret; +//} + inline long build_ground_connection(SupportTreeBuilder &builder, const SupportableMesh &sm, const GroundConnection &conn) @@ -689,7 +724,7 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; double r = j.r; - auto sd = sm.cfg.safety_distance_mm; + auto sd = sm.cfg.safety_distance(r); double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); @@ -700,7 +735,7 @@ GroundConnection find_ground_connection( while (!gnd_route && d < t) { Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double bridge_ratio = d / (d + (endp.z() - ground_level(sm))); double pill_r = r + bridge_ratio * (end_r - r); gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); @@ -745,7 +780,7 @@ GroundConnection optimize_ground_connection( Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - auto sd = sm.cfg.safety_distance_mm; + auto sd = sm.cfg.safety_distance(j.r); auto oresult = solver.to_max().optimize( [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { auto &[plr, azm] = input; @@ -762,6 +797,120 @@ GroundConnection optimize_ground_connection( return find_ground_connection(policy, sm, j, bridgedir, end_radius); } +template +GroundConnection deepsearch_ground_connection( + Ex policy, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) +{ + // Score is the total lenght of the route. Feasible routes will have + // infinite length (rays not colliding with model), thus the stop score + // should be a reasonably big number. + constexpr double StopScore = 1e6; + + const auto sd = sm.cfg.safety_distance(j.r); + const auto gndlvl = ground_level(sm); + const double widening = end_radius - j.r; + const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + + auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + + Optimizer solver(criteria); + solver.seed(0); // enforce deterministic behavior + + auto optfn = [&](const opt::Input<3> &input) { + double ret = NaNd; + + // solver suggests polar, azimuth and bridge length values: + auto &[plr, azm, bridge_len] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = j.pos + bridge_len * n; + + double full_len = bridge_len + bridge_end.z() - gndlvl; + double bridge_r = j.r + widening * bridge_len / full_len; + double brhit_dist = 0.; + + if (bridge_len > EPSILON) { + // beam_mesh_hit with a zero lenght bridge is invalid + + Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; + auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); + brhit_dist = brhit.distance(); + } + + if (brhit_dist < bridge_len) { + ret = brhit_dist; + } else { + // check if pillar can be placed below + auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + + Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; + auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + + if (std::isinf(gndhit.distance())) { + // Ground route is free with this bridge + + if (sm.cfg.object_elevation_mm < EPSILON) { + // Dealing with zero elevation mode, to not route pillars + // into the gap between the optional pad and the model + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + if (gap < zelev_gap) + ret = full_len - zelev_gap + gap; + else // success + ret = StopScore; + } else { + // No zero elevation, return success + ret = StopScore; + } + } else { + // Ground route is not free + ret = bridge_len + gndhit.distance(); + } + } + + return ret; + }; + + auto [plr_init, azm_init] = dir_to_spheric(init_dir); + + // Saturate the polar angle to max tilt defined in config + plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); + + auto oresult = solver.to_max().optimize( + optfn, + initvals({plr_init, azm_init, 0.}), // start with a zero bridge + bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length + ); + + GroundConnection conn; + + if (oresult.score >= StopScore) { + // search was successful, extract and apply the result + auto &[plr, azm, bridge_len] = oresult.optimum; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = j.pos + bridge_len * n; + + double full_len = bridge_len + bridge_end.z() - gndlvl; + double bridge_r = j.r + widening * bridge_len / full_len; + Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + + conn.path.emplace_back(j); + conn.path.emplace_back(Junction{bridge_end, bridge_r}); + + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + } + + return conn; +} + template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, @@ -779,8 +928,7 @@ bool optimize_anchor_placement(Ex policy, double lmax = std::min(sm.cfg.head_width_mm, distance(from.pos, anchor.pos) - 2 * from.r); - double sd = anchor.r_back_mm * sm.cfg.safety_distance_mm / - sm.cfg.head_back_radius_mm; + double sd = sm.cfg.safety_distance(anchor.r_back_mm); Optimizer solver(get_criteria(sm.cfg) .stop_score(anchor.fullwidth()) diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index aca193a1b3..95ac766333 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -28,7 +28,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; sla::GroundConnection conn = - sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); #ifndef NDEBUG @@ -63,6 +63,66 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") REQUIRE(pR + FromRadius > CylRadius); } +TEST_CASE("Avoid disk below junction - Zero elevation", "[suptreeutils]") +{ + // In this test there will be a disk mesh with some radius, centered at + // (0, 0, 0) and above the disk, a junction from which the support pillar + // should be routed. The algorithm needs to find an avoidance route. + + using namespace Slic3r; + + constexpr double FromRadius = .5; + constexpr double EndRadius = 1.; + constexpr double CylRadius = 4.; + constexpr double CylHeight = 1.; + + sla::SupportTreeConfig cfg; + cfg.object_elevation_mm = 0.; + + indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight); + + // 2.5 * CyRadius height should be enough to be able to insert a bridge + // with 45 degree tilt above the disk. + sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius}; + + sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; + + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + +#ifndef NDEBUG + + sla::SupportTreeBuilder builder; + + if (!conn) + builder.add_junction(j); + + sla::build_ground_connection(builder, sm, conn); + + its_merge(disk, builder.merged_mesh()); + + its_write_stl_ascii("output_disk_ze.stl", "disk", disk); +#endif + + REQUIRE(bool(conn)); + + // The route should include the source and one avoidance junction. + REQUIRE(conn.path.size() == 2); + + // Check if the radius increases with each node + REQUIRE(conn.path.front().r < conn.path.back().r); + REQUIRE(conn.path.back().r < conn.pillar_base->r_top); + + // The end radius and the pillar base's upper radius should match + REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); + + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); +} + TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]") { // In this test there will be a disk mesh with some radius, centered at @@ -91,7 +151,7 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; sla::GroundConnection conn = - sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); #ifndef NDEBUG diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 69feea31f4..e097a3bb72 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -118,7 +118,7 @@ void test_supports(const std::string &obj_filename, // Create the special index-triangle mesh with spatial indexing which // is the input of the support point and support mesh generators - AABBMesh emesh{mesh}; + sla::SupportableMesh sm{mesh.its, {}, supportcfg}; #ifdef SLIC3R_HOLE_RAYCASTER if (hollowingcfg.enabled) @@ -130,23 +130,23 @@ void test_supports(const std::string &obj_filename, // Create the support point generator sla::SupportPointGenerator::Config autogencfg; autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm); - sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}}; + sla::SupportPointGenerator point_gen{sm.emesh, autogencfg, [] {}, [](int) {}}; point_gen.seed(0); // Make the test repeatable point_gen.execute(out.model_slices, out.slicegrid); // Get the calculated support points. - std::vector support_points = point_gen.output(); + sm.pts = point_gen.output(); int validityflags = ASSUME_NO_REPAIR; // If there is no elevation, support points shall be removed from the // bottom of the object. if (std::abs(supportcfg.object_elevation_mm) < EPSILON) { - sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm); + sla::remove_bottom_points(sm.pts, zmin + supportcfg.base_height_mm); } else { // Should be support points at least on the bottom of the model - REQUIRE_FALSE(support_points.empty()); + REQUIRE_FALSE(sm.pts.empty()); // Also the support mesh should not be empty. validityflags |= ASSUME_NO_EMPTY; @@ -154,7 +154,6 @@ void test_supports(const std::string &obj_filename, // Generate the actual support tree sla::SupportTreeBuilder treebuilder; - sla::SupportableMesh sm{emesh, support_points, supportcfg}; switch (sm.cfg.tree_type) { case sla::SupportTreeType::Default: { From 0bbd50eaa01c83a2f5dbb8a5b9f9cc53a31769c0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Nov 2022 17:00:16 +0100 Subject: [PATCH 33/86] Bugfixes and new tests for pillar search --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 331 +----------------- src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp | 80 +++++ tests/sla_print/sla_print_tests.cpp | 8 - tests/sla_print/sla_supptreeutils_tests.cpp | 307 ++++++++++------ tests/sla_print/sla_test_utils.hpp | 54 --- 6 files changed, 277 insertions(+), 505 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index d30499c110..3f7fd7b803 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,7 +21,7 @@ class Properties public: - constexpr bool group_pillars() const noexcept { return true; } + constexpr bool group_pillars() const noexcept { return false; } // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 08e410431c..1a771e028b 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -26,30 +26,6 @@ using Slic3r::opt::AlgNLoptGenetic; using Slic3r::Geometry::dir_to_spheric; using Slic3r::Geometry::spheric_to_dir; -// Helper function for pillar interconnection where pairs of already connected -// pillars should be checked for not to be processed again. This can be done -// in constant time with a set of hash values uniquely representing a pair of -// integers. The order of numbers within the pair should not matter, it has -// the same unique hash. The hash value has to have twice as many bits as the -// arguments need. If the same integral type is used for args and return val, -// make sure the arguments use only the half of the type's bit depth. -template> -IntegerOnly pairhash(I a, I b) -{ - using std::ceil; using std::log2; using std::max; using std::min; - static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT); - static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT); - static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits; - - I g = min(a, b), l = max(a, b); - - // Assume the hash will fit into the output variable - assert((g ? (ceil(log2(g))) : 0) <= shift); - assert((l ? (ceil(log2(l))) : 0) <= shift); - - return (DoubleI(g) << shift) + l; -} - // Give points on a 3D ring with given center, radius and orientation // method based on: // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space @@ -294,62 +270,6 @@ Hit pinhead_mesh_hit(Ex ex, head.r_back_mm, head.width_mm, safety_d); } -template -std::optional search_widening_path(Ex policy, - const SupportableMesh &sm, - const Vec3d &jp, - const Vec3d &dir, - double radius, - double new_radius) -{ - double w = radius + 2 * sm.cfg.head_back_radius_mm; - double stopval = w + jp.z() - ground_level(sm); - Optimizer solver(get_criteria(sm.cfg).stop_score(stopval)); - - auto [polar, azimuth] = dir_to_spheric(dir); - - double fallback_ratio = radius / sm.cfg.head_back_radius_mm; - - auto oresult = solver.to_max().optimize( - [&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) { - auto &[plr, azm, t] = input; - - auto d = spheric_to_dir(plr, azm).normalized(); - - auto sd = sm.cfg.safety_distance(new_radius); - - double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius, - new_radius, t, sd) - .distance(); - - Beam beam{jp + t * d, d, new_radius}; - double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance(); - - if (ret > t && std::isinf(down)) - ret += jp.z() - ground_level(sm); - - return ret; - }, - initvals({polar, azimuth, w}), // start with what we have - bounds({ - {PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit - {-PI, PI}, // azimuth can be a full search - {radius + sm.cfg.head_back_radius_mm, - fallback_ratio * sm.cfg.max_bridge_length_mm} - })); - - if (oresult.score >= stopval) { - polar = std::get<0>(oresult.optimum); - azimuth = std::get<1>(oresult.optimum); - double t = std::get<2>(oresult.optimum); - Vec3d endp = jp + t * spheric_to_dir(polar, azimuth); - - return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm); - } - - return {}; -} - inline double distance(const SupportPoint &a, const SupportPoint &b) { return (a.pos - b.pos).norm(); @@ -518,167 +438,6 @@ struct GroundConnection { operator bool() const { return pillar_base.has_value() && !path.empty(); } }; -template -GroundConnection find_pillar_route(Ex policy, - const SupportableMesh &sm, - const Junction &source, - const Vec3d &sourcedir, - double end_radius) -{ - GroundConnection ret; - ret.path.emplace_back(source); - - double sd = sm.cfg.safety_distance(source.r); - auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)}; - - auto hit = beam_mesh_hit(policy, - sm.emesh, - Beam{{source.pos, source.r}, {gp, end_radius}}, - sd); - - if (std::isinf(hit.distance())) { - double base_radius = std::max(sm.cfg.base_radius_mm, end_radius); - - ret.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; - } - - return ret; -} - -//template -//GroundConnection find_pillar_route(Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// const Vec3d &sourcedir, -// double end_radius) -//{ -// GroundConnection ret; - -// ret.path.emplace_back(source); - -// Vec3d jp = source.pos, endp = jp, dir = sourcedir; -// bool can_add_base = false/*, non_head = false*/; - -// double gndlvl = 0.; // The Z level where pedestals should be -// double jp_gnd = 0.; // The lowest Z where a junction center can be -// double gap_dist = 0.; // The gap distance between the model and the pad -// double radius = source.r; -// double sd = sm.cfg.safety_distance(radius); - -// double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); - -// auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; - -// auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd, end_radius] -// (bool base_en = true) -// { -// can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; -// double base_r = can_add_base ? std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; -// gndlvl = ground_level(sm); -// if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm; -// jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm); -// gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON; -// }; - -// eval_limits(); - -// // We are dealing with a mini pillar that's potentially too long -// if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) -// { -// std::optional diffbr = -// search_widening_path(policy, sm, jp, dir, radius, -// sm.cfg.head_back_radius_mm); - -// if (diffbr && diffbr->endp.z() > jp_gnd) { -// endp = diffbr->endp; -// radius = diffbr->end_r; -// ret.path.emplace_back(endp, radius); -// dir = diffbr->get_dir(); -// eval_limits(); -// } else return ret; -// } - -// if (sm.cfg.object_elevation_mm < EPSILON) -// { -// // get a suitable direction for the corrector bridge. It is the -// // original sourcedir's azimuth but the polar angle is saturated to the -// // configured bridge slope. -// auto [polar, azimuth] = dir_to_spheric(dir); -// polar = PI - sm.cfg.bridge_slope; -// Vec3d d = spheric_to_dir(polar, azimuth).normalized(); -// double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance(); -// double tmax = std::min(sm.cfg.max_bridge_length_mm, t); -// t = 0.; - -// double zd = endp.z() - jp_gnd; -// double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); -// tmax = std::min(tmax, tmax2); - -// Vec3d nexp = endp; -// double dlast = 0.; -// double rnext = radius; -// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || -// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && -// t < tmax) -// { -// t += radius; -// nexp = endp + t * d; -// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); -// rnext = rnext + bridge_ratio * (end_radius - rnext); -// } - -// // If could not find avoidance bridge for the pad gap, try again -// // without the pillar base -// if (dlast < gap_dist && can_add_base) { -// nexp = endp; -// t = 0.; -// rnext = radius; -// can_add_base = false; -// eval_limits(can_add_base); - -// zd = endp.z() - jp_gnd; -// tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope); -// tmax = std::min(tmax, tmax2); - -// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || -// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && t < tmax) { -// t += radius; -// nexp = endp + t * d; - -// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); -// rnext = rnext + bridge_ratio * (end_radius - rnext); -// } -// } - -// // Could not find a path to avoid the pad gap -// if (dlast < gap_dist) { -// ret.path.clear(); -// return ret; -// } - -// if (t > 0.) { // Need to make additional bridge -// ret.path.emplace_back(nexp, rnext); -// endp = nexp; -// } -// } - -// Vec3d gp = to_floor(endp); -// auto hit = beam_mesh_hit(policy, sm.emesh, -// Beam{{endp, radius}, {gp, end_radius}}, sd); - -// if (std::isinf(hit.distance())) { -// double base_radius = can_add_base ? -// std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; - -// Vec3d gp = to_floor(endp); -// ret.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; -// } - -// return ret; -//} - inline long build_ground_connection(SupportTreeBuilder &builder, const SupportableMesh &sm, const GroundConnection &conn) @@ -714,89 +473,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder, return ret; } -template -GroundConnection find_ground_connection( - Ex policy, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) -{ - auto hjp = j.pos; - double r = j.r; - auto sd = sm.cfg.safety_distance(r); - double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm)); - - double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); - t = std::min(t, sm.cfg.max_bridge_length_mm); - double d = 0.; - - GroundConnection gnd_route; - - while (!gnd_route && d < t) { - Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - ground_level(sm))); - double pill_r = r + bridge_ratio * (end_r - r); - gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - - d += r; - } - - GroundConnection ret; - - if (d > 0.) - ret.path.emplace_back(j); - - for (auto &p : gnd_route.path) - ret.path.emplace_back(p); - - // This will ultimately determine if the route is valid or not - // but the path junctions will be provided anyways, so invalid paths - // can be inspected - ret.pillar_base = gnd_route.pillar_base; - - return ret; -} - -template -GroundConnection optimize_ground_connection( - Ex policy, - const SupportableMesh &sm, - const Junction &j, - double end_radius, - const Vec3d &init_dir = DOWN) -{ - double downdst = j.pos.z() - ground_level(sm); - - auto res = find_ground_connection(policy, sm, j, init_dir, end_radius); - if (res) - return res; - - // Optimize bridge direction: - // Straight path failed so we will try to search for a suitable - // direction out of the cavity. - auto [polar, azimuth] = dir_to_spheric(init_dir); - - Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); - solver.seed(0); // we want deterministic behavior - - auto sd = sm.cfg.safety_distance(j.r); - auto oresult = solver.to_max().optimize( - [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { - auto &[plr, azm] = input; - Vec3d n = spheric_to_dir(plr, azm).normalized(); - Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}}; - return beam_mesh_hit(policy, sm.emesh, beam, sd).distance(); - }, - initvals({polar, azimuth}), // let's start with what we have - bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} }) - ); - - Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); - - return find_ground_connection(policy, sm, j, bridgedir, end_radius); -} - template GroundConnection deepsearch_ground_connection( Ex policy, @@ -861,10 +537,10 @@ GroundConnection deepsearch_ground_connection( if (gap < zelev_gap) ret = full_len - zelev_gap + gap; else // success - ret = StopScore; + ret = StopScore + EPSILON; } else { // No zero elevation, return success - ret = StopScore; + ret = StopScore + EPSILON; } } else { // Ground route is not free @@ -902,7 +578,8 @@ GroundConnection deepsearch_ground_connection( Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; conn.path.emplace_back(j); - conn.path.emplace_back(Junction{bridge_end, bridge_r}); + if (bridge_len > EPSILON) + conn.path.emplace_back(Junction{bridge_end, bridge_r}); conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; diff --git a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp index 43a8ddb59d..b504d82fb3 100644 --- a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp +++ b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp @@ -8,6 +8,86 @@ namespace Slic3r { namespace sla { +// Helper function for pillar interconnection where pairs of already connected +// pillars should be checked for not to be processed again. This can be done +// in constant time with a set of hash values uniquely representing a pair of +// integers. The order of numbers within the pair should not matter, it has +// the same unique hash. The hash value has to have twice as many bits as the +// arguments need. If the same integral type is used for args and return val, +// make sure the arguments use only the half of the type's bit depth. +template> +IntegerOnly pairhash(I a, I b) +{ + using std::ceil; using std::log2; using std::max; using std::min; + static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT); + static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT); + static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits; + + I g = min(a, b), l = max(a, b); + + // Assume the hash will fit into the output variable + assert((g ? (ceil(log2(g))) : 0) <= shift); + assert((l ? (ceil(log2(l))) : 0) <= shift); + + return (DoubleI(g) << shift) + l; +} + +template +std::optional search_widening_path(Ex policy, + const SupportableMesh &sm, + const Vec3d &jp, + const Vec3d &dir, + double radius, + double new_radius) +{ + double w = radius + 2 * sm.cfg.head_back_radius_mm; + double stopval = w + jp.z() - ground_level(sm); + Optimizer solver(get_criteria(sm.cfg).stop_score(stopval)); + + auto [polar, azimuth] = dir_to_spheric(dir); + + double fallback_ratio = radius / sm.cfg.head_back_radius_mm; + + auto oresult = solver.to_max().optimize( + [&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) { + auto &[plr, azm, t] = input; + + auto d = spheric_to_dir(plr, azm).normalized(); + + auto sd = sm.cfg.safety_distance(new_radius); + + double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius, + new_radius, t, sd) + .distance(); + + Beam beam{jp + t * d, d, new_radius}; + double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance(); + + if (ret > t && std::isinf(down)) + ret += jp.z() - ground_level(sm); + + return ret; + }, + initvals({polar, azimuth, w}), // start with what we have + bounds({ + {PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit + {-PI, PI}, // azimuth can be a full search + {radius + sm.cfg.head_back_radius_mm, + fallback_ratio * sm.cfg.max_bridge_length_mm} + })); + + if (oresult.score >= stopval) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + double t = std::get<2>(oresult.optimum); + Vec3d endp = jp + t * spheric_to_dir(polar, azimuth); + + return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm); + } + + return {}; +} + // This is a proxy function for pillar creation which will mind the gap // between the pad and the model bottom in zero elevation mode. // 'pinhead_junctionpt' is the starting junction point which needs to be diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 8ea91d57a1..d581f8340c 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -32,13 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = { } // namespace -TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") { - test_pairhash(); - test_pairhash(); - test_pairhash(); - test_pairhash(); -} - TEST_CASE("Support point generator should be deterministic if seeded", "[SLASupportGeneration], [SLAPointGen]") { TriangleMesh mesh = load_model("A_upsidedown.obj"); diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 95ac766333..69117358dc 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -1,8 +1,158 @@ #include #include +#include + #include "libslic3r/Execution/ExecutionSeq.hpp" #include "libslic3r/SLA/SupportTreeUtils.hpp" +#include "libslic3r/SLA/SupportTreeUtilsLegacy.hpp" + +// Test pair hash for 'nums' random number pairs. +template void test_pairhash() +{ + const constexpr size_t nums = 1000; + I A[nums] = {0}, B[nums] = {0}; + std::unordered_set CH; + std::unordered_map> ints; + + std::random_device rd; + std::mt19937 gen(rd()); + + const I Ibits = int(sizeof(I) * CHAR_BIT); + const II IIbits = int(sizeof(II) * CHAR_BIT); + + int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; + if (std::is_signed::value) bits -= 1; + const I Imin = 0; + const I Imax = I(std::pow(2., bits) - 1); + + std::uniform_int_distribution dis(Imin, Imax); + + for (size_t i = 0; i < nums;) { + I a = dis(gen); + if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; } + } + + for (size_t i = 0; i < nums;) { + I b = dis(gen); + if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; } + } + + for (size_t i = 0; i < nums; ++i) { + I a = A[i], b = B[i]; + + REQUIRE(a != b); + + II hash_ab = Slic3r::sla::pairhash(a, b); + II hash_ba = Slic3r::sla::pairhash(b, a); + REQUIRE(hash_ab == hash_ba); + + auto it = ints.find(hash_ab); + + if (it != ints.end()) { + REQUIRE(( + (it->second.first == a && it->second.second == b) || + (it->second.first == b && it->second.second == a) + )); + } else + ints[hash_ab] = std::make_pair(a, b); + } +} + +TEST_CASE("Pillar pairhash should be unique", "[suptreeutils]") { + test_pairhash(); + test_pairhash(); + test_pairhash(); + test_pairhash(); +} + +static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn, + const Slic3r::sla::SupportableMesh &sm, + const Slic3r::sla::Junction &j, + double end_r, + const std::string &stl_fname = "output.stl") +{ + using namespace Slic3r; + +#ifndef NDEBUG + + sla::SupportTreeBuilder builder; + + if (!conn) + builder.add_junction(j); + + sla::build_ground_connection(builder, sm, conn); + + indexed_triangle_set mesh = *sm.emesh.get_triangle_mesh(); + its_merge(mesh, builder.merged_mesh()); + + its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh); +#endif + + REQUIRE(bool(conn)); + + // The route should include the source and one avoidance junction. + REQUIRE(conn.path.size() == 2); + + // Check if the radius increases with each node + REQUIRE(conn.path.front().r < conn.path.back().r); + REQUIRE(conn.path.back().r < conn.pillar_base->r_top); + + // The end radius and the pillar base's upper radius should match + REQUIRE(conn.pillar_base->r_top == Approx(end_r)); +} + +TEST_CASE("Pillar search dumb case", "[suptreeutils]") { + using namespace Slic3r; + + constexpr double FromR = 0.5; + auto j = sla::Junction{Vec3d::Zero(), FromR}; + + SECTION("with empty mesh") { + sla::SupportableMesh sm{indexed_triangle_set{}, + sla::SupportPoints{}, + sla::SupportTreeConfig{}}; + + constexpr double EndR = 1.; + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); + + REQUIRE(conn); + REQUIRE(conn.path.size() == 1); + REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); + } + + SECTION("with zero R source and destination") { + sla::SupportableMesh sm{indexed_triangle_set{}, + sla::SupportPoints{}, + sla::SupportTreeConfig{}}; + + j.r = 0.; + constexpr double EndR = 0.; + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); + + REQUIRE(conn); + REQUIRE(conn.path.size() == 1); + REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); + REQUIRE(conn.pillar_base->r_top == Approx(0.)); + } + + SECTION("with zero init direction") { + sla::SupportableMesh sm{indexed_triangle_set{}, + sla::SupportPoints{}, + sla::SupportTreeConfig{}}; + + constexpr double EndR = 1.; + Vec3d init_dir = Vec3d::Zero(); + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir); + + REQUIRE(conn); + REQUIRE(conn.path.size() == 1); + REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); + } +} TEST_CASE("Avoid disk below junction", "[suptreeutils]") { @@ -27,100 +177,34 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + SECTION("without elevation") { -#ifndef NDEBUG + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - sla::SupportTreeBuilder builder; + eval_ground_conn(conn, sm, j, EndRadius, "disk.stl"); - if (!conn) - builder.add_junction(j); + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); + } - sla::build_ground_connection(builder, sm, conn); + SECTION("with elevation") { + sm.cfg.object_elevation_mm = 0.; - its_merge(disk, builder.merged_mesh()); + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - its_write_stl_ascii("output_disk.stl", "disk", disk); -#endif + eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl"); - REQUIRE(bool(conn)); - - // The route should include the source and one avoidance junction. - REQUIRE(conn.path.size() == 2); - - // Check if the radius increases with each node - REQUIRE(conn.path.front().r < conn.path.back().r); - REQUIRE(conn.path.back().r < conn.pillar_base->r_top); - - // The end radius and the pillar base's upper radius should match - REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); - - // Check if the avoidance junction is indeed outside of the disk barrier's - // edge. - auto p = conn.path.back().pos; - double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); - REQUIRE(pR + FromRadius > CylRadius); -} - -TEST_CASE("Avoid disk below junction - Zero elevation", "[suptreeutils]") -{ - // In this test there will be a disk mesh with some radius, centered at - // (0, 0, 0) and above the disk, a junction from which the support pillar - // should be routed. The algorithm needs to find an avoidance route. - - using namespace Slic3r; - - constexpr double FromRadius = .5; - constexpr double EndRadius = 1.; - constexpr double CylRadius = 4.; - constexpr double CylHeight = 1.; - - sla::SupportTreeConfig cfg; - cfg.object_elevation_mm = 0.; - - indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight); - - // 2.5 * CyRadius height should be enough to be able to insert a bridge - // with 45 degree tilt above the disk. - sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius}; - - sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - - sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - -#ifndef NDEBUG - - sla::SupportTreeBuilder builder; - - if (!conn) - builder.add_junction(j); - - sla::build_ground_connection(builder, sm, conn); - - its_merge(disk, builder.merged_mesh()); - - its_write_stl_ascii("output_disk_ze.stl", "disk", disk); -#endif - - REQUIRE(bool(conn)); - - // The route should include the source and one avoidance junction. - REQUIRE(conn.path.size() == 2); - - // Check if the radius increases with each node - REQUIRE(conn.path.front().r < conn.path.back().r); - REQUIRE(conn.path.back().r < conn.pillar_base->r_top); - - // The end radius and the pillar base's upper radius should match - REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); - - // Check if the avoidance junction is indeed outside of the disk barrier's - // edge. - auto p = conn.path.back().pos; - double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); - REQUIRE(pR + FromRadius > CylRadius); + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); + } } TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]") @@ -150,38 +234,31 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + SECTION("without elevation") { + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); -#ifndef NDEBUG + eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier.stl"); - sla::SupportTreeBuilder builder; + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); + } - if (!conn) - builder.add_junction(j); + SECTION("without elevation") { + sm.cfg.object_elevation_mm = 0.; - sla::build_ground_connection(builder, sm, conn); + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - its_merge(disk, builder.merged_mesh()); + eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier_ze.stl"); - its_write_stl_ascii("output_disk_wall.stl", "disk_wall", disk); -#endif - - REQUIRE(bool(conn)); - - // The route should include the source and one avoidance junction. - REQUIRE(conn.path.size() == 2); - - // Check if the radius increases with each node - REQUIRE(conn.path.front().r < conn.path.back().r); - REQUIRE(conn.path.back().r < conn.pillar_base->r_top); - - // The end radius end the pillar base's upper radius should match - REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); - - // Check if the avoidance junction is indeed outside of the disk barrier's - // edge. - auto p = conn.path.back().pos; - double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); - REQUIRE(pR + FromRadius > CylRadius); + // Check if the avoidance junction is indeed outside of the disk barrier's + // edge. + auto p = conn.path.back().pos; + double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y()); + REQUIRE(pR + FromRadius > CylRadius); + } } diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index 187a72d545..103b2f66a4 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -14,14 +14,12 @@ #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/SLA/Pad.hpp" #include "libslic3r/SLA/SupportTreeBuilder.hpp" -#include "libslic3r/SLA/SupportTreeUtils.hpp" #include "libslic3r/SLA/SupportPointGenerator.hpp" #include "libslic3r/SLA/AGGRaster.hpp" #include "libslic3r/SLA/ConcaveHull.hpp" #include "libslic3r/MTUtils.hpp" #include "libslic3r/SVG.hpp" -#include "libslic3r/Format/OBJ.hpp" using namespace Slic3r; @@ -111,58 +109,6 @@ inline void test_support_model_collision( test_support_model_collision(obj_filename, input_supportcfg, hcfg, {}); } -// Test pair hash for 'nums' random number pairs. -template void test_pairhash() -{ - const constexpr size_t nums = 1000; - I A[nums] = {0}, B[nums] = {0}; - std::unordered_set CH; - std::unordered_map> ints; - - std::random_device rd; - std::mt19937 gen(rd()); - - const I Ibits = int(sizeof(I) * CHAR_BIT); - const II IIbits = int(sizeof(II) * CHAR_BIT); - - int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; - if (std::is_signed::value) bits -= 1; - const I Imin = 0; - const I Imax = I(std::pow(2., bits) - 1); - - std::uniform_int_distribution dis(Imin, Imax); - - for (size_t i = 0; i < nums;) { - I a = dis(gen); - if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; } - } - - for (size_t i = 0; i < nums;) { - I b = dis(gen); - if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; } - } - - for (size_t i = 0; i < nums; ++i) { - I a = A[i], b = B[i]; - - REQUIRE(a != b); - - II hash_ab = sla::pairhash(a, b); - II hash_ba = sla::pairhash(b, a); - REQUIRE(hash_ab == hash_ba); - - auto it = ints.find(hash_ab); - - if (it != ints.end()) { - REQUIRE(( - (it->second.first == a && it->second.second == b) || - (it->second.first == b && it->second.second == a) - )); - } else - ints[hash_ab] = std::make_pair(a, b); - } -} - // SLA Raster test utils: using TPixel = uint8_t; From 003647e898cfe5ba49b712a6fd17e7fd858337ac Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 10:48:18 +0100 Subject: [PATCH 34/86] Prepare UI for organic supports option --- src/libslic3r/PrintConfig.cpp | 4 +++- src/libslic3r/SLA/SupportTree.cpp | 3 +++ src/libslic3r/SLA/SupportTreeStrategies.hpp | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 482e36e432..ebdfd2ff68 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -181,7 +181,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAMaterialSpeed); static inline const t_config_enum_values s_keys_map_SLASupportTreeType = { {"default", int(sla::SupportTreeType::Default)}, - {"branching", int(sla::SupportTreeType::Branching)} + {"branching", int(sla::SupportTreeType::Branching)}, + //TODO: {"organic", int(sla::SupportTreeType::Organic)} }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLASupportTreeType); @@ -3614,6 +3615,7 @@ void PrintConfigDef::init_sla_params() def->enum_labels = ConfigOptionEnum::get_enum_names(); def->enum_labels[0] = L("Default"); def->enum_labels[1] = L("Branching"); + // TODO: def->enum_labels[2] = L("Organic"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index b221c4330a..37c2e85e96 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -39,6 +39,9 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, create_branching_tree(*builder, sm); break; } + case SupportTreeType::Organic: { + // TODO + } default:; } diff --git a/src/libslic3r/SLA/SupportTreeStrategies.hpp b/src/libslic3r/SLA/SupportTreeStrategies.hpp index 487f43575b..2cef1a8a94 100644 --- a/src/libslic3r/SLA/SupportTreeStrategies.hpp +++ b/src/libslic3r/SLA/SupportTreeStrategies.hpp @@ -5,7 +5,7 @@ namespace Slic3r { namespace sla { -enum class SupportTreeType { Default, Branching }; +enum class SupportTreeType { Default, Branching, Organic }; enum class PillarConnectionMode { zigzag, cross, dynamic }; }} // namespace Slic3r::sla From 2565d45543432b1c39a00aaa94d9d4eb201a0202 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 21 Nov 2022 12:35:11 +0100 Subject: [PATCH 35/86] Extend Optimizer interface to accept constraint functions --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 94 +++++++++++++++++++---- src/libslic3r/Optimize/Optimizer.hpp | 24 +++++- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 3859217da8..87aec4b361 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -13,7 +13,7 @@ #include -#include +#include "Optimizer.hpp" namespace Slic3r { namespace opt { @@ -64,12 +64,36 @@ struct NLopt { // Helper RAII class for nlopt_opt template class NLoptOpt {}; +// Map a generic function to each argument following the mapping function +template +Fn for_each_argument(Fn &&fn, Args&&...args) +{ + // see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/ + (fn(std::forward(args)),...); + + return fn; +} + +template +Fn for_each_in_tuple(Fn fn, const std::tuple &tup) +{ + auto arg = std::tuple_cat(std::make_tuple(fn), tup); + auto mpfn = [](auto fn, auto...pack) { + return for_each_argument(fn, pack...); + }; + std::apply(mpfn, arg); + + return fn; +} + // Optimizers based on NLopt. template class NLoptOpt> { protected: StopCriteria m_stopcr; OptDir m_dir = OptDir::MIN; + static constexpr double ConstraintEps = 1e-6; + template using TOptData = std::tuple*, NLoptOpt*, nlopt_opt>; @@ -78,7 +102,7 @@ protected: double *gradient, void *data) { - assert(n >= N); + assert(n == N); auto tdata = static_cast*>(data); @@ -101,6 +125,21 @@ protected: return scoreval; } + template + static double constrain_func(unsigned n, const double *params, + double *gradient, + void *data) + { + assert(n == N); + + auto tdata = static_cast*>(data); + + auto &fnptr = std::get<0>(*tdata); + auto funval = to_arr(params); + + return (*fnptr)(funval); + } + template void set_up(NLopt &nl, const Bounds& bounds) { @@ -125,13 +164,30 @@ protected: nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations()); } - template - Result optimize(NLopt &nl, Fn &&fn, const Input &initvals) + template + Result optimize(NLopt &nl, Fn &&fn, const Input &initvals, + const std::tuple &equalities, + const std::tuple &inequalities) { Result r; TOptData data = std::make_tuple(&fn, this, nl.ptr); + auto do_for_each_eq = [this, &nl](auto &&arg) { + auto data = std::make_tuple(&arg, this, nl.ptr); + using F = std::remove_cv_t; + nlopt_add_equality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + }; + + auto do_for_each_ineq = [this, &nl](auto &&arg) { + auto data = std::make_tuple(&arg, this, nl.ptr); + using F = std::remove_cv_t; + nlopt_add_inequality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + }; + + for_each_in_tuple(do_for_each_eq, equalities); + for_each_in_tuple(do_for_each_ineq, inequalities); + switch(m_dir) { case OptDir::MIN: nlopt_set_min_objective(nl.ptr, optfunc, &data); break; @@ -147,15 +203,18 @@ protected: public: - template + template Result optimize(Func&& func, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &equalities, + const std::tuple &inequalities) { NLopt nl{alg, N}; set_up(nl, bounds); - return optimize(nl, std::forward(func), initvals); + return optimize(nl, std::forward(func), initvals, + equalities, inequalities); } explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {} @@ -173,10 +232,12 @@ class NLoptOpt>: public NLoptOpt> using Base = NLoptOpt>; public: - template + template Result optimize(Fn&& f, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &equalities, + const std::tuple &inequalities) { NLopt nl_glob{glob, N}, nl_loc{loc, N}; @@ -184,7 +245,8 @@ public: Base::set_up(nl_loc, bounds); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); - return Base::optimize(nl_glob, std::forward(f), initvals); + return Base::optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); } explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} @@ -201,12 +263,16 @@ public: Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; } Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; } - template + template Result optimize(Func&& func, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &eq_constraints = {}, + const std::tuple &ineq_constraint = {}) { - return m_opt.optimize(std::forward(func), initvals, bounds); + return m_opt.optimize(std::forward(func), initvals, bounds, + eq_constraints, + ineq_constraint); } explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} @@ -225,7 +291,9 @@ public: using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; +using AlgNLoptCobyla = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; +using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptMLSL = detail::NLoptAlgComb; }} // namespace Slic3r::opt diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index bf95d9ee07..6212a5f59d 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -12,6 +12,15 @@ namespace Slic3r { namespace opt { +template +using FloatingOnly = std::enable_if_t::value, O>; + +template> +constexpr T NaN = std::numeric_limits::quiet_NaN(); + +constexpr float NaNf = NaN; +constexpr double NaNd = NaN; + // A type to hold the complete result of the optimization. template struct Result { int resultcode; // Method dependent @@ -79,7 +88,7 @@ public: double stop_score() const { return m_stop_score; } - StopCriteria & max_iterations(double val) + StopCriteria & max_iterations(unsigned val) { m_max_iterations = val; return *this; } @@ -137,16 +146,25 @@ public: // For each dimension an interval (Bound) has to be given marking the bounds // for that dimension. // + // Optionally, some constraints can be given in the form of double(Input) + // functors. The parameters eq_constraints and ineq_constraints can be used + // to add equality and inequality (<= 0) constraints to the optimization. + // Note that it is up the the particular method if it accepts these + // constraints. + // // initvals have to be within the specified bounds, otherwise its undefined // behavior. // // Func can return a score of type double or optionally a ScoreGradient // class to indicate the function gradient for a optimization methods that // make use of the gradient. - template + template Result optimize(Func&& /*func*/, const Input &/*initvals*/, - const Bounds& /*bounds*/) { return {}; } + const Bounds& /*bounds*/, + const std::tuple &eq_constraints = {}, + const std::tuple &ineq_constraint = {} + ) { return {}; } // optional for randomized methods: void seed(long /*s*/) {} From 2cd6a2025419db5ff57d37d6da9782ea76f633cf Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 10:59:52 +0100 Subject: [PATCH 36/86] Move merge point search out of pointcloud to support tree utils --- src/libslic3r/BranchingTree/PointCloud.cpp | 72 +------------- tests/sla_print/sla_print_tests.cpp | 100 -------------------- tests/sla_print/sla_supptreeutils_tests.cpp | 95 +++++++++++++++++++ 3 files changed, 98 insertions(+), 169 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 319f334ffa..4075a20c27 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -1,81 +1,15 @@ #include "PointCloud.hpp" #include "libslic3r/Tesselate.hpp" +#include "libslic3r/SLA/SupportTreeUtils.hpp" #include namespace Slic3r { namespace branchingtree { -std::optional find_merge_pt(const Vec3f &A, - const Vec3f &B, - float critical_angle) +std::optional find_merge_pt(const Vec3f &A, const Vec3f &B, float max_slope) { - // The idea is that A and B both have their support cones. But searching - // for the intersection of these support cones is difficult and its enough - // to reduce this problem to 2D and search for the intersection of two - // rays that merge somewhere between A and B. The 2D plane is a vertical - // slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and - // the 2D X axis is determined by the XY direction of the AB vector. - // - // Z^ - // | A * - // | . . B * - // | . . . . - // | . . . . - // | . x . - // -------------------> XY - - // Determine the transformation matrix for the 2D projection: - Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f}; - Vec3f dir = diff.normalized(); // TODO: avoid normalization - - Eigen::Matrix tr2D; - tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()}; - tr2D.row(1) = Vec3f{0.f, 0.f, 1.f}; - - // Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can - // omit 'a', pretend that its the origin and use BA as the vector b. - Vec2f b = tr2D * (B - A); - - // Get the square sine of the ray emanating from 'a' towards 'b'. This ray might - // exceed the allowed angle but that is corrected subsequently. - // The sign of the original sine is also needed, hence b.y is multiplied by - // abs(b.y) - float b_sqn = b.squaredNorm(); - float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f; - - // sine2 from 'b' to 'a' is the opposite of sine2 from a to b - float sin2sig_b = -sin2sig_a; - - // Derive the allowed angles from the given critical angle. - // critical_angle is measured from the horizontal X axis. - // The rays need to go downwards which corresponds to negative angles - - float sincrit = std::sin(critical_angle); // sine of the critical angle - float sin2crit = -sincrit * sincrit; // signed sine squared - sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays - sin2sig_b = std::min(sin2sig_b, sin2crit); // - float sin2_a = std::abs(sin2sig_a); // Get cosine squared values - float sin2_b = std::abs(sin2sig_b); - float cos2_a = 1.f - sin2_a; - float cos2_b = 1.f - sin2_b; - - // Derive the new direction vectors. This is by square rooting the sin2 - // and cos2 values and restoring the original signs - Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)}; - Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)}; - - // Determine where two rays ([0, 0], Da), (b, Db) intersect. - // Based on - // https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect - // One ray is emanating from (0, 0) so the formula is simplified - double t1 = (Db.y() * b.x() - b.y() * Db.x()) / - (Da.x() * Db.y() - Da.y() * Db.x()); - - Vec2f mp = t1 * Da; - Vec3f Mp = A + tr2D.transpose() * mp; - - return t1 >= 0.f ? Mp : Vec3f{}; + return sla::find_merge_pt(A, B, max_slope); } void to_eigen_mesh(const indexed_triangle_set &its, diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index d581f8340c..a733e77cc5 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -165,106 +165,6 @@ TEST_CASE("DefaultSupports::FloorSupportsDoNotPierceModel", "[SLASupportGenerati // for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg); //} -bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt, float angle) -{ - Vec3d D = (pt - supp).cast(); - double dot_sq = -D.z() * std::abs(-D.z()); - - return dot_sq < - D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle)); -} - -TEST_CASE("BranchingSupports::MergePointFinder", "[SLASupportGeneration][Branching]") { - SECTION("Identical points have the same merge point") { - Vec3f a{0.f, 0.f, 0.f}, b = a; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - REQUIRE((*mergept - b).norm() < EPSILON); - REQUIRE((*mergept - a).norm() < EPSILON); - } - - // ^ Z - // | a * - // | - // | b * <= mergept - SECTION("Points at different heights have the lower point as mergepoint") { - Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f}; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON); - } - - // -|---------> X - // a b - // * * - // * <= mergept - SECTION("Points at different X have mergept in the middle at lower Z") { - Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f}; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - - // Distance of mergept should be equal from both input points - float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm()); - - REQUIRE(D < EPSILON); - REQUIRE(!is_outside_support_cone(a, *mergept, slope)); - REQUIRE(!is_outside_support_cone(b, *mergept, slope)); - } - - // -|---------> Y - // a b - // * * - // * <= mergept - SECTION("Points at different Y have mergept in the middle at lower Z") { - Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f}; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - - // Distance of mergept should be equal from both input points - float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm()); - - REQUIRE(D < EPSILON); - REQUIRE(!is_outside_support_cone(a, *mergept, slope)); - REQUIRE(!is_outside_support_cone(b, *mergept, slope)); - } - - SECTION("Points separated by less than critical angle have the lower point as mergept") { - Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f}; - auto slope = float(PI / 4.); - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - REQUIRE((*mergept - b).norm() < 2 * EPSILON); - } - - // -|----------------------------> Y - // a b - // * * <= mergept * - // - SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") { - Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f}; - auto slope = EPSILON; - - auto mergept = branchingtree::find_merge_pt(a, b, slope); - - REQUIRE(bool(mergept)); - Vec3f middle = (b + a) / 2.; - REQUIRE((*mergept - middle).norm() < 4 * EPSILON); - } -} TEST_CASE("BranchingSupports::ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration][Branching]") { diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 69117358dc..d529eefb5a 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -262,3 +262,98 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" REQUIRE(pR + FromRadius > CylRadius); } } + +TEST_CASE("BranchingSupports::MergePointFinder", "[suptreeutils]") { + using namespace Slic3r; + + SECTION("Identical points have the same merge point") { + Vec3f a{0.f, 0.f, 0.f}, b = a; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + REQUIRE((*mergept - b).norm() < EPSILON); + REQUIRE((*mergept - a).norm() < EPSILON); + } + + // ^ Z + // | a * + // | + // | b * <= mergept + SECTION("Points at different heights have the lower point as mergepoint") { + Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f}; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON); + } + + // -|---------> X + // a b + // * * + // * <= mergept + SECTION("Points at different X have mergept in the middle at lower Z") { + Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f}; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + + // Distance of mergept should be equal from both input points + float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm()); + + REQUIRE(D < EPSILON); + REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope)); + REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope)); + } + + // -|---------> Y + // a b + // * * + // * <= mergept + SECTION("Points at different Y have mergept in the middle at lower Z") { + Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f}; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + + // Distance of mergept should be equal from both input points + float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm()); + + REQUIRE(D < EPSILON); + REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope)); + REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope)); + } + + SECTION("Points separated by less than critical angle have the lower point as mergept") { + Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f}; + auto slope = float(PI / 4.); + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + REQUIRE((*mergept - b).norm() < 2 * EPSILON); + } + + // -|----------------------------> Y + // a b + // * * <= mergept * + // + SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") { + Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f}; + auto slope = EPSILON; + + auto mergept = sla::find_merge_pt(a, b, slope); + + REQUIRE(bool(mergept)); + Vec3f middle = (b + a) / 2.; + REQUIRE((*mergept - middle).norm() < 4 * EPSILON); + } +} + From 9cdd6738aee3ce2b5f9d70ba683b339d169d73f1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:01:49 +0100 Subject: [PATCH 37/86] Widening improvements in SupportTreeUtils Fix failing tests after introducing wideningfn into ground route search --- src/libslic3r/SLA/SupportTreeUtils.hpp | 257 +++++++++++++++++++++++-- 1 file changed, 241 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 1a771e028b..6da28f1fdd 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -473,12 +473,20 @@ inline long build_ground_connection(SupportTreeBuilder &builder, return ret; } -template +template +constexpr bool IsWideningFn = std::is_invocable_r_v; + +template> > GroundConnection deepsearch_ground_connection( Ex policy, const SupportableMesh &sm, const Junction &j, - double end_radius, + WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { // Score is the total lenght of the route. Feasible routes will have @@ -488,9 +496,6 @@ GroundConnection deepsearch_ground_connection( const auto sd = sm.cfg.safety_distance(j.r); const auto gndlvl = ground_level(sm); - const double widening = end_radius - j.r; - const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; auto criteria = get_criteria(sm.cfg).stop_score(StopScore); @@ -507,7 +512,7 @@ GroundConnection deepsearch_ground_connection( Vec3d bridge_end = j.pos + bridge_len * n; double full_len = bridge_len + bridge_end.z() - gndlvl; - double bridge_r = j.r + widening * bridge_len / full_len; + double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); double brhit_dist = 0.; if (bridge_len > EPSILON) { @@ -522,7 +527,8 @@ GroundConnection deepsearch_ground_connection( ret = brhit_dist; } else { // check if pillar can be placed below - auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); @@ -533,9 +539,11 @@ GroundConnection deepsearch_ground_connection( if (sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model - double gap = std::sqrt(sm.emesh.squared_distance(gp)); - if (gap < zelev_gap) - ret = full_len - zelev_gap + gap; + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + if (gap < max_gap) + ret = full_len - max_gap + gap; else // success ret = StopScore + EPSILON; } else { @@ -560,8 +568,8 @@ GroundConnection deepsearch_ground_connection( optfn, initvals({plr_init, azm_init, 0.}), // start with a zero bridge bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle - {-PI, PI}, // bounds for azimuth - {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length ); GroundConnection conn; @@ -572,22 +580,153 @@ GroundConnection deepsearch_ground_connection( Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = j.pos + bridge_len * n; + Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - double full_len = bridge_len + bridge_end.z() - gndlvl; - double bridge_r = j.r + widening * bridge_len / full_len; - Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); + double down_l = bridge_end.z() - gndlvl; + double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); conn.path.emplace_back(j); if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; } return conn; } +template +GroundConnection deepsearch_ground_connection(Ex policy, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) +{ + double gndlvl = ground_level(sm); + auto wfn = [end_radius, gndlvl](Ball src, Vec3d dir, double len) { + Vec3d dst = src.p + len * dir; + double widening = end_radius - src.R; + double zlen = dst.z() - gndlvl; + double full_len = len + zlen; + double r = src.R + widening * len / full_len; + + return r; + }; + + static_assert(IsWideningFn, "Not a widening function"); + + return deepsearch_ground_connection(policy, sm, j, wfn, init_dir); + +// // Score is the total lenght of the route. Feasible routes will have +// // infinite length (rays not colliding with model), thus the stop score +// // should be a reasonably big number. +// constexpr double StopScore = 1e6; + +// const auto sd = sm.cfg.safety_distance(j.r); +// const auto gndlvl = ground_level(sm); +// const double widening = end_radius - j.r; +// const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + +// auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + +// Optimizer solver(criteria); +// solver.seed(0); // enforce deterministic behavior + +// auto optfn = [&](const opt::Input<3> &input) { +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = j.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = j.r + widening * bridge_len / full_len; +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + +// if (std::isinf(gndhit.distance())) { +// // Ground route is free with this bridge + +// if (sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// if (gap < zelev_gap) +// ret = full_len - zelev_gap + gap; +// else // success +// ret = StopScore + EPSILON; +// } else { +// // No zero elevation, return success +// ret = StopScore + EPSILON; +// } +// } else { +// // Ground route is not free +// ret = bridge_len + gndhit.distance(); +// } +// } + +// return ret; +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); + +// auto oresult = solver.to_max().optimize( +// optfn, +// initvals({plr_init, azm_init, 0.}), // start with a zero bridge +// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle +// {-PI, PI}, // bounds for azimuth +// {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length +// ); + +// GroundConnection conn; + +// if (oresult.score >= StopScore) { +// // search was successful, extract and apply the result +// auto &[plr, azm, bridge_len] = oresult.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = j.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = j.r + widening * bridge_len / full_len; +// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + +// conn.path.emplace_back(j); +// if (bridge_len > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; +// } + +// return conn; +} + template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, @@ -671,6 +810,92 @@ std::optional calculate_anchor_placement(Ex policy, return anchor; } +inline bool is_outside_support_cone(const Vec3f &supp, + const Vec3f &pt, + float angle) +{ + using namespace Slic3r; + + Vec3d D = (pt - supp).cast(); + double dot_sq = -D.z() * std::abs(-D.z()); + + return dot_sq < + D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle)); +} + +inline // TODO: should be in a cpp +std::optional find_merge_pt(const Vec3f &A, + const Vec3f &B, + float critical_angle) +{ + // The idea is that A and B both have their support cones. But searching + // for the intersection of these support cones is difficult and its enough + // to reduce this problem to 2D and search for the intersection of two + // rays that merge somewhere between A and B. The 2D plane is a vertical + // slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and + // the 2D X axis is determined by the XY direction of the AB vector. + // + // Z^ + // | A * + // | . . B * + // | . . . . + // | . . . . + // | . x . + // -------------------> XY + + // Determine the transformation matrix for the 2D projection: + Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f}; + Vec3f dir = diff.normalized(); // TODO: avoid normalization + + Eigen::Matrix tr2D; + tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()}; + tr2D.row(1) = Vec3f{0.f, 0.f, 1.f}; + + // Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can + // omit 'a', pretend that its the origin and use BA as the vector b. + Vec2f b = tr2D * (B - A); + + // Get the square sine of the ray emanating from 'a' towards 'b'. This ray might + // exceed the allowed angle but that is corrected subsequently. + // The sign of the original sine is also needed, hence b.y is multiplied by + // abs(b.y) + float b_sqn = b.squaredNorm(); + float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f; + + // sine2 from 'b' to 'a' is the opposite of sine2 from a to b + float sin2sig_b = -sin2sig_a; + + // Derive the allowed angles from the given critical angle. + // critical_angle is measured from the horizontal X axis. + // The rays need to go downwards which corresponds to negative angles + + float sincrit = std::sin(critical_angle); // sine of the critical angle + float sin2crit = -sincrit * sincrit; // signed sine squared + sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays + sin2sig_b = std::min(sin2sig_b, sin2crit); // + float sin2_a = std::abs(sin2sig_a); // Get cosine squared values + float sin2_b = std::abs(sin2sig_b); + float cos2_a = 1.f - sin2_a; + float cos2_b = 1.f - sin2_b; + + // Derive the new direction vectors. This is by square rooting the sin2 + // and cos2 values and restoring the original signs + Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)}; + Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)}; + + // Determine where two rays ([0, 0], Da), (b, Db) intersect. + // Based on + // https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect + // One ray is emanating from (0, 0) so the formula is simplified + double t1 = (Db.y() * b.x() - b.y() * Db.x()) / + (Da.x() * Db.y() - Da.y() * Db.x()); + + Vec2f mp = t1 * Da; + Vec3f Mp = A + tr2D.transpose() * mp; + + return t1 >= 0.f ? Mp : Vec3f{}; +} + }} // namespace Slic3r::sla #endif // SLASUPPORTTREEUTILS_H From 249d2550d359f133d1ffca256a2355ec19973593 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:12:52 +0100 Subject: [PATCH 38/86] Remove pillar grouping as it does not work nicely --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 - src/libslic3r/SLA/BranchingTreeSLA.cpp | 62 +------------------ 2 files changed, 1 insertion(+), 63 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 3f7fd7b803..2d372452ba 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,8 +21,6 @@ class Properties public: - constexpr bool group_pillars() const noexcept { return false; } - // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept { diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 16236f7cdf..7d6f0b95de 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,12 +20,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::set m_ground_mem; - // Establish an index of - using PointIndexEl = std::pair; - boost::geometry::index:: - rtree /* ? */> - m_pillar_index; - std::vector m_pillars; // to put an index over them // cache succesfull ground connections @@ -112,44 +106,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { }); } - std::optional - search_for_existing_pillar(const branchingtree::Node &from) const - { - namespace bgi = boost::geometry::index; - - struct Output - { - std::optional &res; - - Output &operator*() { return *this; } - Output &operator=(const PointIndexEl &el) { res = el; return *this; } - Output &operator++() { return *this; } - }; - - std::optional result; - - auto filter = bgi::satisfies([this, &from](const PointIndexEl &e) { - assert(e.second < m_pillars.size()); - - auto len = (from.pos - e.first).norm(); - const branchingtree::Node &to = m_pillars[e.second]; - double sd = m_sm.cfg.safety_distance_mm; - - Beam beam{Ball{from.pos.cast(), get_radius(from)}, - Ball{e.first.cast(), get_radius(to)}}; - - return !branchingtree::is_occupied(to) && - len < m_sm.cfg.max_bridge_length_mm && - !m_cloud.is_outside_support_cone(from.pos, e.first) && - beam_mesh_hit(ex_tbb, m_sm.emesh, beam, sd).distance() > len; - }); - - m_pillar_index.query(filter && bgi::nearest(from.pos, 1), - Output{result}); - - return result; - } - public: BranchingTreeBuilder(SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -368,23 +324,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); - std::cout << "Original pillar count: " << vbuilder.pillars().size() << std::endl; - - if constexpr (props.group_pillars()) { - - std::vector bedleafs; - std::copy(vbuilder.pillars().begin(), vbuilder.pillars().end(), std::back_inserter(bedleafs)); - - branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; - BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; - branchingtree::build_tree(gndnodes, gndbuilder); - - std::cout << "Grouped pillar count: " << gndbuilder.pillars().size() << std::endl; - build_pillars(builder, gndbuilder, sm); - - } else { - build_pillars(builder, vbuilder, sm); - } + build_pillars(builder, vbuilder, sm); for (size_t id : vbuilder.unroutable_pinheads()) builder.head(id).invalidate(); From 87336349b19ecf0083cb671ec6fc271a1d34f247 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:14:24 +0100 Subject: [PATCH 39/86] Widening improvements 2 --- src/libslic3r/SLA/SupportTreeUtils.hpp | 143 ++++++------------------- 1 file changed, 30 insertions(+), 113 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 6da28f1fdd..41a1968b41 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -485,7 +485,7 @@ template EPSILON) { // beam_mesh_hit with a zero lenght bridge is invalid - Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; + Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); brhit_dist = brhit.distance(); } @@ -579,15 +579,15 @@ GroundConnection deepsearch_ground_connection( auto &[plr, azm, bridge_len] = oresult.optimum; Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = j.pos + bridge_len * n; + Vec3d bridge_end = source.pos + bridge_len * n; Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); + double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); double down_l = bridge_end.z() - gndlvl; double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - conn.path.emplace_back(j); + conn.path.emplace_back(source); if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); @@ -601,12 +601,12 @@ GroundConnection deepsearch_ground_connection( template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, - const Junction &j, + const Junction &source, double end_radius, const Vec3d &init_dir = DOWN) { double gndlvl = ground_level(sm); - auto wfn = [end_radius, gndlvl](Ball src, Vec3d dir, double len) { + auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) { Vec3d dst = src.p + len * dir; double widening = end_radius - src.R; double zlen = dst.z() - gndlvl; @@ -618,113 +618,30 @@ GroundConnection deepsearch_ground_connection(Ex policy, static_assert(IsWideningFn, "Not a widening function"); - return deepsearch_ground_connection(policy, sm, j, wfn, init_dir); + return deepsearch_ground_connection(policy, sm, source, wfn, init_dir); +} -// // Score is the total lenght of the route. Feasible routes will have -// // infinite length (rays not colliding with model), thus the stop score -// // should be a reasonably big number. -// constexpr double StopScore = 1e6; +struct DefaultWideningModel { + static constexpr double WIDENING_SCALE = 0.02; + const SupportableMesh &sm; -// const auto sd = sm.cfg.safety_distance(j.r); -// const auto gndlvl = ground_level(sm); -// const double widening = end_radius - j.r; -// const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + double operator()(const Ball &src, const Vec3d & /*dir*/, double len) { + static_assert(IsWideningFn, + "DefaultWideningModel is not a widening function"); -// auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len; + return src.R + w; + }; +}; -// Optimizer solver(criteria); -// solver.seed(0); // enforce deterministic behavior - -// auto optfn = [&](const opt::Input<3> &input) { -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = j.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = j.r + widening * bridge_len / full_len; -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - -// if (std::isinf(gndhit.distance())) { -// // Ground route is free with this bridge - -// if (sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// if (gap < zelev_gap) -// ret = full_len - zelev_gap + gap; -// else // success -// ret = StopScore + EPSILON; -// } else { -// // No zero elevation, return success -// ret = StopScore + EPSILON; -// } -// } else { -// // Ground route is not free -// ret = bridge_len + gndhit.distance(); -// } -// } - -// return ret; -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - -// auto oresult = solver.to_max().optimize( -// optfn, -// initvals({plr_init, azm_init, 0.}), // start with a zero bridge -// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle -// {-PI, PI}, // bounds for azimuth -// {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length -// ); - -// GroundConnection conn; - -// if (oresult.score >= StopScore) { -// // search was successful, extract and apply the result -// auto &[plr, azm, bridge_len] = oresult.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = j.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = j.r + widening * bridge_len / full_len; -// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - -// conn.path.emplace_back(j); -// if (bridge_len > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; -// } - -// return conn; +template +GroundConnection deepsearch_ground_connection(Ex policy, + const SupportableMesh &sm, + const Junction &source, + const Vec3d &init_dir = DOWN) +{ + return deepsearch_ground_connection(policy, sm, source, + DefaultWideningModel{sm}, init_dir); } template From 3d6bb38dd4b1d50ffe54d9957dba6da9bed2e549 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 14:33:17 +0100 Subject: [PATCH 40/86] Fix failing tests --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 4 ++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 5 +++-- tests/sla_print/sla_supptreeutils_tests.cpp | 18 ++++++++++++++++++ tests/sla_print/sla_test_utils.cpp | 12 ++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 7d6f0b95de..1ad0fd93f6 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -128,8 +128,8 @@ public: void report_unroutable(const branchingtree::Node &j) override { - BOOST_LOG_TRIVIAL(error) << "Cannot route junction at " << j.pos.x() - << " " << j.pos.y() << " " << j.pos.z(); + BOOST_LOG_TRIVIAL(warning) << "Cannot route junction at " << j.pos.x() + << " " << j.pos.y() << " " << j.pos.z(); // Discard all the support points connecting to this branch. discard_subtree(j.id); diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 41a1968b41..2d0fd859e3 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -591,8 +591,9 @@ GroundConnection deepsearch_ground_connection( if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + if (bridge_end.z() >= gndlvl) + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; } return conn; diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index d529eefb5a..8d1029152b 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -263,6 +263,24 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" } } +TEST_CASE("Find ground route just above ground", "[suptreeutils]") { + using namespace Slic3r; + + sla::SupportTreeConfig cfg; + cfg.object_elevation_mm = 0.; + + sla::Junction j{Vec3d{0., 0., 2. * cfg.head_back_radius_mm}, cfg.head_back_radius_mm}; + + sla::SupportableMesh sm{{}, sla::SupportPoints{}, cfg}; + + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, Geometry::spheric_to_dir(3 * PI/ 4, PI)); + + REQUIRE(conn); + + REQUIRE(conn.pillar_base->pos.z() >= Approx(ground_level(sm))); +} + TEST_CASE("BranchingSupports::MergePointFinder", "[suptreeutils]") { using namespace Slic3r; diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index e097a3bb72..b601cef112 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -181,6 +181,18 @@ void test_supports(const std::string &obj_filename, if (std::abs(supportcfg.object_elevation_mm) < EPSILON) allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm; +#ifndef NDEBUG + if (!(obb.min.z() >= Approx(allowed_zmin)) || !(obb.max.z() <= Approx(zmax))) + { + indexed_triangle_set its; + treebuilder.retrieve_full_mesh(its); + TriangleMesh m{its}; + m.merge(mesh); + m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" + + obj_filename).c_str()); + } +#endif + REQUIRE(obb.min.z() >= Approx(allowed_zmin)); REQUIRE(obb.max.z() <= Approx(zmax)); From cdac79016380ded66a02e8d65dfc34a90cbdc752 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 18:23:34 +0100 Subject: [PATCH 41/86] Try to fix pillar route search --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 31 +++++++---- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 60 ++++++++++++--------- tests/sla_print/sla_supptreeutils_tests.cpp | 6 +-- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 87aec4b361..46a0d06484 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -141,7 +141,9 @@ protected: } template - void set_up(NLopt &nl, const Bounds& bounds) + static void set_up(NLopt &nl, + const Bounds &bounds, + const StopCriteria &stopcr) { std::array lb, ub; @@ -153,15 +155,15 @@ protected: nlopt_set_lower_bounds(nl.ptr, lb.data()); nlopt_set_upper_bounds(nl.ptr, ub.data()); - double abs_diff = m_stopcr.abs_score_diff(); - double rel_diff = m_stopcr.rel_score_diff(); - double stopval = m_stopcr.stop_score(); + double abs_diff = stopcr.abs_score_diff(); + double rel_diff = stopcr.rel_score_diff(); + double stopval = stopcr.stop_score(); if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff); if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval); - if(m_stopcr.max_iterations() > 0) - nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations()); + if(stopcr.max_iterations() > 0) + nlopt_set_maxeval(nl.ptr, stopcr.max_iterations()); } template @@ -211,7 +213,7 @@ public: const std::tuple &inequalities) { NLopt nl{alg, N}; - set_up(nl, bounds); + set_up(nl, bounds, m_stopcr); return optimize(nl, std::forward(func), initvals, equalities, inequalities); @@ -230,6 +232,7 @@ template class NLoptOpt>: public NLoptOpt> { using Base = NLoptOpt>; + StopCriteria m_loc_stopcr; public: template @@ -241,15 +244,20 @@ public: { NLopt nl_glob{glob, N}, nl_loc{loc, N}; - Base::set_up(nl_glob, bounds); - Base::set_up(nl_loc, bounds); + Base::set_up(nl_glob, bounds, Base::get_criteria()); + Base::set_up(nl_loc, bounds, m_loc_stopcr); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); return Base::optimize(nl_glob, std::forward(f), initvals, equalities, inequalities); } - explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} + explicit NLoptOpt(StopCriteria stopcr = {}) + : Base{stopcr}, m_loc_stopcr{stopcr} + {} + + void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; } + const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } }; } // namespace detail; @@ -285,6 +293,9 @@ public: const StopCriteria &get_criteria() const { return m_opt.get_criteria(); } void seed(long s) { m_opt.seed(s); } + + void set_loc_criteria(const StopCriteria &cr) { m_opt.set_loc_criteria(cr); } + const StopCriteria &get_loc_criteria() const noexcept { return m_opt.get_loc_criteria(); } }; // Predefinded NLopt algorithms diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index e0d7db97de..ed5725780f 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -105,7 +105,7 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; static const double constexpr optimizer_rel_score_diff = 1e-10; - static const unsigned constexpr optimizer_max_iterations = 2000; + static const unsigned constexpr optimizer_max_iterations = 30000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 2d0fd859e3..0dffd08527 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -492,17 +492,24 @@ GroundConnection deepsearch_ground_connection( // Score is the total lenght of the route. Feasible routes will have // infinite length (rays not colliding with model), thus the stop score // should be a reasonably big number. - constexpr double StopScore = 1e6; + constexpr double Penality = 1e5; + constexpr double PenOffs = 1e2; const auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); - auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + auto criteria = get_criteria(sm.cfg); + criteria.abs_score_diff(1.); + criteria.rel_score_diff(0.1); + criteria.max_iterations(5000); Optimizer solver(criteria); + solver.set_loc_criteria(criteria.max_iterations(100).abs_score_diff(1.)); solver.seed(0); // enforce deterministic behavior + size_t icnt = 0; auto optfn = [&](const opt::Input<3> &input) { + ++icnt; double ret = NaNd; // solver suggests polar, azimuth and bridge length values: @@ -511,7 +518,7 @@ GroundConnection deepsearch_ground_connection( Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_len * n; - double full_len = bridge_len + bridge_end.z() - gndlvl; + double down_l = bridge_end.z() - gndlvl; double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); double brhit_dist = 0.; @@ -524,7 +531,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist; + ret = brhit_dist + Penality; } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -532,28 +539,27 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + double gnd_hit_d = std::min(gndhit.distance(), down_l); + double penality = 0.; - if (std::isinf(gndhit.distance())) { - // Ground route is free with this bridge - - if (sm.cfg.object_elevation_mm < EPSILON) { - // Dealing with zero elevation mode, to not route pillars - // into the gap between the optional pad and the model - double gap = std::sqrt(sm.emesh.squared_distance(gp)); - double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - if (gap < max_gap) - ret = full_len - max_gap + gap; - else // success - ret = StopScore + EPSILON; - } else { - // No zero elevation, return success - ret = StopScore + EPSILON; + if (!std::isinf(gndhit.distance())) + penality = Penality; + else if (sm.cfg.object_elevation_mm < EPSILON) { + // Dealing with zero elevation mode, to not route pillars + // into the gap between the optional pad and the model + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + if (gap < min_gap) { + penality = Penality + PenOffs * (min_gap - gap); } - } else { - // Ground route is not free - ret = bridge_len + gndhit.distance(); +// gnd_hit_d += std::max(0., min_gap - gap); //penality = Penality + 100000. * (min_gap - gap); +// if (gap < min_gap) { +// penality = Penality; +// } } + + ret = bridge_len + gnd_hit_d + penality; } return ret; @@ -564,7 +570,7 @@ GroundConnection deepsearch_ground_connection( // Saturate the polar angle to max tilt defined in config plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - auto oresult = solver.to_max().optimize( + auto oresult = solver.to_min().optimize( optfn, initvals({plr_init, azm_init, 0.}), // start with a zero bridge bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle @@ -572,10 +578,12 @@ GroundConnection deepsearch_ground_connection( {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length ); + std::cout << "iters: " << icnt << std::endl; + GroundConnection conn; - if (oresult.score >= StopScore) { - // search was successful, extract and apply the result + if (oresult.score < Penality) { + // Extract and apply the result auto &[plr, azm, bridge_len] = oresult.optimum; Vec3d n = spheric_to_dir(plr, azm); diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 8d1029152b..58918db412 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -177,7 +177,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - SECTION("without elevation") { + SECTION("with elevation") { sla::GroundConnection conn = sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); @@ -191,7 +191,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") REQUIRE(pR + FromRadius > CylRadius); } - SECTION("with elevation") { + SECTION("without elevation") { sm.cfg.object_elevation_mm = 0.; sla::GroundConnection conn = @@ -234,7 +234,7 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - SECTION("without elevation") { + SECTION("with elevation") { sla::GroundConnection conn = sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); From 056e7400278cd81bf93aee67c6a751a0e9ddbbfc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 29 Nov 2022 18:25:39 +0100 Subject: [PATCH 42/86] Better handling of nonlinear constraints in nlopt wrapper --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 147 ++++++++++++++++------ 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 46a0d06484..900728804e 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -74,18 +74,28 @@ Fn for_each_argument(Fn &&fn, Args&&...args) return fn; } -template -Fn for_each_in_tuple(Fn fn, const std::tuple &tup) +// Call fn on each element of the input tuple tup. +template +Fn for_each_in_tuple(Fn fn, Tup &&tup) { - auto arg = std::tuple_cat(std::make_tuple(fn), tup); - auto mpfn = [](auto fn, auto...pack) { - return for_each_argument(fn, pack...); + auto mpfn = [&fn](auto&...pack) { + for_each_argument(fn, pack...); }; - std::apply(mpfn, arg); + + std::apply(mpfn, tup); return fn; } +// Wrap each element of the tuple tup into a wrapper class W and return +// a new tuple with each element being of type W where T_i is the type of +// i-th element of tup. +template class W, class...Args> +auto wrap_tup(const std::tuple &tup) +{ + return std::tuple...>(tup); +} + // Optimizers based on NLopt. template class NLoptOpt> { protected: @@ -94,32 +104,38 @@ protected: static constexpr double ConstraintEps = 1e-6; - template using TOptData = - std::tuple*, NLoptOpt*, nlopt_opt>; + template struct OptData { + Fn fn; + NLoptOpt *self = nullptr; + nlopt_opt opt_raw = nullptr; + + OptData(const Fn &f): fn{f} {} + + OptData(const Fn &f, NLoptOpt *s, nlopt_opt nlopt_raw) + : fn{f}, self{s}, opt_raw{nlopt_raw} {} + }; template static double optfunc(unsigned n, const double *params, - double *gradient, - void *data) + double *gradient, void *data) { assert(n == N); - auto tdata = static_cast*>(data); + auto tdata = static_cast*>(data); - if (std::get<1>(*tdata)->m_stopcr.stop_condition()) - nlopt_force_stop(std::get<2>(*tdata)); + if (tdata->self->m_stopcr.stop_condition()) + nlopt_force_stop(tdata->opt_raw); - auto fnptr = std::get<0>(*tdata); auto funval = to_arr(params); double scoreval = 0.; - using RetT = decltype((*fnptr)(funval)); + using RetT = decltype(tdata->fn(funval)); if constexpr (std::is_convertible_v>) { - ScoreGradient score = (*fnptr)(funval); + ScoreGradient score = tdata->fn(funval); for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; scoreval = score.score; } else { - scoreval = (*fnptr)(funval); + scoreval = tdata->fn(funval); } return scoreval; @@ -127,17 +143,14 @@ protected: template static double constrain_func(unsigned n, const double *params, - double *gradient, - void *data) + double *gradient, void *data) { assert(n == N); - auto tdata = static_cast*>(data); - - auto &fnptr = std::get<0>(*tdata); + auto tdata = static_cast*>(data); auto funval = to_arr(params); - return (*fnptr)(funval); + return tdata->fn(funval); } template @@ -173,22 +186,27 @@ protected: { Result r; - TOptData data = std::make_tuple(&fn, this, nl.ptr); + OptData data {fn, this, nl.ptr}; - auto do_for_each_eq = [this, &nl](auto &&arg) { - auto data = std::make_tuple(&arg, this, nl.ptr); - using F = std::remove_cv_t; - nlopt_add_equality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + auto do_for_each_eq = [this, &nl](auto &arg) { + arg.self = this; + arg.opt_raw = nl.ptr; + using F = decltype(arg.fn); + nlopt_add_equality_constraint (nl.ptr, constrain_func, &arg, ConstraintEps); }; - auto do_for_each_ineq = [this, &nl](auto &&arg) { - auto data = std::make_tuple(&arg, this, nl.ptr); - using F = std::remove_cv_t; - nlopt_add_inequality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + auto do_for_each_ineq = [this, &nl](auto &arg) { + arg.self = this; + arg.opt_raw = nl.ptr; + using F = decltype(arg.fn); + nlopt_add_inequality_constraint (nl.ptr, constrain_func, &arg, ConstraintEps); }; - for_each_in_tuple(do_for_each_eq, equalities); - for_each_in_tuple(do_for_each_ineq, inequalities); + auto eq_data = wrap_tup(equalities); + for_each_in_tuple(do_for_each_eq, eq_data); + + auto ineq_data = wrap_tup(inequalities); + for_each_in_tuple(do_for_each_ineq, ineq_data); switch(m_dir) { case OptDir::MIN: @@ -260,8 +278,19 @@ public: const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } }; +template struct AlgFeatures_ { + static constexpr bool SupportsInequalities = false; + static constexpr bool SupportsEqualities = false; +}; + } // namespace detail; +template constexpr bool SupportsEqualities = + detail::AlgFeatures_>::SupportsEqualities; + +template constexpr bool SupportsInequalities = + detail::AlgFeatures_>::SupportsInequalities; + // Optimizers based on NLopt. template class Optimizer> { detail::NLoptOpt m_opt; @@ -278,6 +307,14 @@ public: const std::tuple &eq_constraints = {}, const std::tuple &ineq_constraint = {}) { + static_assert(std::tuple_size_v> == 0 + || SupportsEqualities, + "Equality constraints are not supported."); + + static_assert(std::tuple_size_v> == 0 + || SupportsInequalities, + "Inequality constraints are not supported."); + return m_opt.optimize(std::forward(func), initvals, bounds, eq_constraints, ineq_constraint); @@ -299,13 +336,41 @@ public: }; // Predefinded NLopt algorithms -using AlgNLoptGenetic = detail::NLoptAlgComb; -using AlgNLoptSubplex = detail::NLoptAlg; -using AlgNLoptSimplex = detail::NLoptAlg; -using AlgNLoptCobyla = detail::NLoptAlg; -using AlgNLoptDIRECT = detail::NLoptAlg; -using AlgNLoptISRES = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptGenetic = detail::NLoptAlgComb; +using AlgNLoptSubplex = detail::NLoptAlg; +using AlgNLoptSimplex = detail::NLoptAlg; +using AlgNLoptCobyla = detail::NLoptAlg; +using AlgNLoptDIRECT = detail::NLoptAlg; +using AlgNLoptORIG_DIRECT = detail::NLoptAlg; +using AlgNLoptISRES = detail::NLoptAlg; +using AlgNLoptAGS = detail::NLoptAlg; + +using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; + +namespace detail { + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = false; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = false; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + +} // namespace detail }} // namespace Slic3r::opt From 0f34dfbeac20dbb951341979175da38858ec4e91 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 29 Nov 2022 18:26:20 +0100 Subject: [PATCH 43/86] Trying 2 phase optimization for pillar route search --- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 130 ++++++++++++-------- tests/sla_print/sla_supptreeutils_tests.cpp | 4 +- 3 files changed, 80 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index ed5725780f..e0d7db97de 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -105,7 +105,7 @@ struct SupportTreeConfig static const double constexpr max_solo_pillar_height_mm = 15.0; static const double constexpr max_dual_pillar_height_mm = 35.0; static const double constexpr optimizer_rel_score_diff = 1e-10; - static const unsigned constexpr optimizer_max_iterations = 30000; + static const unsigned constexpr optimizer_max_iterations = 2000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 0dffd08527..94da648448 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -489,26 +489,30 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - // Score is the total lenght of the route. Feasible routes will have - // infinite length (rays not colliding with model), thus the stop score - // should be a reasonably big number. - constexpr double Penality = 1e5; - constexpr double PenOffs = 1e2; + const auto sd = sm.cfg.safety_distance(source.r); + const auto gndlvl = ground_level(sm); - const auto sd = sm.cfg.safety_distance(source.r); - const auto gndlvl = ground_level(sm); + auto criteria_heavy = get_criteria(sm.cfg); + criteria_heavy.max_iterations(30000); + criteria_heavy.abs_score_diff(NaNd); + criteria_heavy.rel_score_diff(NaNd); - auto criteria = get_criteria(sm.cfg); - criteria.abs_score_diff(1.); - criteria.rel_score_diff(0.1); - criteria.max_iterations(5000); + // Cobyla (local method) supports inequality constraints which will be + // needed here. + Optimizer solver_heavy(criteria_heavy); + solver_heavy.seed(0); - Optimizer solver(criteria); - solver.set_loc_criteria(criteria.max_iterations(100).abs_score_diff(1.)); - solver.seed(0); // enforce deterministic behavior + auto criteria_easy = get_criteria(sm.cfg); + criteria_easy.max_iterations(1000); + criteria_easy.abs_score_diff(NaNd); + criteria_easy.rel_score_diff(NaNd); + + Optimizer solver_easy(criteria_easy); + solver_easy.set_loc_criteria(StopCriteria{}.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); + solver_easy.seed(0); size_t icnt = 0; - auto optfn = [&](const opt::Input<3> &input) { + auto l_fn = [&](const opt::Input<3> &input) { ++icnt; double ret = NaNd; @@ -531,7 +535,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist + Penality; + ret = brhit_dist; } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -539,70 +543,90 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = std::min(gndhit.distance(), down_l); - double penality = 0.; + double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (!std::isinf(gndhit.distance())) - penality = Penality; - else if (sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); double base_r = std::max(sm.cfg.base_radius_mm, end_radius); double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - if (gap < min_gap) { - penality = Penality + PenOffs * (min_gap - gap); - } -// gnd_hit_d += std::max(0., min_gap - gap); //penality = Penality + 100000. * (min_gap - gap); -// if (gap < min_gap) { -// penality = Penality; -// } + gnd_hit_d = gnd_hit_d - min_gap + gap; } - ret = bridge_len + gnd_hit_d + penality; + ret = bridge_len + gnd_hit_d; } return ret; }; + auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { + // solver suggests polar, azimuth and bridge length values: + auto &[plr, azm, bridge_l] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = source.pos + bridge_l * n; + + double down_l = bridge_end.z() - gndlvl; + double full_l = bridge_l + down_l; + + return full_l; + }; + + auto ineq_fn = [&](const opt::Input<3> &input) { + double h = h_fn(input); + double l = l_fn(input); + double r = h - l; + + return r; + }; + auto [plr_init, azm_init] = dir_to_spheric(init_dir); // Saturate the polar angle to max tilt defined in config plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - - auto oresult = solver.to_min().optimize( - optfn, - initvals({plr_init, azm_init, 0.}), // start with a zero bridge + auto bound_constraints = bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle - {-PI, PI}, // bounds for azimuth - {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + + auto oresult_init = solver_easy.to_max().optimize( + l_fn, + initvals({plr_init, azm_init, 0.}), // start with a zero bridge + bound_constraints ); - std::cout << "iters: " << icnt << std::endl; + auto oresult = solver_heavy.to_min().optimize( + h_fn, + oresult_init.optimum, + bound_constraints, + {}, + std::make_tuple(ineq_fn) + ); + + std::cout << "Iterations: " << icnt << std::endl; GroundConnection conn; - if (oresult.score < Penality) { - // Extract and apply the result - auto &[plr, azm, bridge_len] = oresult.optimum; + // Extract and apply the result + auto &[plr, azm, bridge_l] = oresult.optimum; - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_len * n; - Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = source.pos + bridge_l * n; + Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); - double down_l = bridge_end.z() - gndlvl; - double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); - double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); + double down_l = bridge_end.z() - gndlvl; + double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - conn.path.emplace_back(source); - if (bridge_len > EPSILON) - conn.path.emplace_back(Junction{bridge_end, bridge_r}); + conn.path.emplace_back(source); + if (bridge_l > EPSILON) + conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (bridge_end.z() >= gndlvl) - conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - } + if (ineq_fn(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; return conn; } diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 58918db412..17bd17a032 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -180,7 +180,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") SECTION("with elevation") { sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN); eval_ground_conn(conn, sm, j, EndRadius, "disk.stl"); @@ -195,7 +195,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sm.cfg.object_elevation_mm = 0.; sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN); eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl"); From dfa6d03bedfb2df5590048793efb7e410231b31e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 30 Nov 2022 18:46:52 +0100 Subject: [PATCH 44/86] Add AUGLAG support for nlopt wrapper --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 164 +++++++++++++++------- 1 file changed, 110 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 900728804e..f360c67538 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -40,30 +40,70 @@ struct IsNLoptAlg> { static const constexpr bool value = true; }; +// NLopt can wrap any of its algorithms to use the augmented lagrangian method +// for deriving an object function from all equality and inequality constraints +// This way one can use algorithms that do not support these constraints natively +template struct NLoptAUGLAG {}; + +template +struct IsNLoptAlg>> { + static const constexpr bool value = true; +}; + +template struct IsNLoptAlg>> { + static const constexpr bool value = true; +}; + template using NLoptOnly = std::enable_if_t::value, T>; +template struct GetNLoptAlg_ { + static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS; + static constexpr nlopt_algorithm Global = NLOPT_NUM_ALGORITHMS; + static constexpr bool IsAUGLAG = false; +}; + +template struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS; + static constexpr nlopt_algorithm Global = a; + static constexpr bool IsAUGLAG = false; +}; + +template +struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = l; + static constexpr nlopt_algorithm Global = g; + static constexpr bool IsAUGLAG = false; +}; + +template constexpr nlopt_algorithm GetNLoptAlg_Global = GetNLoptAlg_>::Global; +template constexpr nlopt_algorithm GetNLoptAlg_Local = GetNLoptAlg_>::Local; +template constexpr bool IsAUGLAG = GetNLoptAlg_>::IsAUGLAG; + +template struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = GetNLoptAlg_Local; + static constexpr nlopt_algorithm Global = GetNLoptAlg_Global; + static constexpr bool IsAUGLAG = true; +}; enum class OptDir { MIN, MAX }; // Where to optimize -struct NLopt { // Helper RAII class for nlopt_opt +struct NLoptRAII { // Helper RAII class for nlopt_opt nlopt_opt ptr = nullptr; - template explicit NLopt(A&&...a) + template explicit NLoptRAII(A&&...a) { ptr = nlopt_create(std::forward(a)...); } - NLopt(const NLopt&) = delete; - NLopt(NLopt&&) = delete; - NLopt& operator=(const NLopt&) = delete; - NLopt& operator=(NLopt&&) = delete; + NLoptRAII(const NLoptRAII&) = delete; + NLoptRAII(NLoptRAII&&) = delete; + NLoptRAII& operator=(const NLoptRAII&) = delete; + NLoptRAII& operator=(NLoptRAII&&) = delete; - ~NLopt() { nlopt_destroy(ptr); } + ~NLoptRAII() { nlopt_destroy(ptr); } }; -template class NLoptOpt {}; - // Map a generic function to each argument following the mapping function template Fn for_each_argument(Fn &&fn, Args&&...args) @@ -96,10 +136,10 @@ auto wrap_tup(const std::tuple &tup) return std::tuple...>(tup); } -// Optimizers based on NLopt. -template class NLoptOpt> { -protected: +template> +class NLoptOpt { StopCriteria m_stopcr; + StopCriteria m_loc_stopcr; OptDir m_dir = OptDir::MIN; static constexpr double ConstraintEps = 1e-6; @@ -154,7 +194,7 @@ protected: } template - static void set_up(NLopt &nl, + static void set_up(NLoptRAII &nl, const Bounds &bounds, const StopCriteria &stopcr) { @@ -180,7 +220,7 @@ protected: } template - Result optimize(NLopt &nl, Fn &&fn, const Input &initvals, + Result optimize(NLoptRAII &nl, Fn &&fn, const Input &initvals, const std::tuple &equalities, const std::tuple &inequalities) { @@ -221,36 +261,6 @@ protected: return r; } -public: - - template - Result optimize(Func&& func, - const Input &initvals, - const Bounds& bounds, - const std::tuple &equalities, - const std::tuple &inequalities) - { - NLopt nl{alg, N}; - set_up(nl, bounds, m_stopcr); - - return optimize(nl, std::forward(func), initvals, - equalities, inequalities); - } - - explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {} - - void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } - const StopCriteria &get_criteria() const noexcept { return m_stopcr; } - void set_dir(OptDir dir) noexcept { m_dir = dir; } - - void seed(long s) { nlopt_srand(s); } -}; - -template -class NLoptOpt>: public NLoptOpt> -{ - using Base = NLoptOpt>; - StopCriteria m_loc_stopcr; public: template @@ -260,22 +270,59 @@ public: const std::tuple &equalities, const std::tuple &inequalities) { - NLopt nl_glob{glob, N}, nl_loc{loc, N}; + if constexpr (IsAUGLAG) { + NLoptRAII nl_wrap{NLOPT_AUGLAG, N}; + set_up(nl_wrap, bounds, get_criteria()); - Base::set_up(nl_glob, bounds, Base::get_criteria()); - Base::set_up(nl_loc, bounds, m_loc_stopcr); - nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + NLoptRAII nl_glob{GetNLoptAlg_Global, N}; + set_up(nl_glob, bounds, get_criteria()); + nlopt_set_local_optimizer(nl_wrap.ptr, nl_glob.ptr); - return Base::optimize(nl_glob, std::forward(f), initvals, - equalities, inequalities); + if constexpr (GetNLoptAlg_Local < NLOPT_NUM_ALGORITHMS) { + NLoptRAII nl_loc{GetNLoptAlg_Local, N}; + set_up(nl_loc, bounds, m_loc_stopcr); + nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + + return optimize(nl_wrap, std::forward(f), initvals, + equalities, inequalities); + } else { + return optimize(nl_wrap, std::forward(f), initvals, + equalities, inequalities); + } + } else { + NLoptRAII nl_glob{GetNLoptAlg_Global, N}; + set_up(nl_glob, bounds, get_criteria()); + + if constexpr (GetNLoptAlg_Local < NLOPT_NUM_ALGORITHMS) { + NLoptRAII nl_loc{GetNLoptAlg_Local, N}; + set_up(nl_loc, bounds, m_loc_stopcr); + nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + + return optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); + } else { + return optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); + } + } + + assert(false); + + return {}; } - explicit NLoptOpt(StopCriteria stopcr = {}) - : Base{stopcr}, m_loc_stopcr{stopcr} + explicit NLoptOpt(const StopCriteria &stopcr_glob = {}) + : m_stopcr(stopcr_glob) {} + void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } + const StopCriteria &get_criteria() const noexcept { return m_stopcr; } + void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; } const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } + + void set_dir(OptDir dir) noexcept { m_dir = dir; } + void seed(long s) { nlopt_srand(s); } }; template struct AlgFeatures_ { @@ -345,8 +392,12 @@ using AlgNLoptORIG_DIRECT = detail::NLoptAlg; using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptAGS = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; -using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; +using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; +using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb; + +// To craft auglag algorithms (constraint support through object function transformation) +using detail::NLoptAUGLAG; namespace detail { @@ -370,6 +421,11 @@ template<> struct AlgFeatures_ { static constexpr bool SupportsEqualities = true; }; +template struct AlgFeatures_> { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + } // namespace detail }} // namespace Slic3r::opt From 02b06f0107ea24a09e6856f440bced42c249687d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 30 Nov 2022 18:47:24 +0100 Subject: [PATCH 45/86] try 2 phase optimization with auglag and inequalities --- src/libslic3r/SLA/SupportTreeUtils.hpp | 366 ++++++++++++++++++-- tests/sla_print/sla_supptreeutils_tests.cpp | 10 +- 2 files changed, 343 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 94da648448..b0e76682f8 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -492,24 +492,26 @@ GroundConnection deepsearch_ground_connection( const auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); - auto criteria_heavy = get_criteria(sm.cfg); - criteria_heavy.max_iterations(30000); - criteria_heavy.abs_score_diff(NaNd); - criteria_heavy.rel_score_diff(NaNd); + auto criteria = get_criteria(sm.cfg); + criteria.max_iterations(2000); + criteria.abs_score_diff(NaNd); + criteria.rel_score_diff(NaNd); + + auto criteria_loc = criteria; + criteria_loc.max_iterations(100); + criteria_loc.abs_score_diff(EPSILON); + criteria_loc.rel_score_diff(0.05); // Cobyla (local method) supports inequality constraints which will be // needed here. - Optimizer solver_heavy(criteria_heavy); - solver_heavy.seed(0); + Optimizer> solver(criteria); + solver.set_loc_criteria(criteria_loc); + solver.seed(0); - auto criteria_easy = get_criteria(sm.cfg); - criteria_easy.max_iterations(1000); - criteria_easy.abs_score_diff(NaNd); - criteria_easy.rel_score_diff(NaNd); - - Optimizer solver_easy(criteria_easy); - solver_easy.set_loc_criteria(StopCriteria{}.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); - solver_easy.seed(0); + constexpr double Cap = 1e6; + Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); + solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); + solver_initial.seed(0); size_t icnt = 0; auto l_fn = [&](const opt::Input<3> &input) { @@ -543,20 +545,27 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); + double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); double base_r = std::max(sm.cfg.base_radius_mm, end_radius); double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - gnd_hit_d = gnd_hit_d - min_gap + gap; + + if (gap < min_gap) { + gnd_hit_d = down_l - min_gap + gap; + } } ret = bridge_len + gnd_hit_d; } + if (std::isinf(ret)) { + ret = Cap + EPSILON; + } + return ret; }; @@ -578,7 +587,16 @@ GroundConnection deepsearch_ground_connection( double l = l_fn(input); double r = h - l; - return r; + return r; // <= 0 + }; + + auto ineq_fn_gnd = [&](const opt::Input<3> &input) { + auto &[plr, azm, bridge_l] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = source.pos + bridge_l * n; + + return gndlvl - bridge_end.z(); // <= 0 }; auto [plr_init, azm_init] = dir_to_spheric(init_dir); @@ -587,22 +605,24 @@ GroundConnection deepsearch_ground_connection( plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); auto bound_constraints = bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle - {-PI, PI}, // bounds for azimuth - {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - auto oresult_init = solver_easy.to_max().optimize( + auto oresult_init = solver_initial.to_max().optimize( l_fn, - initvals({plr_init, azm_init, 0.}), // start with a zero bridge - bound_constraints - ); + initvals({plr_init, azm_init, 0.}), + bound_constraints/*, + {}, + std::make_tuple(ineq_fn_gnd)*/ + ); - auto oresult = solver_heavy.to_min().optimize( + auto oresult = solver.to_min().optimize( h_fn, oresult_init.optimum, bound_constraints, {}, - std::make_tuple(ineq_fn) - ); + std::make_tuple(ineq_fn, ineq_fn_gnd) + ); std::cout << "Iterations: " << icnt << std::endl; @@ -624,13 +644,303 @@ GroundConnection deepsearch_ground_connection( if (bridge_l > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (ineq_fn(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) + if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; return conn; } +//template> > +//GroundConnection deepsearch_ground_connection( +// Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// WideningFn &&wideningfn, +// const Vec3d &init_dir = DOWN) +//{ +// const auto sd = sm.cfg.safety_distance(source.r); +// const auto gndlvl = ground_level(sm); + +// auto criteria_heavy = get_criteria(sm.cfg); +// criteria_heavy.max_iterations(10000); +// criteria_heavy.abs_score_diff(NaNd); +// criteria_heavy.rel_score_diff(NaNd); + +// // Cobyla (local method) supports inequality constraints which will be +// // needed here. +// Optimizer solver_heavy(criteria_heavy); +// solver_heavy.seed(0); + +// // Score is the total lenght of the route. Feasible routes will have +// // infinite length (rays not colliding with model), thus the stop score +// // should be a reasonably big number. +// constexpr double StopScore = 1e6; + +// auto criteria_easy = get_criteria(sm.cfg); +// criteria_easy.max_iterations(1000); +// criteria_easy.abs_score_diff(NaNd); +// criteria_easy.rel_score_diff(NaNd); +// criteria_easy.stop_score(StopScore); + +// Optimizer solver_easy(criteria_easy); +// solver_easy.set_loc_criteria(criteria_easy.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); +// solver_easy.seed(0); + +// size_t icnt = 0; +// auto optfn = [&](const opt::Input<3> &input) { +// ++icnt; + +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + +// if (std::isinf(gndhit.distance())) { +// // Ground route is free with this bridge + +// if (sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; +// if (gap < max_gap) +// ret = full_len - max_gap + gap; +// else // success +// ret = StopScore + EPSILON; +// } else { +// // No zero elevation, return success +// ret = StopScore + EPSILON; +// } +// } else { +// // Ground route is not free +// ret = bridge_len + gndhit.distance(); +// } +// } + +// return ret; +// }; + +// auto l_fn = [&](const opt::Input<3> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); +// double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); + +// if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; +// gnd_hit_d = gnd_hit_d - min_gap + gap; +// } + +// ret = bridge_len + gnd_hit_d; +// } + +// return ret; +// }; + +// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn = [&](const opt::Input<3> &input) { +// double h = h_fn(input); +// double l = l_fn(input); +// double r = h - l; + +// return r; +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); +// auto bound_constraints = +// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle +// {-PI, PI}, // bounds for azimuth +// {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + +// auto oresult_init = solver_easy.to_max().optimize( +// optfn, +// initvals({plr_init, azm_init, 0.}), // start with a zero bridge +// bound_constraints +// ); + +// auto l_fn_len = [&](const opt::Input<1> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &bridge_len = input[0]; +// auto &[plr, azm, _] = oresult_init.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); +// double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); + +// if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; +// gnd_hit_d = gnd_hit_d - min_gap + gap; +// } + +// ret = bridge_len + gnd_hit_d; +// } + +// return ret; +// }; + +// auto h_fn_len = [&source, gndlvl, &oresult_init](const opt::Input<1> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &bridge_l = input[0]; +// auto &[plr, azm, _] = oresult_init.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn_len = [&](const opt::Input<1> &input) { +// double h = h_fn_len(input); +// double l = l_fn_len(input); +// double r = h - l; + +// return r; +// }; + +// auto oresult = solver_heavy.to_min().optimize( +// h_fn_len, +// opt::Input<1>({oresult_init.optimum[2]}), +// {bound_constraints[2]}, +// {}, +// std::make_tuple(ineq_fn_len) +// ); + +// std::cout << "Iterations: " << icnt << std::endl; + +// GroundConnection conn; + +// // Extract and apply the result +//// auto &[plr, azm, bridge_l] = oresult.optimum; +// double plr = oresult_init.optimum[0]; +// double azm = oresult_init.optimum[1]; +// double bridge_l = oresult.optimum[0]; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; +// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); +// double down_l = bridge_end.z() - gndlvl; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + +// conn.path.emplace_back(source); +// if (bridge_l > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// if (ineq_fn_len(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + +// return conn; +//} + template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 17bd17a032..c8abbb8315 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -74,7 +74,7 @@ static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn, { using namespace Slic3r; -#ifndef NDEBUG +//#ifndef NDEBUG sla::SupportTreeBuilder builder; @@ -87,7 +87,7 @@ static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn, its_merge(mesh, builder.merged_mesh()); its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh); -#endif +//#endif REQUIRE(bool(conn)); @@ -118,7 +118,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); } @@ -133,7 +133,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); REQUIRE(conn.pillar_base->r_top == Approx(0.)); } @@ -149,7 +149,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); } } From 5e34bbcbe597105066b13ddb5f1b3a3d56b265c3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 14:02:48 +0100 Subject: [PATCH 46/86] try z level optimization with post processing --- src/libslic3r/SLA/SupportTreeUtils.hpp | 274 +++++++++++++++++++------ 1 file changed, 210 insertions(+), 64 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index b0e76682f8..bc6f869192 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -489,32 +489,31 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - const auto sd = sm.cfg.safety_distance(source.r); + auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); auto criteria = get_criteria(sm.cfg); - criteria.max_iterations(2000); + criteria.max_iterations(5000); criteria.abs_score_diff(NaNd); criteria.rel_score_diff(NaNd); + criteria.stop_score(gndlvl); auto criteria_loc = criteria; criteria_loc.max_iterations(100); criteria_loc.abs_score_diff(EPSILON); criteria_loc.rel_score_diff(0.05); - // Cobyla (local method) supports inequality constraints which will be - // needed here. - Optimizer> solver(criteria); + Optimizer solver(criteria); solver.set_loc_criteria(criteria_loc); solver.seed(0); - constexpr double Cap = 1e6; - Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); - solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); - solver_initial.seed(0); - + // functor returns the z height of collision point, given a polar and + // azimuth angles as bridge direction and bridge length. The route is + // traced from source, throught this bridge and an attached pillar. If there + // is a collision with the mesh, the Z height is returned. Otherwise the + // z level of ground is returned. size_t icnt = 0; - auto l_fn = [&](const opt::Input<3> &input) { + auto z_fn = [&](const opt::Input<3> &input) { ++icnt; double ret = NaNd; @@ -537,7 +536,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist; + ret = (source.pos + brhit_dist * n).z(); } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -545,9 +544,9 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); + double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); @@ -559,46 +558,12 @@ GroundConnection deepsearch_ground_connection( } } - ret = bridge_len + gnd_hit_d; - } - - if (std::isinf(ret)) { - ret = Cap + EPSILON; + ret = bridge_end.z() - gnd_hit_d; } return ret; }; - auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { - // solver suggests polar, azimuth and bridge length values: - auto &[plr, azm, bridge_l] = input; - - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_l * n; - - double down_l = bridge_end.z() - gndlvl; - double full_l = bridge_l + down_l; - - return full_l; - }; - - auto ineq_fn = [&](const opt::Input<3> &input) { - double h = h_fn(input); - double l = l_fn(input); - double r = h - l; - - return r; // <= 0 - }; - - auto ineq_fn_gnd = [&](const opt::Input<3> &input) { - auto &[plr, azm, bridge_l] = input; - - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_l * n; - - return gndlvl - bridge_end.z(); // <= 0 - }; - auto [plr_init, azm_init] = dir_to_spheric(init_dir); // Saturate the polar angle to max tilt defined in config @@ -608,20 +573,15 @@ GroundConnection deepsearch_ground_connection( {-PI, PI}, // bounds for azimuth {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - auto oresult_init = solver_initial.to_max().optimize( - l_fn, - initvals({plr_init, azm_init, 0.}), - bound_constraints/*, - {}, - std::make_tuple(ineq_fn_gnd)*/ - ); - + // The optimizer can navigate fairly well on the mesh surface, finding + // lower and lower Z coordinates as collision points. MLSL is not a local + // search method, so it should not be trapped in a local minima. Eventually, + // this search should arrive at a ground location, like water flows down a + // surface. auto oresult = solver.to_min().optimize( - h_fn, - oresult_init.optimum, - bound_constraints, - {}, - std::make_tuple(ineq_fn, ineq_fn_gnd) + z_fn, + initvals({plr_init, azm_init, 0.}), + bound_constraints ); std::cout << "Iterations: " << icnt << std::endl; @@ -629,7 +589,22 @@ GroundConnection deepsearch_ground_connection( GroundConnection conn; // Extract and apply the result - auto &[plr, azm, bridge_l] = oresult.optimum; + auto [plr, azm, bridge_l] = oresult.optimum; + + // Now that the optimizer gave a possible route to ground with a bridge + // direction and lenght. This lenght can be shortened further by + // brute-force queries of free route straigt down for a possible pillar. + // NOTE: This requirement could be added to the optimization, but it would + // not find quickly enough an accurate solution. + double l = 0.; + double zlvl = std::numeric_limits::infinity(); + while(zlvl > gndlvl && l < sm.cfg.max_bridge_length_mm) { + zlvl = z_fn({plr, azm, l}); + if (zlvl <= gndlvl) + bridge_l = l; + + l += source.r; + } Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_l * n; @@ -644,7 +619,7 @@ GroundConnection deepsearch_ground_connection( if (bridge_l > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) + if (z_fn(opt::Input<3>({plr, azm, bridge_l})) <= gndlvl) conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; @@ -663,6 +638,177 @@ GroundConnection deepsearch_ground_connection( // const auto sd = sm.cfg.safety_distance(source.r); // const auto gndlvl = ground_level(sm); +// auto criteria = get_criteria(sm.cfg); +// criteria.max_iterations(2000); +// criteria.abs_score_diff(NaNd); +// criteria.rel_score_diff(NaNd); + +// auto criteria_loc = criteria; +// criteria_loc.max_iterations(100); +// criteria_loc.abs_score_diff(EPSILON); +// criteria_loc.rel_score_diff(0.05); + +// // Cobyla (local method) supports inequality constraints which will be +// // needed here. +// Optimizer> solver(criteria); +// solver.set_loc_criteria(criteria_loc); +// solver.seed(0); + +// constexpr double Cap = 1e6; +// Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); +// solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); +// solver_initial.seed(0); + +// size_t icnt = 0; +// auto l_fn = [&](const opt::Input<3> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); +// double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); + +// if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { +// // Dealing with zero elevation mode, to not route pillars +// // into the gap between the optional pad and the model +// double gap = std::sqrt(sm.emesh.squared_distance(gp)); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + +// if (gap < min_gap) { +// gnd_hit_d = down_l - min_gap + gap; +// } +// } + +// ret = bridge_len + gnd_hit_d; +// } + +// if (std::isinf(ret)) { +// ret = Cap + EPSILON; +// } + +// return ret; +// }; + +// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn = [&](const opt::Input<3> &input) { +// double h = h_fn(input); +// double l = l_fn(input); +// double r = h - l; + +// return r; // <= 0 +// }; + +// auto ineq_fn_gnd = [&](const opt::Input<3> &input) { +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// return gndlvl - bridge_end.z(); // <= 0 +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); +// auto bound_constraints = +// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle +// {-PI, PI}, // bounds for azimuth +// {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + +// auto oresult_init = solver_initial.to_max().optimize( +// l_fn, +// initvals({plr_init, azm_init, 0.}), +// bound_constraints/*, +// {}, +// std::make_tuple(ineq_fn_gnd)*/ +// ); + +// auto oresult = solver.to_min().optimize( +// h_fn, +// oresult_init.optimum, +// bound_constraints, +// {}, +// std::make_tuple(ineq_fn, ineq_fn_gnd) +// ); + +// std::cout << "Iterations: " << icnt << std::endl; + +// GroundConnection conn; + +// // Extract and apply the result +// auto &[plr, azm, bridge_l] = oresult.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; +// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); +// double down_l = bridge_end.z() - gndlvl; +// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); +// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + +// conn.path.emplace_back(source); +// if (bridge_l > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + +// return conn; +//} + +//template> > +//GroundConnection deepsearch_ground_connection( +// Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// WideningFn &&wideningfn, +// const Vec3d &init_dir = DOWN) +//{ +// const auto sd = sm.cfg.safety_distance(source.r); +// const auto gndlvl = ground_level(sm); + // auto criteria_heavy = get_criteria(sm.cfg); // criteria_heavy.max_iterations(10000); // criteria_heavy.abs_score_diff(NaNd); From 3d81800d152e4dcdea8f0344346ff774ded82b3d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:17:15 +0100 Subject: [PATCH 47/86] Improve code --- src/libslic3r/SLA/SupportTreeUtils.hpp | 642 +++++-------------------- 1 file changed, 110 insertions(+), 532 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index bc6f869192..13586d9375 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -110,31 +110,34 @@ inline StopCriteria get_criteria(const SupportTreeConfig &cfg) // A simple sphere with a center and a radius struct Ball { Vec3d p; double R; }; -struct Beam { // Defines a set of rays displaced along a cone's surface - static constexpr size_t SAMPLES = 8; +template +struct Beam_ { // Defines a set of rays displaced along a cone's surface + static constexpr size_t SAMPLES = Samples; - Vec3d src; - Vec3d dir; + Vec3d src; + Vec3d dir; double r1; double r2; // radius of the beam 1 unit further from src in dir direction - Beam(const Vec3d &s, const Vec3d &d, double R1, double R2): - src{s}, dir{d}, r1{R1}, r2{R2} {}; + Beam_(const Vec3d &s, const Vec3d &d, double R1, double R2) + : src{s}, dir{d}, r1{R1}, r2{R2} {}; - Beam(const Ball &src_ball, const Ball &dst_ball): - src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R} + Beam_(const Ball &src_ball, const Ball &dst_ball) + : src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R} { - r2 = src_ball.R + - (dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p); + r2 = src_ball.R + + (dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p); } - Beam(const Vec3d &s, const Vec3d &d, double R) + Beam_(const Vec3d &s, const Vec3d &d, double R) : src{s}, dir{d}, r1{R}, r2{R} {} }; -template -Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) +using Beam = Beam_<8>; + +template +Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) { Vec3d src = beam.src; Vec3d dst = src + beam.dir; @@ -143,12 +146,12 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) Vec3d D = (dst - src); Vec3d dir = D.normalized(); - PointRing ring{dir}; + PointRing ring{dir}; using Hit = AABBMesh::hit_result; // Hit results - std::array hits; + std::array hits; execution::for_each( ex, size_t(0), hits.size(), @@ -172,7 +175,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) } } else hit = hr; - }, std::min(execution::max_concurrency(ex), Beam::SAMPLES)); + }, std::min(execution::max_concurrency(ex), S)); return min_hit(hits.begin(), hits.end()); } @@ -480,6 +483,79 @@ constexpr bool IsWideningFn = std::is_invocable_r_v; +template struct BeamSamples { static constexpr size_t Value = 8; }; +template constexpr size_t BeamSamplesV = BeamSamples>::Value; + + +enum class GroundRouteCheck { Full, PillarOnly }; + +template> > +Vec3d check_ground_route( + Ex policy, + const SupportableMesh &sm, + const Junction &source, + const Vec3d &dir, + double bridge_len, + WideningFn &&wideningfn, + GroundRouteCheck type = GroundRouteCheck::Full + ) +{ + static const constexpr auto Samples = BeamSamplesV; + + Vec3d ret; + + const auto sd = sm.cfg.safety_distance(source.r); + const auto gndlvl = ground_level(sm); + + Vec3d bridge_end = source.pos + bridge_len * dir; + + double down_l = bridge_end.z() - gndlvl; + double bridge_r = wideningfn(Ball{source.pos, source.r}, dir, bridge_len); + double brhit_dist = 0.; + + if (bridge_len > EPSILON && type == GroundRouteCheck::Full) { + // beam_mesh_hit with a zero lenght bridge is invalid + + Beam_ bridgebeam{Ball{source.pos, source.r}, + Ball{bridge_end, bridge_r}}; + + auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); + brhit_dist = brhit.distance(); + } else { + brhit_dist = bridge_len; + } + + if (brhit_dist < bridge_len) { + ret = (source.pos + brhit_dist * dir); + } else { + // check if pillar can be placed below + auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + double end_radius = wideningfn( + Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); + + Beam_ gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; + auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); + + if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { + // Dealing with zero elevation mode, to not route pillars + // into the gap between the optional pad and the model + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + + if (gap < min_gap) { + gnd_hit_d = down_l - min_gap + gap; + } + } + + ret = Vec3d{bridge_end.x(), bridge_end.y(), bridge_end.z() - gnd_hit_d}; + } + + return ret; +} + template> > GroundConnection deepsearch_ground_connection( @@ -489,7 +565,6 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); auto criteria = get_criteria(sm.cfg); @@ -512,56 +587,15 @@ GroundConnection deepsearch_ground_connection( // traced from source, throught this bridge and an attached pillar. If there // is a collision with the mesh, the Z height is returned. Otherwise the // z level of ground is returned. - size_t icnt = 0; auto z_fn = [&](const opt::Input<3> &input) { - ++icnt; - double ret = NaNd; - // solver suggests polar, azimuth and bridge length values: auto &[plr, azm, bridge_len] = input; Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_len * n; - double down_l = bridge_end.z() - gndlvl; - double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); - double brhit_dist = 0.; + Vec3d hitpt = check_ground_route(policy, sm, source, n, bridge_len, wideningfn); - if (bridge_len > EPSILON) { - // beam_mesh_hit with a zero lenght bridge is invalid - - Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; - auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); - brhit_dist = brhit.distance(); - } - - if (brhit_dist < bridge_len) { - ret = (source.pos + brhit_dist * n).z(); - } else { - // check if pillar can be placed below - auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; - double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - - Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; - auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - - if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { - // Dealing with zero elevation mode, to not route pillars - // into the gap between the optional pad and the model - double gap = std::sqrt(sm.emesh.squared_distance(gp)); - double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - - if (gap < min_gap) { - gnd_hit_d = down_l - min_gap + gap; - } - } - - ret = bridge_end.z() - gnd_hit_d; - } - - return ret; + return hitpt.z(); }; auto [plr_init, azm_init] = dir_to_spheric(init_dir); @@ -584,29 +618,30 @@ GroundConnection deepsearch_ground_connection( bound_constraints ); - std::cout << "Iterations: " << icnt << std::endl; - GroundConnection conn; // Extract and apply the result auto [plr, azm, bridge_l] = oresult.optimum; + Vec3d n = spheric_to_dir(plr, azm); - // Now that the optimizer gave a possible route to ground with a bridge - // direction and lenght. This lenght can be shortened further by - // brute-force queries of free route straigt down for a possible pillar. - // NOTE: This requirement could be added to the optimization, but it would - // not find quickly enough an accurate solution. - double l = 0.; + // Now the optimizer gave a possible route to ground with a bridge direction + // and length. This length can be shortened further by brute-force queries + // of free route straigt down for a possible pillar. + // NOTE: This requirement could be incorporated into the optimization as a + // constraint, but it would not find quickly enough an accurate solution. + double l = 0., l_max = bridge_l; double zlvl = std::numeric_limits::infinity(); - while(zlvl > gndlvl && l < sm.cfg.max_bridge_length_mm) { - zlvl = z_fn({plr, azm, l}); + while(zlvl > gndlvl && l <= l_max) { + + zlvl = check_ground_route(policy, sm, source, n, l, wideningfn, + GroundRouteCheck::PillarOnly).z(); + if (zlvl <= gndlvl) bridge_l = l; l += source.r; } - Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_l * n; Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -626,467 +661,6 @@ GroundConnection deepsearch_ground_connection( return conn; } -//template> > -//GroundConnection deepsearch_ground_connection( -// Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// WideningFn &&wideningfn, -// const Vec3d &init_dir = DOWN) -//{ -// const auto sd = sm.cfg.safety_distance(source.r); -// const auto gndlvl = ground_level(sm); - -// auto criteria = get_criteria(sm.cfg); -// criteria.max_iterations(2000); -// criteria.abs_score_diff(NaNd); -// criteria.rel_score_diff(NaNd); - -// auto criteria_loc = criteria; -// criteria_loc.max_iterations(100); -// criteria_loc.abs_score_diff(EPSILON); -// criteria_loc.rel_score_diff(0.05); - -// // Cobyla (local method) supports inequality constraints which will be -// // needed here. -// Optimizer> solver(criteria); -// solver.set_loc_criteria(criteria_loc); -// solver.seed(0); - -// constexpr double Cap = 1e6; -// Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); -// solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); -// solver_initial.seed(0); - -// size_t icnt = 0; -// auto l_fn = [&](const opt::Input<3> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); -// double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); - -// if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - -// if (gap < min_gap) { -// gnd_hit_d = down_l - min_gap + gap; -// } -// } - -// ret = bridge_len + gnd_hit_d; -// } - -// if (std::isinf(ret)) { -// ret = Cap + EPSILON; -// } - -// return ret; -// }; - -// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn = [&](const opt::Input<3> &input) { -// double h = h_fn(input); -// double l = l_fn(input); -// double r = h - l; - -// return r; // <= 0 -// }; - -// auto ineq_fn_gnd = [&](const opt::Input<3> &input) { -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// return gndlvl - bridge_end.z(); // <= 0 -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); -// auto bound_constraints = -// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle -// {-PI, PI}, // bounds for azimuth -// {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - -// auto oresult_init = solver_initial.to_max().optimize( -// l_fn, -// initvals({plr_init, azm_init, 0.}), -// bound_constraints/*, -// {}, -// std::make_tuple(ineq_fn_gnd)*/ -// ); - -// auto oresult = solver.to_min().optimize( -// h_fn, -// oresult_init.optimum, -// bound_constraints, -// {}, -// std::make_tuple(ineq_fn, ineq_fn_gnd) -// ); - -// std::cout << "Iterations: " << icnt << std::endl; - -// GroundConnection conn; - -// // Extract and apply the result -// auto &[plr, azm, bridge_l] = oresult.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; -// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); -// double down_l = bridge_end.z() - gndlvl; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - -// conn.path.emplace_back(source); -// if (bridge_l > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - -// return conn; -//} - -//template> > -//GroundConnection deepsearch_ground_connection( -// Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// WideningFn &&wideningfn, -// const Vec3d &init_dir = DOWN) -//{ -// const auto sd = sm.cfg.safety_distance(source.r); -// const auto gndlvl = ground_level(sm); - -// auto criteria_heavy = get_criteria(sm.cfg); -// criteria_heavy.max_iterations(10000); -// criteria_heavy.abs_score_diff(NaNd); -// criteria_heavy.rel_score_diff(NaNd); - -// // Cobyla (local method) supports inequality constraints which will be -// // needed here. -// Optimizer solver_heavy(criteria_heavy); -// solver_heavy.seed(0); - -// // Score is the total lenght of the route. Feasible routes will have -// // infinite length (rays not colliding with model), thus the stop score -// // should be a reasonably big number. -// constexpr double StopScore = 1e6; - -// auto criteria_easy = get_criteria(sm.cfg); -// criteria_easy.max_iterations(1000); -// criteria_easy.abs_score_diff(NaNd); -// criteria_easy.rel_score_diff(NaNd); -// criteria_easy.stop_score(StopScore); - -// Optimizer solver_easy(criteria_easy); -// solver_easy.set_loc_criteria(criteria_easy.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); -// solver_easy.seed(0); - -// size_t icnt = 0; -// auto optfn = [&](const opt::Input<3> &input) { -// ++icnt; - -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - -// if (std::isinf(gndhit.distance())) { -// // Ground route is free with this bridge - -// if (sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; -// if (gap < max_gap) -// ret = full_len - max_gap + gap; -// else // success -// ret = StopScore + EPSILON; -// } else { -// // No zero elevation, return success -// ret = StopScore + EPSILON; -// } -// } else { -// // Ground route is not free -// ret = bridge_len + gndhit.distance(); -// } -// } - -// return ret; -// }; - -// auto l_fn = [&](const opt::Input<3> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); -// double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - -// if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; -// gnd_hit_d = gnd_hit_d - min_gap + gap; -// } - -// ret = bridge_len + gnd_hit_d; -// } - -// return ret; -// }; - -// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn = [&](const opt::Input<3> &input) { -// double h = h_fn(input); -// double l = l_fn(input); -// double r = h - l; - -// return r; -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); -// auto bound_constraints = -// bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle -// {-PI, PI}, // bounds for azimuth -// {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - -// auto oresult_init = solver_easy.to_max().optimize( -// optfn, -// initvals({plr_init, azm_init, 0.}), // start with a zero bridge -// bound_constraints -// ); - -// auto l_fn_len = [&](const opt::Input<1> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &bridge_len = input[0]; -// auto &[plr, azm, _] = oresult_init.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{source.pos, source.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl); - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); -// double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - -// if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { -// // Dealing with zero elevation mode, to not route pillars -// // into the gap between the optional pad and the model -// double gap = std::sqrt(sm.emesh.squared_distance(gp)); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; -// gnd_hit_d = gnd_hit_d - min_gap + gap; -// } - -// ret = bridge_len + gnd_hit_d; -// } - -// return ret; -// }; - -// auto h_fn_len = [&source, gndlvl, &oresult_init](const opt::Input<1> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &bridge_l = input[0]; -// auto &[plr, azm, _] = oresult_init.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn_len = [&](const opt::Input<1> &input) { -// double h = h_fn_len(input); -// double l = l_fn_len(input); -// double r = h - l; - -// return r; -// }; - -// auto oresult = solver_heavy.to_min().optimize( -// h_fn_len, -// opt::Input<1>({oresult_init.optimum[2]}), -// {bound_constraints[2]}, -// {}, -// std::make_tuple(ineq_fn_len) -// ); - -// std::cout << "Iterations: " << icnt << std::endl; - -// GroundConnection conn; - -// // Extract and apply the result -//// auto &[plr, azm, bridge_l] = oresult.optimum; -// double plr = oresult_init.optimum[0]; -// double azm = oresult_init.optimum[1]; -// double bridge_l = oresult.optimum[0]; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; -// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l); -// double down_l = bridge_end.z() - gndlvl; -// double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); -// double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - -// conn.path.emplace_back(source); -// if (bridge_l > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// if (ineq_fn_len(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - -// return conn; -//} - template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, @@ -1123,6 +697,10 @@ struct DefaultWideningModel { }; }; +template<> struct BeamSamples { + static constexpr size_t Value = 16; +}; + template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, From 60b59d08b9717c90cb455e11532cceba706e3b81 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:34:52 +0100 Subject: [PATCH 48/86] Disable subtree rescure when discarding subtrees It generates many abandoned single pillars --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 1ad0fd93f6..4230e38bb4 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -70,6 +70,22 @@ class BranchingTreeBuilder: public branchingtree::Builder { } void discard_subtree(size_t root) + { + // Discard all the support points connecting to this branch. + traverse(m_cloud, root, [this](const branchingtree::Node &node) { + int suppid_parent = m_cloud.get_leaf_id(node.id); + int suppid_left = m_cloud.get_leaf_id(node.left); + int suppid_right = m_cloud.get_leaf_id(node.right); + if (suppid_parent >= 0) + m_unroutable_pinheads.emplace_back(suppid_parent); + if (suppid_left >= 0) + m_unroutable_pinheads.emplace_back(suppid_left); + if (suppid_right >= 0) + m_unroutable_pinheads.emplace_back(suppid_right); + }); + } + + void discard_subtree_rescure(size_t root) { // Discard all the support points connecting to this branch. // As a last resort, try to route child nodes to ground and stop From dfea5e5633c6216b3241614110ff4e915063584a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:40:52 +0100 Subject: [PATCH 49/86] prepare new test data --- tests/data/disk_with_hole.obj | 1696 +++++++++++++++++++++++++++++++++ 1 file changed, 1696 insertions(+) create mode 100644 tests/data/disk_with_hole.obj diff --git a/tests/data/disk_with_hole.obj b/tests/data/disk_with_hole.obj new file mode 100644 index 0000000000..f16cafb87a --- /dev/null +++ b/tests/data/disk_with_hole.obj @@ -0,0 +1,1696 @@ +#### +# +# OBJ File Generated by Meshlab +# +#### +# Object disk_with_hole.obj +# +# Vertices: 720 +# Faces: 240 +# +#### +vn -0.328454 -0.737720 0.000000 +v -25.000000 -43.301270 -5.000000 +vn -0.310446 -0.697273 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.638901 -1.434994 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.328454 -0.737720 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.310446 -0.697273 0.000000 +v -25.000000 -43.301270 -5.000000 +vn -0.638901 -1.434994 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.725904 -0.235860 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -0.768012 -0.249542 0.000000 +v -48.907379 -10.395584 5.000000 +vn -1.493916 -0.485403 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -0.725904 -0.235860 0.000000 +v -48.907379 -10.395584 5.000000 +vn -0.768012 -0.249542 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -1.493916 -0.485403 0.000000 +v -45.677273 -20.336832 5.000000 +vn 0.725904 0.235860 0.000000 +v 48.907379 10.395584 5.000000 +vn 0.768012 0.249542 0.000000 +v 45.677273 20.336832 -5.000000 +vn 1.493916 0.485403 0.000000 +v 45.677273 20.336832 5.000000 +vn 0.725904 0.235860 0.000000 +v 45.677273 20.336832 -5.000000 +vn 0.768012 0.249542 0.000000 +v 48.907379 10.395584 5.000000 +vn 1.493916 0.485403 0.000000 +v 48.907379 10.395584 -5.000000 +vn 0.759080 0.079783 0.000000 +v 50.000000 0.000000 5.000000 +vn 0.803112 0.084411 0.000000 +v 48.907379 10.395584 -5.000000 +vn 1.562191 0.164193 0.000000 +v 48.907379 10.395584 5.000000 +vn 0.759080 0.079783 0.000000 +v 48.907379 10.395584 -5.000000 +vn 0.803112 0.084411 0.000000 +v 50.000000 0.000000 5.000000 +vn 1.562191 0.164193 0.000000 +v 50.000000 0.000000 -5.000000 +vn 0.661003 0.381630 0.000000 +v 45.677273 20.336832 5.000000 +vn 0.699346 0.403768 0.000000 +v 40.450851 29.389263 -5.000000 +vn 1.360350 0.785398 0.000000 +v 40.450851 29.389263 5.000000 +vn 0.661003 0.381630 0.000000 +v 40.450851 29.389263 -5.000000 +vn 0.699346 0.403768 0.000000 +v 45.677273 20.336832 5.000000 +vn 1.360350 0.785398 0.000000 +v 45.677273 20.336832 -5.000000 +vn 0.474657 -0.653310 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.448633 -0.617491 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.923291 -1.270801 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.474657 -0.653310 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.448633 -0.617491 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.923291 -1.270801 0.000000 +v 33.456528 -37.157242 -5.000000 +vn 0.328454 0.737720 0.000000 +v 25.000000 43.301270 -5.000000 +vn 0.310446 0.697273 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.638901 1.434994 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.328454 0.737720 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.310446 0.697273 0.000000 +v 25.000000 43.301270 -5.000000 +vn 0.638901 1.434994 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.759080 -0.079783 0.000000 +v 48.907379 -10.395584 5.000000 +vn 0.803112 -0.084411 0.000000 +v 50.000000 0.000000 -5.000000 +vn 1.562191 -0.164193 0.000000 +v 50.000000 0.000000 5.000000 +vn 0.759080 -0.079783 0.000000 +v 50.000000 0.000000 -5.000000 +vn 0.803112 -0.084411 0.000000 +v 48.907379 -10.395584 5.000000 +vn 1.562191 -0.164193 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 0.567213 0.510721 0.000000 +v 40.450851 29.389263 5.000000 +vn 0.600116 0.540347 0.000000 +v 33.456528 37.157242 -5.000000 +vn 1.167329 1.051068 0.000000 +v 33.456528 37.157242 5.000000 +vn 0.567213 0.510721 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.600116 0.540347 0.000000 +v 40.450851 29.389263 5.000000 +vn 1.167329 1.051068 0.000000 +v 40.450851 29.389263 -5.000000 +vn -0.661003 0.381630 0.000000 +v -45.677273 20.336832 -5.000000 +vn -0.699346 0.403768 0.000000 +v -40.450851 29.389263 5.000000 +vn -1.360350 0.785398 0.000000 +v -40.450851 29.389263 -5.000000 +vn -0.661003 0.381630 0.000000 +v -40.450851 29.389263 5.000000 +vn -0.699346 0.403768 0.000000 +v -45.677273 20.336832 -5.000000 +vn -1.360350 0.785398 0.000000 +v -45.677273 20.336832 5.000000 +vn 0.567213 -0.510721 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.600116 -0.540347 0.000000 +v 40.450851 -29.389263 -5.000000 +vn 1.167329 -1.051068 0.000000 +v 40.450851 -29.389263 5.000000 +vn 0.567213 -0.510721 0.000000 +v 40.450851 -29.389263 -5.000000 +vn 0.600116 -0.540347 0.000000 +v 33.456528 -37.157242 5.000000 +vn 1.167329 -1.051068 0.000000 +v 33.456528 -37.157242 -5.000000 +vn -0.567213 0.510721 0.000000 +v -40.450851 29.389263 -5.000000 +vn -0.600116 0.540347 0.000000 +v -33.456528 37.157242 5.000000 +vn -1.167329 1.051068 0.000000 +v -33.456528 37.157242 -5.000000 +vn -0.567213 0.510721 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.600116 0.540347 0.000000 +v -40.450851 29.389263 -5.000000 +vn -1.167329 1.051068 0.000000 +v -40.450851 29.389263 5.000000 +vn 0.661003 -0.381630 0.000000 +v 40.450851 -29.389263 5.000000 +vn 0.699346 -0.403768 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 1.360350 -0.785398 0.000000 +v 45.677273 -20.336832 5.000000 +vn 0.661003 -0.381630 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 0.699346 -0.403768 0.000000 +v 40.450851 -29.389263 5.000000 +vn 1.360350 -0.785398 0.000000 +v 40.450851 -29.389263 -5.000000 +vn -0.474657 0.653310 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.448633 0.617491 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.923291 1.270801 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.474657 0.653310 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.448633 0.617491 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.923291 1.270801 0.000000 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 0.397030 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 0.971546 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.773017 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 0.533261 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 0.983586 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.624746 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 0.935258 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 0.079637 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 2.126698 +v 28.090170 14.122147 5.000000 +vn 0.000000 0.000000 0.710460 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.068680 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.362453 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 0.095914 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.821343 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 1.224335 +v 28.090170 14.122147 5.000000 +vn 0.000000 0.000000 0.849935 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 1.114545 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.177112 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 0.129351 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.696999 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.315244 +v 29.781475 17.920883 5.000000 +vn 0.000000 0.000000 0.109378 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 2.035788 +v 29.781475 17.920883 5.000000 +vn 0.000000 0.000000 0.996427 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 0.131250 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 2.066767 +v 29.781475 22.079117 5.000000 +vn 0.000000 0.000000 0.943575 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 0.161065 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.696263 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.000000 1.284264 +v 29.781475 22.079117 5.000000 +vn 0.000000 0.000000 0.162839 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.323985 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 1.654769 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.000000 0.163690 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.800790 +v 26.691305 27.431448 5.000000 +vn 0.000000 0.000000 1.177113 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 0.797639 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 1.263865 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.080089 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.175248 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.416103 +v 25.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.550242 +v 26.691305 27.431448 5.000000 +vn 0.000000 0.000000 0.152239 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.054425 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 1.934929 +v 25.000000 28.660254 5.000000 +vn 0.000000 0.000000 0.150263 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.492361 +v 21.045284 29.945217 5.000000 +vn 0.000000 0.000000 1.498969 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 0.137161 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.145761 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 1.858670 +v 21.045284 29.945217 5.000000 +vn 0.000000 0.000000 0.955486 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.621466 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 1.564641 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.583804 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 0.115742 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.442047 +v 16.909828 29.510565 5.000000 +vn 0.000000 0.000000 0.104548 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.128057 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.908987 +v 16.909828 29.510565 5.000000 +vn 0.000000 0.000000 0.926960 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.458257 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.756376 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.764718 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 0.086613 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.290263 +v 13.308694 27.431448 5.000000 +vn 0.000000 0.000000 2.060768 +v 13.308694 27.431448 5.000000 +vn 0.000000 0.000000 0.074549 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.006277 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 0.947726 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.349832 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 1.844034 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.938074 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.282044 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 1.921476 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.994924 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 0.062953 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.083717 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 1.054154 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.068696 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 2.018744 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 1.891912 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 0.312011 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.937670 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.099058 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 0.057667 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.984868 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.936816 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 0.258266 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 0.946511 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.099133 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 0.048750 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.993709 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 1.936892 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 0.223894 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 0.980807 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.071817 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 0.041770 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 2.028005 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 1.909575 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 0.200963 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 1.031054 +v 33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 1.901099 +v 33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 0.185196 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 1.055297 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.212075 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 0.036463 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.893055 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 0.174414 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.126786 +v 15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.840393 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 0.032941 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.798030 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.310621 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.031018 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.721914 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.388663 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 0.030639 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.500477 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.610479 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 0.031757 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.271469 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.838368 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 0.034542 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.178902 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.928151 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 0.038851 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.051199 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 2.051544 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.985271 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 0.052938 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.103383 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 0.166514 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.169712 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.805367 +v 15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 0.961197 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.239254 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 1.941141 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.161147 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.250946 +v -5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.729500 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 2.008394 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 0.045050 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.088148 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 0.157350 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.303035 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.681207 +v -5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.004346 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 0.211341 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.925906 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.154864 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.388627 +v -25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.598102 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 0.192291 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 1.888957 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.060345 +v -40.450851 29.389263 5.000000 +vn 0.000000 0.000000 0.153677 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.444390 +v -33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 1.543527 +v -25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 0.179392 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 1.871808 +v -40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.090393 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 0.153352 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.500477 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.487764 +v -33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 0.170109 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.807220 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.164264 +v -48.907379 10.395584 5.000000 +vn 0.000000 0.000000 0.154128 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.586425 +v -45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.401039 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 0.163654 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.767889 +v -48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.210049 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 0.156018 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.639846 +v -48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.345728 +v -45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 0.158937 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.690347 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.292307 +v -48.907379 -10.395584 5.000000 +vn 0.725904 -0.235860 0.000000 +v 45.677273 -20.336832 5.000000 +vn 0.768012 -0.249542 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 1.493916 -0.485403 0.000000 +v 48.907379 -10.395584 5.000000 +vn 0.725904 -0.235860 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 0.768012 -0.249542 0.000000 +v 45.677273 -20.336832 5.000000 +vn 1.493916 -0.485403 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.807535 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.763261 0.000000 +v -5.226422 49.726093 5.000000 +vn 0.000000 1.570796 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.807535 0.000000 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.763261 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.000000 1.570796 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.474657 -0.653310 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.448633 -0.617491 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.923291 -1.270801 0.000000 +v -33.456528 -37.157242 5.000000 +vn -0.474657 -0.653310 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.448633 -0.617491 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.923291 -1.270801 0.000000 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.312011 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.891912 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.937670 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.258266 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -1.936816 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.946511 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.068696 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.054154 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -2.018744 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -0.223894 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -1.936892 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.980807 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.079637 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.935258 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -2.126698 +v 28.090170 14.122147 -5.000000 +vn 0.000000 0.000000 -0.200963 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.909575 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.031054 +v 33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -2.035788 +v 29.781475 17.920883 -5.000000 +vn 0.000000 0.000000 -0.109378 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.996427 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -1.068680 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.710460 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.362453 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -2.066767 +v 29.781475 22.079117 -5.000000 +vn 0.000000 0.000000 -0.131250 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.943575 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.696999 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -0.129351 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.315244 +v 29.781475 17.920883 -5.000000 +vn 0.000000 0.000000 -0.983586 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.533261 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -1.624746 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.821343 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -0.095914 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -1.224335 +v 28.090170 14.122147 -5.000000 +vn 0.000000 0.000000 -0.185196 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.901099 +v 33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -1.055297 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.971546 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.397030 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -1.773017 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.057667 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -1.099058 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -1.984868 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -0.048750 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -1.099133 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -1.993709 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -0.041770 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.071817 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -2.028005 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -0.036463 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.212075 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.893055 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.126786 +v 15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -0.174414 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.840393 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.169712 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -0.166514 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.805367 +v 15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.798030 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -0.032941 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.310621 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.250946 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -0.161147 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.729500 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.303035 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -0.157350 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.681207 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.721914 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -0.031018 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.388663 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.388627 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.154864 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.598102 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.444390 +v -33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -0.153677 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.543527 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.696263 +v 29.135454 24.067366 -5.000000 +vn 0.000000 0.000000 -0.161065 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.284264 +v 29.781475 22.079117 -5.000000 +vn 0.000000 0.000000 -1.323985 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -0.162839 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.654769 +v 29.135454 24.067366 -5.000000 +vn 0.000000 0.000000 -1.114545 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.849935 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -1.177112 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.800790 +v 26.691305 27.431448 -5.000000 +vn 0.000000 0.000000 -0.163690 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.177113 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -1.416103 +v 25.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.175248 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.550242 +v 26.691305 27.431448 -5.000000 +vn 0.000000 0.000000 -1.054425 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -0.152239 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.934929 +v 25.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -1.263865 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -0.797639 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -1.080089 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.492361 +v 21.045284 29.945217 -5.000000 +vn 0.000000 0.000000 -0.150263 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.498969 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -1.145761 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -0.137161 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.858670 +v 21.045284 29.945217 -5.000000 +vn 0.000000 0.000000 -0.115742 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.583804 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -1.442047 +v 16.909828 29.510565 -5.000000 +vn 0.000000 0.000000 -0.621466 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -0.955486 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.564641 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.128057 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.104548 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.908987 +v 16.909828 29.510565 -5.000000 +vn 0.000000 0.000000 -0.086613 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.764718 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -1.290263 +v 13.308694 27.431448 -5.000000 +vn 0.000000 0.000000 -0.074549 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -2.060768 +v 13.308694 27.431448 -5.000000 +vn 0.000000 0.000000 -1.006277 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -0.062953 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.994924 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -1.083717 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -0.052938 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.985271 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -1.103383 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -0.458257 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.926960 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.756376 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -0.045050 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -2.008394 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -1.088148 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.500477 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.153352 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.487764 +v -33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -1.500477 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -0.030639 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.610479 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -0.349832 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -0.947726 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.844034 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.586425 +v -45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.154128 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.401039 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.282044 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -0.938074 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.921476 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.639846 +v -48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.156018 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.345728 +v -45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.239254 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -0.961197 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.941141 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.690347 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.158937 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.292307 +v -48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.211341 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.004346 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.925906 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.271469 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -0.031757 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.838368 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.051199 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -0.038851 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -2.051544 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.767889 +v -48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.163654 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.210049 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.888957 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -0.192291 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -1.060345 +v -40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.807220 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.170109 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.164264 +v -48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -1.871808 +v -40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.179392 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -1.090393 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.178902 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -0.034542 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.928151 +v 10.218524 17.920883 -5.000000 +vn -0.328454 0.737720 0.000000 +v -15.450850 47.552826 -5.000000 +vn -0.310446 0.697273 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.638901 1.434994 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.328454 0.737720 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.310446 0.697273 0.000000 +v -15.450850 47.552826 -5.000000 +vn -0.638901 1.434994 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.759080 -0.079783 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -0.803112 -0.084411 0.000000 +v -50.000000 0.000000 5.000000 +vn -1.562191 -0.164193 0.000000 +v -50.000000 0.000000 -5.000000 +vn -0.759080 -0.079783 0.000000 +v -50.000000 0.000000 5.000000 +vn -0.803112 -0.084411 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -1.562191 -0.164193 0.000000 +v -48.907379 -10.395584 5.000000 +vn -0.567213 -0.510721 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.600116 -0.540347 0.000000 +v -40.450851 -29.389263 5.000000 +vn -1.167329 -1.051068 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -0.567213 -0.510721 0.000000 +v -40.450851 -29.389263 5.000000 +vn -0.600116 -0.540347 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -1.167329 -1.051068 0.000000 +v -33.456528 -37.157242 5.000000 +vn -0.661003 -0.381630 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -0.699346 -0.403768 0.000000 +v -45.677273 -20.336832 5.000000 +vn -1.360350 -0.785398 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -0.661003 -0.381630 0.000000 +v -45.677273 -20.336832 5.000000 +vn -0.699346 -0.403768 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -1.360350 -0.785398 0.000000 +v -40.450851 -29.389263 5.000000 +vn 0.167896 -0.789889 0.000000 +v 5.226422 -49.726093 -5.000000 +vn 0.158691 -0.746582 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.326587 -1.536471 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.167896 -0.789889 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.158691 -0.746582 0.000000 +v 5.226422 -49.726093 -5.000000 +vn 0.326587 -1.536471 0.000000 +v 15.450850 -47.552826 -5.000000 +vn -0.759080 0.079783 0.000000 +v -50.000000 0.000000 -5.000000 +vn -0.803112 0.084411 0.000000 +v -48.907379 10.395584 5.000000 +vn -1.562191 0.164193 0.000000 +v -48.907379 10.395584 -5.000000 +vn -0.759080 0.079783 0.000000 +v -48.907379 10.395584 5.000000 +vn -0.803112 0.084411 0.000000 +v -50.000000 0.000000 -5.000000 +vn -1.562191 0.164193 0.000000 +v -50.000000 0.000000 5.000000 +vn 0.167896 0.789889 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.158691 0.746582 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.326587 1.536471 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.167896 0.789889 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.158691 0.746582 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.326587 1.536471 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.474657 0.653310 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.448633 0.617491 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.923291 1.270801 0.000000 +v 33.456528 37.157242 5.000000 +vn 0.474657 0.653310 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.448633 0.617491 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.923291 1.270801 0.000000 +v 25.000000 43.301270 -5.000000 +vn -0.167896 0.789889 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.158691 0.746582 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.326587 1.536471 0.000000 +v -5.226422 49.726093 5.000000 +vn -0.167896 0.789889 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.158691 0.746582 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.326587 1.536471 0.000000 +v -15.450850 47.552826 -5.000000 +vn 0.328454 -0.737720 0.000000 +v 15.450850 -47.552826 -5.000000 +vn 0.310446 -0.697273 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.638901 -1.434994 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.328454 -0.737720 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.310446 -0.697273 0.000000 +v 15.450850 -47.552826 -5.000000 +vn 0.638901 -1.434994 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 -0.807535 0.000000 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 -0.763261 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.000000 -1.570796 0.000000 +v -5.226422 -49.726093 5.000000 +vn 0.000000 -0.807535 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.000000 -0.763261 0.000000 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 -1.570796 0.000000 +v 5.226422 -49.726093 -5.000000 +vn -0.167896 -0.789889 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.158691 -0.746582 0.000000 +v -5.226422 -49.726093 5.000000 +vn -0.326587 -1.536471 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.167896 -0.789889 0.000000 +v -5.226422 -49.726093 5.000000 +vn -0.158691 -0.746582 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.326587 -1.536471 0.000000 +v -5.226422 -49.726093 -5.000000 +vn -0.725904 0.235860 0.000000 +v -48.907379 10.395584 -5.000000 +vn -0.768012 0.249542 0.000000 +v -45.677273 20.336832 5.000000 +vn -1.493916 0.485403 0.000000 +v -45.677273 20.336832 -5.000000 +vn -0.725904 0.235860 0.000000 +v -45.677273 20.336832 5.000000 +vn -0.768012 0.249542 0.000000 +v -48.907379 10.395584 -5.000000 +vn -1.493916 0.485403 0.000000 +v -48.907379 10.395584 5.000000 +vn -1.297914 -0.421718 0.000000 +v 29.781475 22.079117 -5.000000 +vn -0.196002 -0.063685 0.000000 +v 29.135454 24.067366 5.000000 +vn -1.493916 -0.485403 0.000000 +v 29.135454 24.067366 -5.000000 +vn -1.297914 -0.421718 0.000000 +v 29.135454 24.067366 5.000000 +vn -0.196002 -0.063685 0.000000 +v 29.781475 22.079117 -5.000000 +vn -1.493916 -0.485403 0.000000 +v 29.781475 22.079117 5.000000 +vn -1.357231 -0.142651 0.000000 +v 30.000000 20.000000 -5.000000 +vn -0.204960 -0.021542 0.000000 +v 29.781475 22.079117 5.000000 +vn -1.562191 -0.164194 0.000000 +v 29.781475 22.079117 -5.000000 +vn -1.357231 -0.142651 0.000000 +v 29.781475 22.079117 5.000000 +vn -0.204960 -0.021542 0.000000 +v 30.000000 20.000000 -5.000000 +vn -1.562191 -0.164194 0.000000 +v 30.000000 20.000000 5.000000 +vn -1.181872 -0.682353 0.000000 +v 29.135454 24.067366 -5.000000 +vn -0.178478 -0.103044 0.000000 +v 28.090170 25.877853 5.000000 +vn -1.360350 -0.785397 0.000000 +v 28.090170 25.877853 -5.000000 +vn -1.181872 -0.682353 0.000000 +v 28.090170 25.877853 5.000000 +vn -0.178478 -0.103044 0.000000 +v 29.135454 24.067366 -5.000000 +vn -1.360350 -0.785397 0.000000 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.206089 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.000000 1.364708 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.000000 1.570796 0.000000 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.206089 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.000000 1.364708 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.000000 1.570796 0.000000 +v 18.954716 10.054781 -5.000000 +vn -0.083824 0.188272 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.555077 1.246722 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.638901 1.434994 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.083824 0.188272 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.555077 1.246722 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.638901 1.434994 0.000000 +v 23.090170 10.489434 -5.000000 +vn 1.297914 0.421717 0.000000 +v 10.864545 15.932633 5.000000 +vn 0.196002 0.063685 0.000000 +v 10.218524 17.920883 -5.000000 +vn 1.493916 0.485402 0.000000 +v 10.218524 17.920883 5.000000 +vn 1.297914 0.421717 0.000000 +v 10.218524 17.920883 -5.000000 +vn 0.196002 0.063685 0.000000 +v 10.864545 15.932633 5.000000 +vn 1.493916 0.485402 0.000000 +v 10.864545 15.932633 -5.000000 +vn -1.357231 0.142651 0.000000 +v 29.781475 17.920883 -5.000000 +vn -0.204960 0.021542 0.000000 +v 30.000000 20.000000 5.000000 +vn -1.562191 0.164194 0.000000 +v 30.000000 20.000000 -5.000000 +vn -1.357231 0.142651 0.000000 +v 30.000000 20.000000 5.000000 +vn -0.204960 0.021542 0.000000 +v 29.781475 17.920883 -5.000000 +vn -1.562191 0.164194 0.000000 +v 29.781475 17.920883 5.000000 +vn 1.014175 -0.913168 0.000000 +v 11.909829 25.877853 5.000000 +vn 0.153154 -0.137900 0.000000 +v 13.308694 27.431448 -5.000000 +vn 1.167328 -1.051069 0.000000 +v 13.308694 27.431448 5.000000 +vn 1.014175 -0.913168 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.153154 -0.137900 0.000000 +v 11.909829 25.877853 5.000000 +vn 1.167328 -1.051069 0.000000 +v 11.909829 25.877853 -5.000000 +vn 0.042848 0.201585 0.000000 +v 18.954716 10.054781 -5.000000 +vn 0.283738 1.334885 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.326586 1.536471 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.042848 0.201585 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.283738 1.334885 0.000000 +v 18.954716 10.054781 -5.000000 +vn 0.326586 1.536471 0.000000 +v 16.909828 10.489434 -5.000000 +vn -1.297914 0.421717 0.000000 +v 29.135454 15.932633 -5.000000 +vn -0.196002 0.063685 0.000000 +v 29.781475 17.920883 5.000000 +vn -1.493916 0.485402 0.000000 +v 29.781475 17.920883 -5.000000 +vn -1.297914 0.421717 0.000000 +v 29.781475 17.920883 5.000000 +vn -0.196002 0.063685 0.000000 +v 29.135454 15.932633 -5.000000 +vn -1.493916 0.485402 0.000000 +v 29.135454 15.932633 5.000000 +vn 0.083824 0.188271 0.000000 +v 16.909828 10.489434 -5.000000 +vn 0.555077 1.246722 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.638901 1.434994 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.083824 0.188271 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.555077 1.246722 0.000000 +v 16.909828 10.489434 -5.000000 +vn 0.638901 1.434994 0.000000 +v 15.000000 11.339746 -5.000000 +vn -0.042848 -0.201585 0.000000 +v 21.045284 29.945217 -5.000000 +vn -0.283738 -1.334886 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.326586 -1.536471 0.000000 +v 21.045284 29.945217 5.000000 +vn -0.042848 -0.201585 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.283738 -1.334886 0.000000 +v 21.045284 29.945217 -5.000000 +vn -0.326586 -1.536471 0.000000 +v 23.090170 29.510565 -5.000000 +vn 0.000000 -0.206089 0.000000 +v 18.954716 29.945217 -5.000000 +vn 0.000000 -1.364708 0.000000 +v 21.045284 29.945217 5.000000 +vn 0.000000 -1.570796 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.000000 -0.206089 0.000000 +v 21.045284 29.945217 5.000000 +vn 0.000000 -1.364708 0.000000 +v 18.954716 29.945217 -5.000000 +vn 0.000000 -1.570796 0.000000 +v 21.045284 29.945217 -5.000000 +vn -1.181872 0.682353 0.000000 +v 28.090170 14.122147 -5.000000 +vn -0.178478 0.103044 0.000000 +v 29.135454 15.932633 5.000000 +vn -1.360350 0.785398 0.000000 +v 29.135454 15.932633 -5.000000 +vn -1.181872 0.682353 0.000000 +v 29.135454 15.932633 5.000000 +vn -0.178478 0.103044 0.000000 +v 28.090170 14.122147 -5.000000 +vn -1.360350 0.785398 0.000000 +v 28.090170 14.122147 5.000000 +vn 1.297914 -0.421718 0.000000 +v 10.218524 22.079117 5.000000 +vn 0.196002 -0.063685 0.000000 +v 10.864545 24.067366 -5.000000 +vn 1.493916 -0.485403 0.000000 +v 10.864545 24.067366 5.000000 +vn 1.297914 -0.421718 0.000000 +v 10.864545 24.067366 -5.000000 +vn 0.196002 -0.063685 0.000000 +v 10.218524 22.079117 5.000000 +vn 1.493916 -0.485403 0.000000 +v 10.218524 22.079117 -5.000000 +vn 1.014175 0.913168 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.153154 0.137900 0.000000 +v 11.909829 14.122147 -5.000000 +vn 1.167329 1.051068 0.000000 +v 11.909829 14.122147 5.000000 +vn 1.014175 0.913168 0.000000 +v 11.909829 14.122147 -5.000000 +vn 0.153154 0.137900 0.000000 +v 13.308694 12.568551 5.000000 +vn 1.167329 1.051068 0.000000 +v 13.308694 12.568551 -5.000000 +vn -1.014175 -0.913168 0.000000 +v 28.090170 25.877853 -5.000000 +vn -0.153154 -0.137900 0.000000 +v 26.691305 27.431448 5.000000 +vn -1.167328 -1.051069 0.000000 +v 26.691305 27.431448 -5.000000 +vn -1.014175 -0.913168 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.153154 -0.137900 0.000000 +v 28.090170 25.877853 -5.000000 +vn -1.167328 -1.051069 0.000000 +v 28.090170 25.877853 5.000000 +vn 1.357232 -0.142651 0.000000 +v 10.000000 20.000000 5.000000 +vn 0.204960 -0.021542 0.000000 +v 10.218524 22.079117 -5.000000 +vn 1.562191 -0.164193 0.000000 +v 10.218524 22.079117 5.000000 +vn 1.357232 -0.142651 0.000000 +v 10.218524 22.079117 -5.000000 +vn 0.204960 -0.021542 0.000000 +v 10.000000 20.000000 5.000000 +vn 1.562191 -0.164193 0.000000 +v 10.000000 20.000000 -5.000000 +vn 0.042848 -0.201585 0.000000 +v 16.909828 29.510565 -5.000000 +vn 0.283737 -1.334885 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.326586 -1.536471 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.042848 -0.201585 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.283737 -1.334885 0.000000 +v 16.909828 29.510565 -5.000000 +vn 0.326586 -1.536471 0.000000 +v 18.954716 29.945217 -5.000000 +vn -0.121136 -0.166729 0.000000 +v 25.000000 28.660254 -5.000000 +vn -0.802155 -1.104072 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.923291 -1.270801 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.121136 -0.166729 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.802155 -1.104072 0.000000 +v 25.000000 28.660254 -5.000000 +vn -0.923291 -1.270801 0.000000 +v 26.691305 27.431448 -5.000000 +vn 0.083824 -0.188271 0.000000 +v 15.000000 28.660254 -5.000000 +vn 0.555077 -1.246722 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.638901 -1.434994 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.083824 -0.188271 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.555077 -1.246722 0.000000 +v 15.000000 28.660254 -5.000000 +vn 0.638901 -1.434994 0.000000 +v 16.909828 29.510565 -5.000000 +vn -1.014175 0.913168 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.153154 0.137900 0.000000 +v 28.090170 14.122147 5.000000 +vn -1.167329 1.051068 0.000000 +v 28.090170 14.122147 -5.000000 +vn -1.014175 0.913168 0.000000 +v 28.090170 14.122147 5.000000 +vn -0.153154 0.137900 0.000000 +v 26.691305 12.568551 -5.000000 +vn -1.167329 1.051068 0.000000 +v 26.691305 12.568551 5.000000 +vn -0.083824 -0.188272 0.000000 +v 23.090170 29.510565 -5.000000 +vn -0.555077 -1.246722 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.638901 -1.434994 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.083824 -0.188272 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.555077 -1.246722 0.000000 +v 23.090170 29.510565 -5.000000 +vn -0.638901 -1.434994 0.000000 +v 25.000000 28.660254 -5.000000 +vn 0.121136 0.166729 0.000000 +v 15.000000 11.339746 -5.000000 +vn 0.802155 1.104072 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.923291 1.270801 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.121136 0.166729 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.802155 1.104072 0.000000 +v 15.000000 11.339746 -5.000000 +vn 0.923291 1.270801 0.000000 +v 13.308694 12.568551 -5.000000 +vn -0.121136 0.166729 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.802155 1.104072 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.923291 1.270801 0.000000 +v 26.691305 12.568551 5.000000 +vn -0.121136 0.166729 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.802155 1.104072 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.923291 1.270801 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.042848 0.201585 0.000000 +v 23.090170 10.489434 -5.000000 +vn -0.283738 1.334885 0.000000 +v 21.045284 10.054781 5.000000 +vn -0.326587 1.536471 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.042848 0.201585 0.000000 +v 21.045284 10.054781 5.000000 +vn -0.283738 1.334885 0.000000 +v 23.090170 10.489434 -5.000000 +vn -0.326587 1.536471 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.121136 -0.166729 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.802155 -1.104072 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.923291 -1.270801 0.000000 +v 13.308694 27.431448 5.000000 +vn 0.121136 -0.166729 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.802155 -1.104072 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.923291 -1.270801 0.000000 +v 15.000000 28.660254 -5.000000 +vn 1.181872 0.682353 0.000000 +v 11.909829 14.122147 5.000000 +vn 0.178478 0.103044 0.000000 +v 10.864545 15.932633 -5.000000 +vn 1.360350 0.785398 0.000000 +v 10.864545 15.932633 5.000000 +vn 1.181872 0.682353 0.000000 +v 10.864545 15.932633 -5.000000 +vn 0.178478 0.103044 0.000000 +v 11.909829 14.122147 5.000000 +vn 1.360350 0.785398 0.000000 +v 11.909829 14.122147 -5.000000 +vn 1.357232 0.142651 0.000000 +v 10.218524 17.920883 5.000000 +vn 0.204960 0.021542 0.000000 +v 10.000000 20.000000 -5.000000 +vn 1.562191 0.164193 0.000000 +v 10.000000 20.000000 5.000000 +vn 1.357232 0.142651 0.000000 +v 10.000000 20.000000 -5.000000 +vn 0.204960 0.021542 0.000000 +v 10.218524 17.920883 5.000000 +vn 1.562191 0.164193 0.000000 +v 10.218524 17.920883 -5.000000 +vn 1.181872 -0.682353 0.000000 +v 10.864545 24.067366 5.000000 +vn 0.178478 -0.103044 0.000000 +v 11.909829 25.877853 -5.000000 +vn 1.360350 -0.785397 0.000000 +v 11.909829 25.877853 5.000000 +vn 1.181872 -0.682353 0.000000 +v 11.909829 25.877853 -5.000000 +vn 0.178478 -0.103044 0.000000 +v 10.864545 24.067366 5.000000 +vn 1.360350 -0.785397 0.000000 +v 10.864545 24.067366 -5.000000 +# 720 vertices, 0 vertices normals + +f 1//1 2//2 3//3 +f 4//4 5//5 6//6 +f 7//7 8//8 9//9 +f 10//10 11//11 12//12 +f 13//13 14//14 15//15 +f 16//16 17//17 18//18 +f 19//19 20//20 21//21 +f 22//22 23//23 24//24 +f 25//25 26//26 27//27 +f 28//28 29//29 30//30 +f 31//31 32//32 33//33 +f 34//34 35//35 36//36 +f 37//37 38//38 39//39 +f 40//40 41//41 42//42 +f 43//43 44//44 45//45 +f 46//46 47//47 48//48 +f 49//49 50//50 51//51 +f 52//52 53//53 54//54 +f 55//55 56//56 57//57 +f 58//58 59//59 60//60 +f 61//61 62//62 63//63 +f 64//64 65//65 66//66 +f 67//67 68//68 69//69 +f 70//70 71//71 72//72 +f 73//73 74//74 75//75 +f 76//76 77//77 78//78 +f 79//79 80//80 81//81 +f 82//82 83//83 84//84 +f 85//85 86//86 87//87 +f 88//88 89//89 90//90 +f 91//91 92//92 93//93 +f 94//94 95//95 96//96 +f 97//97 98//98 99//99 +f 100//100 101//101 102//102 +f 103//103 104//104 105//105 +f 106//106 107//107 108//108 +f 109//109 110//110 111//111 +f 112//112 113//113 114//114 +f 115//115 116//116 117//117 +f 118//118 119//119 120//120 +f 121//121 122//122 123//123 +f 124//124 125//125 126//126 +f 127//127 128//128 129//129 +f 130//130 131//131 132//132 +f 133//133 134//134 135//135 +f 136//136 137//137 138//138 +f 139//139 140//140 141//141 +f 142//142 143//143 144//144 +f 145//145 146//146 147//147 +f 148//148 149//149 150//150 +f 151//151 152//152 153//153 +f 154//154 155//155 156//156 +f 157//157 158//158 159//159 +f 160//160 161//161 162//162 +f 163//163 164//164 165//165 +f 166//166 167//167 168//168 +f 169//169 170//170 171//171 +f 172//172 173//173 174//174 +f 175//175 176//176 177//177 +f 178//178 179//179 180//180 +f 181//181 182//182 183//183 +f 184//184 185//185 186//186 +f 187//187 188//188 189//189 +f 190//190 191//191 192//192 +f 193//193 194//194 195//195 +f 196//196 197//197 198//198 +f 199//199 200//200 201//201 +f 202//202 203//203 204//204 +f 205//205 206//206 207//207 +f 208//208 209//209 210//210 +f 211//211 212//212 213//213 +f 214//214 215//215 216//216 +f 217//217 218//218 219//219 +f 220//220 221//221 222//222 +f 223//223 224//224 225//225 +f 226//226 227//227 228//228 +f 229//229 230//230 231//231 +f 232//232 233//233 234//234 +f 235//235 236//236 237//237 +f 238//238 239//239 240//240 +f 241//241 242//242 243//243 +f 244//244 245//245 246//246 +f 247//247 248//248 249//249 +f 250//250 251//251 252//252 +f 253//253 254//254 255//255 +f 256//256 257//257 258//258 +f 259//259 260//260 261//261 +f 262//262 263//263 264//264 +f 265//265 266//266 267//267 +f 268//268 269//269 270//270 +f 271//271 272//272 273//273 +f 274//274 275//275 276//276 +f 277//277 278//278 279//279 +f 280//280 281//281 282//282 +f 283//283 284//284 285//285 +f 286//286 287//287 288//288 +f 289//289 290//290 291//291 +f 292//292 293//293 294//294 +f 295//295 296//296 297//297 +f 298//298 299//299 300//300 +f 301//301 302//302 303//303 +f 304//304 305//305 306//306 +f 307//307 308//308 309//309 +f 310//310 311//311 312//312 +f 313//313 314//314 315//315 +f 316//316 317//317 318//318 +f 319//319 320//320 321//321 +f 322//322 323//323 324//324 +f 325//325 326//326 327//327 +f 328//328 329//329 330//330 +f 331//331 332//332 333//333 +f 334//334 335//335 336//336 +f 337//337 338//338 339//339 +f 340//340 341//341 342//342 +f 343//343 344//344 345//345 +f 346//346 347//347 348//348 +f 349//349 350//350 351//351 +f 352//352 353//353 354//354 +f 355//355 356//356 357//357 +f 358//358 359//359 360//360 +f 361//361 362//362 363//363 +f 364//364 365//365 366//366 +f 367//367 368//368 369//369 +f 370//370 371//371 372//372 +f 373//373 374//374 375//375 +f 376//376 377//377 378//378 +f 379//379 380//380 381//381 +f 382//382 383//383 384//384 +f 385//385 386//386 387//387 +f 388//388 389//389 390//390 +f 391//391 392//392 393//393 +f 394//394 395//395 396//396 +f 397//397 398//398 399//399 +f 400//400 401//401 402//402 +f 403//403 404//404 405//405 +f 406//406 407//407 408//408 +f 409//409 410//410 411//411 +f 412//412 413//413 414//414 +f 415//415 416//416 417//417 +f 418//418 419//419 420//420 +f 421//421 422//422 423//423 +f 424//424 425//425 426//426 +f 427//427 428//428 429//429 +f 430//430 431//431 432//432 +f 433//433 434//434 435//435 +f 436//436 437//437 438//438 +f 439//439 440//440 441//441 +f 442//442 443//443 444//444 +f 445//445 446//446 447//447 +f 448//448 449//449 450//450 +f 451//451 452//452 453//453 +f 454//454 455//455 456//456 +f 457//457 458//458 459//459 +f 460//460 461//461 462//462 +f 463//463 464//464 465//465 +f 466//466 467//467 468//468 +f 469//469 470//470 471//471 +f 472//472 473//473 474//474 +f 475//475 476//476 477//477 +f 478//478 479//479 480//480 +f 481//481 482//482 483//483 +f 484//484 485//485 486//486 +f 487//487 488//488 489//489 +f 490//490 491//491 492//492 +f 493//493 494//494 495//495 +f 496//496 497//497 498//498 +f 499//499 500//500 501//501 +f 502//502 503//503 504//504 +f 505//505 506//506 507//507 +f 508//508 509//509 510//510 +f 511//511 512//512 513//513 +f 514//514 515//515 516//516 +f 517//517 518//518 519//519 +f 520//520 521//521 522//522 +f 523//523 524//524 525//525 +f 526//526 527//527 528//528 +f 529//529 530//530 531//531 +f 532//532 533//533 534//534 +f 535//535 536//536 537//537 +f 538//538 539//539 540//540 +f 541//541 542//542 543//543 +f 544//544 545//545 546//546 +f 547//547 548//548 549//549 +f 550//550 551//551 552//552 +f 553//553 554//554 555//555 +f 556//556 557//557 558//558 +f 559//559 560//560 561//561 +f 562//562 563//563 564//564 +f 565//565 566//566 567//567 +f 568//568 569//569 570//570 +f 571//571 572//572 573//573 +f 574//574 575//575 576//576 +f 577//577 578//578 579//579 +f 580//580 581//581 582//582 +f 583//583 584//584 585//585 +f 586//586 587//587 588//588 +f 589//589 590//590 591//591 +f 592//592 593//593 594//594 +f 595//595 596//596 597//597 +f 598//598 599//599 600//600 +f 601//601 602//602 603//603 +f 604//604 605//605 606//606 +f 607//607 608//608 609//609 +f 610//610 611//611 612//612 +f 613//613 614//614 615//615 +f 616//616 617//617 618//618 +f 619//619 620//620 621//621 +f 622//622 623//623 624//624 +f 625//625 626//626 627//627 +f 628//628 629//629 630//630 +f 631//631 632//632 633//633 +f 634//634 635//635 636//636 +f 637//637 638//638 639//639 +f 640//640 641//641 642//642 +f 643//643 644//644 645//645 +f 646//646 647//647 648//648 +f 649//649 650//650 651//651 +f 652//652 653//653 654//654 +f 655//655 656//656 657//657 +f 658//658 659//659 660//660 +f 661//661 662//662 663//663 +f 664//664 665//665 666//666 +f 667//667 668//668 669//669 +f 670//670 671//671 672//672 +f 673//673 674//674 675//675 +f 676//676 677//677 678//678 +f 679//679 680//680 681//681 +f 682//682 683//683 684//684 +f 685//685 686//686 687//687 +f 688//688 689//689 690//690 +f 691//691 692//692 693//693 +f 694//694 695//695 696//696 +f 697//697 698//698 699//699 +f 700//700 701//701 702//702 +f 703//703 704//704 705//705 +f 706//706 707//707 708//708 +f 709//709 710//710 711//711 +f 712//712 713//713 714//714 +f 715//715 716//716 717//717 +f 718//718 719//719 720//720 +# 240 faces, 0 coords texture + +# End of File From 44bc8d8f5fb05f2d6e82aa5d098e5a73ca55c093 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 10:52:08 +0100 Subject: [PATCH 50/86] Small refactor and comments --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 84 +++++++++++++++-------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index f360c67538..9e423ff919 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -392,7 +392,7 @@ using AlgNLoptORIG_DIRECT = detail::NLoptAlg; using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptAGS = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Subplx = detail::NLoptAlgComb; using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 13586d9375..78e9afb882 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -136,8 +136,11 @@ struct Beam_ { // Defines a set of rays displaced along a cone's surface using Beam = Beam_<8>; -template -Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) +template +Hit beam_mesh_hit(Ex policy, + const AABBMesh &mesh, + const Beam_ &beam, + double sd) { Vec3d src = beam.src; Vec3d dst = src + beam.dir; @@ -146,15 +149,15 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) Vec3d D = (dst - src); Vec3d dir = D.normalized(); - PointRing ring{dir}; + PointRing ring{dir}; using Hit = AABBMesh::hit_result; // Hit results - std::array hits; + std::array hits; execution::for_each( - ex, size_t(0), hits.size(), + policy, size_t(0), hits.size(), [&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) { Hit &hit = hits[i]; @@ -175,7 +178,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) } } else hit = hr; - }, std::min(execution::max_concurrency(ex), S)); + }, std::min(execution::max_concurrency(policy), RayCount)); return min_hit(hits.begin(), hits.end()); } @@ -359,7 +362,7 @@ bool optimize_pinhead_placement(Ex policy, // viable normal that doesn't collide with the model // geometry and its very close to the default. - Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); + Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); solver.seed(0); // we want deterministic behavior auto oresult = solver.to_max().optimize( @@ -483,21 +486,27 @@ constexpr bool IsWideningFn = std::is_invocable_r_v; +// A widening function can determine how many ray samples should a beam contain +// (see in beam_mesh_hit) template struct BeamSamples { static constexpr size_t Value = 8; }; template constexpr size_t BeamSamplesV = BeamSamples>::Value; - +// To use with check_ground_route, full will check the bridge and the pillar, +// PillarOnly checks only the pillar for collisions. enum class GroundRouteCheck { Full, PillarOnly }; +// Returns the collision point with mesh if there is a collision or a ground point, +// given a source point with a direction of a potential avoidance bridge and +// a bridge length. template> > Vec3d check_ground_route( Ex policy, const SupportableMesh &sm, - const Junction &source, - const Vec3d &dir, - double bridge_len, - WideningFn &&wideningfn, + const Junction &source, // source location + const Vec3d &dir, // direction of the bridge from the source + double bridge_len, // lenght of the avoidance bridge + WideningFn &&wideningfn, // Widening strategy GroundRouteCheck type = GroundRouteCheck::Full ) { @@ -556,6 +565,9 @@ Vec3d check_ground_route( return ret; } +// Searching a ground connection from an arbitrary source point. +// Currently, the result will contain one avoidance bridge (at most) and a +// pillar to the ground, if it's feasible template> > GroundConnection deepsearch_ground_connection( @@ -565,26 +577,35 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { + constexpr unsigned MaxIterationsGlobal = 5000; + constexpr unsigned MaxIterationsLocal = 100; + constexpr double RelScoreDiff = 0.05; + const auto gndlvl = ground_level(sm); - auto criteria = get_criteria(sm.cfg); - criteria.max_iterations(5000); + // The used solver (AlgNLoptMLSL_Subplx search method) is composed of a global (MLSL) + // and a local (Subplex) search method. Criteria can be set in a way that + // local searches are quick and less accurate. The global method will only + // consider the max iteration number and the stop score (Z level <= ground) + + auto criteria = get_criteria(sm.cfg); // get defaults from cfg + criteria.max_iterations(MaxIterationsGlobal); criteria.abs_score_diff(NaNd); criteria.rel_score_diff(NaNd); criteria.stop_score(gndlvl); auto criteria_loc = criteria; - criteria_loc.max_iterations(100); + criteria_loc.max_iterations(MaxIterationsLocal); criteria_loc.abs_score_diff(EPSILON); - criteria_loc.rel_score_diff(0.05); + criteria_loc.rel_score_diff(RelScoreDiff); - Optimizer solver(criteria); + Optimizer solver(criteria); solver.set_loc_criteria(criteria_loc); - solver.seed(0); + solver.seed(0); // require repeatability // functor returns the z height of collision point, given a polar and // azimuth angles as bridge direction and bridge length. The route is - // traced from source, throught this bridge and an attached pillar. If there + // traced from source, through this bridge and an attached pillar. If there // is a collision with the mesh, the Z height is returned. Otherwise the // z level of ground is returned. auto z_fn = [&](const opt::Input<3> &input) { @@ -598,20 +619,22 @@ GroundConnection deepsearch_ground_connection( return hitpt.z(); }; + // Calculate the initial direction of the search by + // saturating the polar angle to max tilt defined in config auto [plr_init, azm_init] = dir_to_spheric(init_dir); - - // Saturate the polar angle to max tilt defined in config plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); + auto bound_constraints = - bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle - {-PI, PI}, // bounds for azimuth - {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + bounds({ + {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} // bounds bridge length + }); // The optimizer can navigate fairly well on the mesh surface, finding // lower and lower Z coordinates as collision points. MLSL is not a local // search method, so it should not be trapped in a local minima. Eventually, - // this search should arrive at a ground location, like water flows down a - // surface. + // this search should arrive at a ground location. auto oresult = solver.to_min().optimize( z_fn, initvals({plr_init, azm_init, 0.}), @@ -628,7 +651,9 @@ GroundConnection deepsearch_ground_connection( // and length. This length can be shortened further by brute-force queries // of free route straigt down for a possible pillar. // NOTE: This requirement could be incorporated into the optimization as a - // constraint, but it would not find quickly enough an accurate solution. + // constraint, but it would not find quickly enough an accurate solution, + // and it would be very hard to define a stop score which is very useful in + // terminating the search as soon as the ground is found. double l = 0., l_max = bridge_l; double zlvl = std::numeric_limits::infinity(); while(zlvl > gndlvl && l <= l_max) { @@ -650,10 +675,14 @@ GroundConnection deepsearch_ground_connection( double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l); double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + // Even if the search was not succesful, the result is populated by the + // source and the last best result of the optimization. conn.path.emplace_back(source); if (bridge_l > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); + // The resulting ground connection is only valid if the pillar base is set. + // At this point it will only be set if the search was succesful. if (z_fn(opt::Input<3>({plr, azm, bridge_l})) <= gndlvl) conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; @@ -661,6 +690,7 @@ GroundConnection deepsearch_ground_connection( return conn; } +// Ground route search with a predefined end radius template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, From 7eb5ca7396ada2122f4a674167c3385a5eba1ab5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 12:06:16 +0100 Subject: [PATCH 51/86] Log support tree creation time --- src/libslic3r/SLA/SupportTree.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 37c2e85e96..cfafdf7e97 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -18,6 +18,8 @@ #include #include +#include + //! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -30,6 +32,9 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, auto builder = make_unique(ctl); if (sm.cfg.enabled) { + Benchmark bench; + bench.start(); + switch (sm.cfg.tree_type) { case SupportTreeType::Default: { create_default_tree(*builder, sm); @@ -45,6 +50,12 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, default:; } + bench.stop(); + + BOOST_LOG_TRIVIAL(info) << "Support tree creation took: " + << bench.getElapsedSec() + << " seconds"; + builder->merge_and_cleanup(); // clean metadata, leave only the meshes. } From ebb8f9bc80b4b1f8915ad7014146a1aab4eb8bfa Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 12:06:37 +0100 Subject: [PATCH 52/86] Disable parallel beam hits in branching tree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 4230e38bb4..5c7e751fa9 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -13,6 +13,8 @@ namespace Slic3r { namespace sla { +inline constexpr const auto &beam_ex_policy = ex_tbb; + class BranchingTreeBuilder: public branchingtree::Builder { SupportTreeBuilder &m_builder; const SupportableMesh &m_sm; @@ -178,7 +180,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, Vec3d fromd = from.pos.cast(), tod = to.pos.cast(); double fromR = get_radius(from), toR = get_radius(to); Beam beam{Ball{fromd, fromR}, Ball{tod, toR}}; - auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam, + auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam, m_sm.cfg.safety_distance_mm); bool ret = hit.distance() > (tod - fromd).norm(); @@ -201,8 +203,8 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node, Beam beam2{Ball{from2d, closestR}, Ball{tod, mergeR}}; auto sd = m_sm.cfg.safety_distance_mm ; - auto hit1 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam1, sd); - auto hit2 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam2, sd); + auto hit1 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam1, sd); + auto hit2 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam2, sd); bool ret = hit1.distance() > (tod - from1d).norm() && hit2.distance() > (tod - from2d).norm(); @@ -222,7 +224,7 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, sla::Junction j{from.pos.cast(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); - auto conn = deepsearch_ground_connection(ex_tbb, m_sm, j, + auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j, get_radius(to), init_dir); if (conn) { @@ -255,13 +257,13 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, auto anchor = m_sm.cfg.ground_facing_only ? std::optional{} : // If no mesh connections are allowed - calculate_anchor_placement(ex_tbb, m_sm, fromj, + calculate_anchor_placement(beam_ex_policy , m_sm, fromj, to.pos.cast()); if (anchor) { sla::Junction toj = {anchor->junction_point(), anchor->r_back_mm}; - auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, + auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh, Beam{{fromj.pos, fromj.r}, {toj.pos, toj.r}}, 0.); if (hit.distance() > distance(fromj.pos, toj.pos)) { From 58acc893b3c9da4af912a648f743afebba6709e3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:49:26 +0100 Subject: [PATCH 53/86] Solve mini pillar widening by enforcing min radius on bed points Use subtree rescue after all --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 5c7e751fa9..d88bdb1b05 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -150,7 +150,7 @@ public: << " " << j.pos.y() << " " << j.pos.z(); // Discard all the support points connecting to this branch. - discard_subtree(j.id); + discard_subtree_rescure(j.id); } const std::vector& unroutable_pinheads() const @@ -336,6 +336,9 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s float(props.ground_level()), props.sampling_radius()); + for (auto &bp : bedpts) + bp.Rmin = sm.cfg.head_back_radius_mm; + branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts), std::move(leafs), props}; From d4a46d373a4b269b84b9b14ab85a4221795a1aea Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:50:12 +0100 Subject: [PATCH 54/86] Solve mini pillar widening in DefaultWideningStrategy --- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 78e9afb882..a47f8d6620 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -723,7 +723,7 @@ struct DefaultWideningModel { "DefaultWideningModel is not a widening function"); double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len; - return src.R + w; + return std::max(src.R, sm.cfg.head_back_radius_mm) + w; }; }; From e16c886a1adf2062e7bb34d75826bafa3dbe1c2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:50:36 +0100 Subject: [PATCH 55/86] Remove dead code --- src/libslic3r/BranchingTree/PointCloud.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 4075a20c27..1497f08945 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -74,8 +74,6 @@ std::vector sample_mesh(const indexed_triangle_set &its, double radius) std::vector sample_bed(const ExPolygons &bed, float z, double radius) { - std::vector ret; - auto triangles = triangulate_expolygons_3d(bed, z); indexed_triangle_set its; its.vertices.reserve(triangles.size()); From c79a46e6cb0dbb7591469354f9a2db90411cf5dd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 14:03:25 +0100 Subject: [PATCH 56/86] Remove unnecessary stuff --- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/OrganicTree/OrganicTree.hpp | 95 - src/libslic3r/OrganicTree/OrganicTreeImpl.hpp | 11 - src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 - tests/data/disk_with_hole.obj | 1696 ----------------- 5 files changed, 1808 deletions(-) delete mode 100644 src/libslic3r/OrganicTree/OrganicTree.hpp delete mode 100644 src/libslic3r/OrganicTree/OrganicTreeImpl.hpp delete mode 100644 tests/data/disk_with_hole.obj diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 232285cee9..7e02598d83 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -357,8 +357,6 @@ set(SLIC3R_SOURCES BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp BranchingTree/PointCloud.hpp - OrganicTree/OrganicTree.hpp - OrganicTree/OrganicTreeImpl.hpp Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp diff --git a/src/libslic3r/OrganicTree/OrganicTree.hpp b/src/libslic3r/OrganicTree/OrganicTree.hpp deleted file mode 100644 index cf34c9eb32..0000000000 --- a/src/libslic3r/OrganicTree/OrganicTree.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef ORGANICTREE_HPP -#define ORGANICTREE_HPP - -#include -#include -#include - -namespace Slic3r { namespace organictree { - -enum class NodeType { Bed, Mesh, Junction }; - -template struct DomainTraits_ { - using Node = typename T::Node; - - static void push(const T &dom, const Node &n) - { - dom.push_junction(n); - } - - static Node pop(T &dom) { return dom.pop(); } - - static bool empty(const T &dom) { return dom.empty(); } - - static std::optional> - closest(const T &dom, const Node &n) - { - return dom.closest(n); - } - - static Node merge_node(const T &dom, const Node &a, const Node &b) - { - return dom.merge_node(a, b); - } - - static void bridge(T &dom, const Node &from, const Node &to) - { - dom.bridge(from, to); - } - - static void anchor(T &dom, const Node &from, const Node &to) - { - dom.anchor(from, to); - } - - static void pillar(T &dom, const Node &from, const Node &to) - { - dom.pillar(from, to); - } - - static void merge (T &dom, const Node &n1, const Node &n2, const Node &mrg) - { - dom.merge(n1, n2, mrg); - } - - static void report_fail(T &dom, const Node &n) { dom.report_fail(n); } -}; - -template -void build_tree(Domain &&D) -{ - using Dom = DomainTraits_>>; - using Node = typename Dom::Node; - - while (! Dom::empty(D)) { - Node n = Dom::pop(D); - - std::optional> C = Dom::closest(D, n); - - if (!C) { - Dom::report_fail(D, n); - } else switch (C->second) { - case NodeType::Bed: - Dom::pillar(D, n, C->first); - break; - case NodeType::Mesh: - Dom::anchor(D, n, C->first); - break; - case NodeType::Junction: { - Node M = Dom::merge_node(D, n, C->first); - - if (M == C->first) { - Dom::bridge(D, n, C->first); - } else { - Dom::push(D, M); - Dom::merge(D, n, M, C->first); - } - break; - } - } - } -} - -}} // namespace Slic3r::organictree - -#endif // ORGANICTREE_HPP diff --git a/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp b/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp deleted file mode 100644 index 6e18550dac..0000000000 --- a/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef ORGANICTREEIMPL_HPP -#define ORGANICTREEIMPL_HPP - -namespace Slic3r { namespace organictree { - - - - -}} - -#endif // ORGANICTREEIMPL_HPP diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index e0f3acb701..c22cdf606a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -12,8 +12,6 @@ #include #include -#include - #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp" @@ -1021,8 +1019,6 @@ void GLGizmoSlaSupports::select_point(int i) m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; } else { - if (!m_editing_cache[i].selected) - BOOST_LOG_TRIVIAL(debug) << "Support point selected [" << i << "]: " << m_editing_cache[i].support_point.pos.transpose() << " \tnormal: " << m_editing_cache[i].normal.transpose(); m_editing_cache[i].selected = true; m_selection_empty = false; m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; diff --git a/tests/data/disk_with_hole.obj b/tests/data/disk_with_hole.obj deleted file mode 100644 index f16cafb87a..0000000000 --- a/tests/data/disk_with_hole.obj +++ /dev/null @@ -1,1696 +0,0 @@ -#### -# -# OBJ File Generated by Meshlab -# -#### -# Object disk_with_hole.obj -# -# Vertices: 720 -# Faces: 240 -# -#### -vn -0.328454 -0.737720 0.000000 -v -25.000000 -43.301270 -5.000000 -vn -0.310446 -0.697273 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.638901 -1.434994 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.328454 -0.737720 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.310446 -0.697273 0.000000 -v -25.000000 -43.301270 -5.000000 -vn -0.638901 -1.434994 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.725904 -0.235860 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -0.768012 -0.249542 0.000000 -v -48.907379 -10.395584 5.000000 -vn -1.493916 -0.485403 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -0.725904 -0.235860 0.000000 -v -48.907379 -10.395584 5.000000 -vn -0.768012 -0.249542 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -1.493916 -0.485403 0.000000 -v -45.677273 -20.336832 5.000000 -vn 0.725904 0.235860 0.000000 -v 48.907379 10.395584 5.000000 -vn 0.768012 0.249542 0.000000 -v 45.677273 20.336832 -5.000000 -vn 1.493916 0.485403 0.000000 -v 45.677273 20.336832 5.000000 -vn 0.725904 0.235860 0.000000 -v 45.677273 20.336832 -5.000000 -vn 0.768012 0.249542 0.000000 -v 48.907379 10.395584 5.000000 -vn 1.493916 0.485403 0.000000 -v 48.907379 10.395584 -5.000000 -vn 0.759080 0.079783 0.000000 -v 50.000000 0.000000 5.000000 -vn 0.803112 0.084411 0.000000 -v 48.907379 10.395584 -5.000000 -vn 1.562191 0.164193 0.000000 -v 48.907379 10.395584 5.000000 -vn 0.759080 0.079783 0.000000 -v 48.907379 10.395584 -5.000000 -vn 0.803112 0.084411 0.000000 -v 50.000000 0.000000 5.000000 -vn 1.562191 0.164193 0.000000 -v 50.000000 0.000000 -5.000000 -vn 0.661003 0.381630 0.000000 -v 45.677273 20.336832 5.000000 -vn 0.699346 0.403768 0.000000 -v 40.450851 29.389263 -5.000000 -vn 1.360350 0.785398 0.000000 -v 40.450851 29.389263 5.000000 -vn 0.661003 0.381630 0.000000 -v 40.450851 29.389263 -5.000000 -vn 0.699346 0.403768 0.000000 -v 45.677273 20.336832 5.000000 -vn 1.360350 0.785398 0.000000 -v 45.677273 20.336832 -5.000000 -vn 0.474657 -0.653310 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.448633 -0.617491 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.923291 -1.270801 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.474657 -0.653310 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.448633 -0.617491 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.923291 -1.270801 0.000000 -v 33.456528 -37.157242 -5.000000 -vn 0.328454 0.737720 0.000000 -v 25.000000 43.301270 -5.000000 -vn 0.310446 0.697273 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.638901 1.434994 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.328454 0.737720 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.310446 0.697273 0.000000 -v 25.000000 43.301270 -5.000000 -vn 0.638901 1.434994 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.759080 -0.079783 0.000000 -v 48.907379 -10.395584 5.000000 -vn 0.803112 -0.084411 0.000000 -v 50.000000 0.000000 -5.000000 -vn 1.562191 -0.164193 0.000000 -v 50.000000 0.000000 5.000000 -vn 0.759080 -0.079783 0.000000 -v 50.000000 0.000000 -5.000000 -vn 0.803112 -0.084411 0.000000 -v 48.907379 -10.395584 5.000000 -vn 1.562191 -0.164193 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 0.567213 0.510721 0.000000 -v 40.450851 29.389263 5.000000 -vn 0.600116 0.540347 0.000000 -v 33.456528 37.157242 -5.000000 -vn 1.167329 1.051068 0.000000 -v 33.456528 37.157242 5.000000 -vn 0.567213 0.510721 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.600116 0.540347 0.000000 -v 40.450851 29.389263 5.000000 -vn 1.167329 1.051068 0.000000 -v 40.450851 29.389263 -5.000000 -vn -0.661003 0.381630 0.000000 -v -45.677273 20.336832 -5.000000 -vn -0.699346 0.403768 0.000000 -v -40.450851 29.389263 5.000000 -vn -1.360350 0.785398 0.000000 -v -40.450851 29.389263 -5.000000 -vn -0.661003 0.381630 0.000000 -v -40.450851 29.389263 5.000000 -vn -0.699346 0.403768 0.000000 -v -45.677273 20.336832 -5.000000 -vn -1.360350 0.785398 0.000000 -v -45.677273 20.336832 5.000000 -vn 0.567213 -0.510721 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.600116 -0.540347 0.000000 -v 40.450851 -29.389263 -5.000000 -vn 1.167329 -1.051068 0.000000 -v 40.450851 -29.389263 5.000000 -vn 0.567213 -0.510721 0.000000 -v 40.450851 -29.389263 -5.000000 -vn 0.600116 -0.540347 0.000000 -v 33.456528 -37.157242 5.000000 -vn 1.167329 -1.051068 0.000000 -v 33.456528 -37.157242 -5.000000 -vn -0.567213 0.510721 0.000000 -v -40.450851 29.389263 -5.000000 -vn -0.600116 0.540347 0.000000 -v -33.456528 37.157242 5.000000 -vn -1.167329 1.051068 0.000000 -v -33.456528 37.157242 -5.000000 -vn -0.567213 0.510721 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.600116 0.540347 0.000000 -v -40.450851 29.389263 -5.000000 -vn -1.167329 1.051068 0.000000 -v -40.450851 29.389263 5.000000 -vn 0.661003 -0.381630 0.000000 -v 40.450851 -29.389263 5.000000 -vn 0.699346 -0.403768 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 1.360350 -0.785398 0.000000 -v 45.677273 -20.336832 5.000000 -vn 0.661003 -0.381630 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 0.699346 -0.403768 0.000000 -v 40.450851 -29.389263 5.000000 -vn 1.360350 -0.785398 0.000000 -v 40.450851 -29.389263 -5.000000 -vn -0.474657 0.653310 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.448633 0.617491 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.923291 1.270801 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.474657 0.653310 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.448633 0.617491 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.923291 1.270801 0.000000 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 0.397030 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 0.971546 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.773017 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 0.533261 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 0.983586 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.624746 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 0.935258 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 0.079637 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 2.126698 -v 28.090170 14.122147 5.000000 -vn 0.000000 0.000000 0.710460 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.068680 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.362453 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 0.095914 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.821343 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 1.224335 -v 28.090170 14.122147 5.000000 -vn 0.000000 0.000000 0.849935 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 1.114545 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.177112 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 0.129351 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.696999 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.315244 -v 29.781475 17.920883 5.000000 -vn 0.000000 0.000000 0.109378 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 2.035788 -v 29.781475 17.920883 5.000000 -vn 0.000000 0.000000 0.996427 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 0.131250 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 2.066767 -v 29.781475 22.079117 5.000000 -vn 0.000000 0.000000 0.943575 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 0.161065 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.696263 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.000000 1.284264 -v 29.781475 22.079117 5.000000 -vn 0.000000 0.000000 0.162839 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.323985 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 1.654769 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.000000 0.163690 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.800790 -v 26.691305 27.431448 5.000000 -vn 0.000000 0.000000 1.177113 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 0.797639 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 1.263865 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.080089 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.175248 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.416103 -v 25.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.550242 -v 26.691305 27.431448 5.000000 -vn 0.000000 0.000000 0.152239 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.054425 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 1.934929 -v 25.000000 28.660254 5.000000 -vn 0.000000 0.000000 0.150263 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.492361 -v 21.045284 29.945217 5.000000 -vn 0.000000 0.000000 1.498969 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 0.137161 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.145761 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 1.858670 -v 21.045284 29.945217 5.000000 -vn 0.000000 0.000000 0.955486 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.621466 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 1.564641 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.583804 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 0.115742 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.442047 -v 16.909828 29.510565 5.000000 -vn 0.000000 0.000000 0.104548 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.128057 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.908987 -v 16.909828 29.510565 5.000000 -vn 0.000000 0.000000 0.926960 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.458257 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.756376 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.764718 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 0.086613 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.290263 -v 13.308694 27.431448 5.000000 -vn 0.000000 0.000000 2.060768 -v 13.308694 27.431448 5.000000 -vn 0.000000 0.000000 0.074549 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.006277 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 0.947726 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.349832 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 1.844034 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.938074 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.282044 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 1.921476 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.994924 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 0.062953 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.083717 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 1.054154 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.068696 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 2.018744 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 1.891912 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 0.312011 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.937670 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.099058 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 0.057667 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.984868 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.936816 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 0.258266 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 0.946511 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.099133 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 0.048750 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.993709 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 1.936892 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 0.223894 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 0.980807 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.071817 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 0.041770 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 2.028005 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 1.909575 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 0.200963 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 1.031054 -v 33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 1.901099 -v 33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 0.185196 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 1.055297 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.212075 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 0.036463 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.893055 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 0.174414 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.126786 -v 15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.840393 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 0.032941 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.798030 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.310621 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.031018 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.721914 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.388663 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 0.030639 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.500477 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.610479 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 0.031757 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.271469 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.838368 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 0.034542 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.178902 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.928151 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 0.038851 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.051199 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 2.051544 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.985271 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 0.052938 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.103383 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 0.166514 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.169712 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.805367 -v 15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 0.961197 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.239254 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 1.941141 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.161147 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.250946 -v -5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.729500 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 2.008394 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 0.045050 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.088148 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 0.157350 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.303035 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.681207 -v -5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.004346 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 0.211341 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.925906 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.154864 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.388627 -v -25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.598102 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 0.192291 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 1.888957 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.060345 -v -40.450851 29.389263 5.000000 -vn 0.000000 0.000000 0.153677 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.444390 -v -33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 1.543527 -v -25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 0.179392 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 1.871808 -v -40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.090393 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 0.153352 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.500477 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.487764 -v -33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 0.170109 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.807220 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.164264 -v -48.907379 10.395584 5.000000 -vn 0.000000 0.000000 0.154128 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.586425 -v -45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.401039 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 0.163654 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.767889 -v -48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.210049 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 0.156018 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.639846 -v -48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.345728 -v -45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 0.158937 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.690347 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.292307 -v -48.907379 -10.395584 5.000000 -vn 0.725904 -0.235860 0.000000 -v 45.677273 -20.336832 5.000000 -vn 0.768012 -0.249542 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 1.493916 -0.485403 0.000000 -v 48.907379 -10.395584 5.000000 -vn 0.725904 -0.235860 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 0.768012 -0.249542 0.000000 -v 45.677273 -20.336832 5.000000 -vn 1.493916 -0.485403 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.807535 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.763261 0.000000 -v -5.226422 49.726093 5.000000 -vn 0.000000 1.570796 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.807535 0.000000 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.763261 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.000000 1.570796 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.474657 -0.653310 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.448633 -0.617491 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.923291 -1.270801 0.000000 -v -33.456528 -37.157242 5.000000 -vn -0.474657 -0.653310 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.448633 -0.617491 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.923291 -1.270801 0.000000 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.312011 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.891912 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.937670 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.258266 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -1.936816 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.946511 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.068696 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.054154 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -2.018744 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -0.223894 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -1.936892 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.980807 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.079637 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.935258 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -2.126698 -v 28.090170 14.122147 -5.000000 -vn 0.000000 0.000000 -0.200963 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.909575 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.031054 -v 33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -2.035788 -v 29.781475 17.920883 -5.000000 -vn 0.000000 0.000000 -0.109378 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.996427 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -1.068680 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.710460 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.362453 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -2.066767 -v 29.781475 22.079117 -5.000000 -vn 0.000000 0.000000 -0.131250 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.943575 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.696999 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -0.129351 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.315244 -v 29.781475 17.920883 -5.000000 -vn 0.000000 0.000000 -0.983586 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.533261 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -1.624746 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.821343 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -0.095914 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -1.224335 -v 28.090170 14.122147 -5.000000 -vn 0.000000 0.000000 -0.185196 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.901099 -v 33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -1.055297 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.971546 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.397030 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -1.773017 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.057667 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -1.099058 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -1.984868 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -0.048750 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -1.099133 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -1.993709 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -0.041770 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.071817 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -2.028005 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -0.036463 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.212075 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.893055 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.126786 -v 15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -0.174414 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.840393 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.169712 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -0.166514 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.805367 -v 15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.798030 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -0.032941 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.310621 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.250946 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -0.161147 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.729500 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.303035 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -0.157350 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.681207 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.721914 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -0.031018 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.388663 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.388627 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.154864 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.598102 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.444390 -v -33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -0.153677 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.543527 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.696263 -v 29.135454 24.067366 -5.000000 -vn 0.000000 0.000000 -0.161065 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.284264 -v 29.781475 22.079117 -5.000000 -vn 0.000000 0.000000 -1.323985 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -0.162839 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.654769 -v 29.135454 24.067366 -5.000000 -vn 0.000000 0.000000 -1.114545 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.849935 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -1.177112 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.800790 -v 26.691305 27.431448 -5.000000 -vn 0.000000 0.000000 -0.163690 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.177113 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -1.416103 -v 25.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.175248 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.550242 -v 26.691305 27.431448 -5.000000 -vn 0.000000 0.000000 -1.054425 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -0.152239 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.934929 -v 25.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -1.263865 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -0.797639 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -1.080089 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.492361 -v 21.045284 29.945217 -5.000000 -vn 0.000000 0.000000 -0.150263 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.498969 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -1.145761 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -0.137161 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.858670 -v 21.045284 29.945217 -5.000000 -vn 0.000000 0.000000 -0.115742 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.583804 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -1.442047 -v 16.909828 29.510565 -5.000000 -vn 0.000000 0.000000 -0.621466 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -0.955486 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.564641 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.128057 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.104548 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.908987 -v 16.909828 29.510565 -5.000000 -vn 0.000000 0.000000 -0.086613 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.764718 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -1.290263 -v 13.308694 27.431448 -5.000000 -vn 0.000000 0.000000 -0.074549 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -2.060768 -v 13.308694 27.431448 -5.000000 -vn 0.000000 0.000000 -1.006277 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -0.062953 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.994924 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -1.083717 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -0.052938 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.985271 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -1.103383 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -0.458257 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.926960 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.756376 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -0.045050 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -2.008394 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -1.088148 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.500477 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.153352 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.487764 -v -33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -1.500477 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -0.030639 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.610479 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -0.349832 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -0.947726 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.844034 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.586425 -v -45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.154128 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.401039 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.282044 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -0.938074 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.921476 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.639846 -v -48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.156018 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.345728 -v -45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.239254 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -0.961197 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.941141 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.690347 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.158937 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.292307 -v -48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.211341 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.004346 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.925906 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.271469 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -0.031757 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.838368 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.051199 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -0.038851 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -2.051544 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.767889 -v -48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.163654 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.210049 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.888957 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -0.192291 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -1.060345 -v -40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.807220 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.170109 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.164264 -v -48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -1.871808 -v -40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.179392 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -1.090393 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.178902 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -0.034542 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.928151 -v 10.218524 17.920883 -5.000000 -vn -0.328454 0.737720 0.000000 -v -15.450850 47.552826 -5.000000 -vn -0.310446 0.697273 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.638901 1.434994 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.328454 0.737720 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.310446 0.697273 0.000000 -v -15.450850 47.552826 -5.000000 -vn -0.638901 1.434994 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.759080 -0.079783 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -0.803112 -0.084411 0.000000 -v -50.000000 0.000000 5.000000 -vn -1.562191 -0.164193 0.000000 -v -50.000000 0.000000 -5.000000 -vn -0.759080 -0.079783 0.000000 -v -50.000000 0.000000 5.000000 -vn -0.803112 -0.084411 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -1.562191 -0.164193 0.000000 -v -48.907379 -10.395584 5.000000 -vn -0.567213 -0.510721 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.600116 -0.540347 0.000000 -v -40.450851 -29.389263 5.000000 -vn -1.167329 -1.051068 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -0.567213 -0.510721 0.000000 -v -40.450851 -29.389263 5.000000 -vn -0.600116 -0.540347 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -1.167329 -1.051068 0.000000 -v -33.456528 -37.157242 5.000000 -vn -0.661003 -0.381630 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -0.699346 -0.403768 0.000000 -v -45.677273 -20.336832 5.000000 -vn -1.360350 -0.785398 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -0.661003 -0.381630 0.000000 -v -45.677273 -20.336832 5.000000 -vn -0.699346 -0.403768 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -1.360350 -0.785398 0.000000 -v -40.450851 -29.389263 5.000000 -vn 0.167896 -0.789889 0.000000 -v 5.226422 -49.726093 -5.000000 -vn 0.158691 -0.746582 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.326587 -1.536471 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.167896 -0.789889 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.158691 -0.746582 0.000000 -v 5.226422 -49.726093 -5.000000 -vn 0.326587 -1.536471 0.000000 -v 15.450850 -47.552826 -5.000000 -vn -0.759080 0.079783 0.000000 -v -50.000000 0.000000 -5.000000 -vn -0.803112 0.084411 0.000000 -v -48.907379 10.395584 5.000000 -vn -1.562191 0.164193 0.000000 -v -48.907379 10.395584 -5.000000 -vn -0.759080 0.079783 0.000000 -v -48.907379 10.395584 5.000000 -vn -0.803112 0.084411 0.000000 -v -50.000000 0.000000 -5.000000 -vn -1.562191 0.164193 0.000000 -v -50.000000 0.000000 5.000000 -vn 0.167896 0.789889 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.158691 0.746582 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.326587 1.536471 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.167896 0.789889 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.158691 0.746582 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.326587 1.536471 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.474657 0.653310 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.448633 0.617491 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.923291 1.270801 0.000000 -v 33.456528 37.157242 5.000000 -vn 0.474657 0.653310 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.448633 0.617491 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.923291 1.270801 0.000000 -v 25.000000 43.301270 -5.000000 -vn -0.167896 0.789889 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.158691 0.746582 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.326587 1.536471 0.000000 -v -5.226422 49.726093 5.000000 -vn -0.167896 0.789889 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.158691 0.746582 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.326587 1.536471 0.000000 -v -15.450850 47.552826 -5.000000 -vn 0.328454 -0.737720 0.000000 -v 15.450850 -47.552826 -5.000000 -vn 0.310446 -0.697273 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.638901 -1.434994 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.328454 -0.737720 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.310446 -0.697273 0.000000 -v 15.450850 -47.552826 -5.000000 -vn 0.638901 -1.434994 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 -0.807535 0.000000 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 -0.763261 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.000000 -1.570796 0.000000 -v -5.226422 -49.726093 5.000000 -vn 0.000000 -0.807535 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.000000 -0.763261 0.000000 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 -1.570796 0.000000 -v 5.226422 -49.726093 -5.000000 -vn -0.167896 -0.789889 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.158691 -0.746582 0.000000 -v -5.226422 -49.726093 5.000000 -vn -0.326587 -1.536471 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.167896 -0.789889 0.000000 -v -5.226422 -49.726093 5.000000 -vn -0.158691 -0.746582 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.326587 -1.536471 0.000000 -v -5.226422 -49.726093 -5.000000 -vn -0.725904 0.235860 0.000000 -v -48.907379 10.395584 -5.000000 -vn -0.768012 0.249542 0.000000 -v -45.677273 20.336832 5.000000 -vn -1.493916 0.485403 0.000000 -v -45.677273 20.336832 -5.000000 -vn -0.725904 0.235860 0.000000 -v -45.677273 20.336832 5.000000 -vn -0.768012 0.249542 0.000000 -v -48.907379 10.395584 -5.000000 -vn -1.493916 0.485403 0.000000 -v -48.907379 10.395584 5.000000 -vn -1.297914 -0.421718 0.000000 -v 29.781475 22.079117 -5.000000 -vn -0.196002 -0.063685 0.000000 -v 29.135454 24.067366 5.000000 -vn -1.493916 -0.485403 0.000000 -v 29.135454 24.067366 -5.000000 -vn -1.297914 -0.421718 0.000000 -v 29.135454 24.067366 5.000000 -vn -0.196002 -0.063685 0.000000 -v 29.781475 22.079117 -5.000000 -vn -1.493916 -0.485403 0.000000 -v 29.781475 22.079117 5.000000 -vn -1.357231 -0.142651 0.000000 -v 30.000000 20.000000 -5.000000 -vn -0.204960 -0.021542 0.000000 -v 29.781475 22.079117 5.000000 -vn -1.562191 -0.164194 0.000000 -v 29.781475 22.079117 -5.000000 -vn -1.357231 -0.142651 0.000000 -v 29.781475 22.079117 5.000000 -vn -0.204960 -0.021542 0.000000 -v 30.000000 20.000000 -5.000000 -vn -1.562191 -0.164194 0.000000 -v 30.000000 20.000000 5.000000 -vn -1.181872 -0.682353 0.000000 -v 29.135454 24.067366 -5.000000 -vn -0.178478 -0.103044 0.000000 -v 28.090170 25.877853 5.000000 -vn -1.360350 -0.785397 0.000000 -v 28.090170 25.877853 -5.000000 -vn -1.181872 -0.682353 0.000000 -v 28.090170 25.877853 5.000000 -vn -0.178478 -0.103044 0.000000 -v 29.135454 24.067366 -5.000000 -vn -1.360350 -0.785397 0.000000 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.206089 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.000000 1.364708 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.000000 1.570796 0.000000 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.206089 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.000000 1.364708 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.000000 1.570796 0.000000 -v 18.954716 10.054781 -5.000000 -vn -0.083824 0.188272 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.555077 1.246722 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.638901 1.434994 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.083824 0.188272 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.555077 1.246722 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.638901 1.434994 0.000000 -v 23.090170 10.489434 -5.000000 -vn 1.297914 0.421717 0.000000 -v 10.864545 15.932633 5.000000 -vn 0.196002 0.063685 0.000000 -v 10.218524 17.920883 -5.000000 -vn 1.493916 0.485402 0.000000 -v 10.218524 17.920883 5.000000 -vn 1.297914 0.421717 0.000000 -v 10.218524 17.920883 -5.000000 -vn 0.196002 0.063685 0.000000 -v 10.864545 15.932633 5.000000 -vn 1.493916 0.485402 0.000000 -v 10.864545 15.932633 -5.000000 -vn -1.357231 0.142651 0.000000 -v 29.781475 17.920883 -5.000000 -vn -0.204960 0.021542 0.000000 -v 30.000000 20.000000 5.000000 -vn -1.562191 0.164194 0.000000 -v 30.000000 20.000000 -5.000000 -vn -1.357231 0.142651 0.000000 -v 30.000000 20.000000 5.000000 -vn -0.204960 0.021542 0.000000 -v 29.781475 17.920883 -5.000000 -vn -1.562191 0.164194 0.000000 -v 29.781475 17.920883 5.000000 -vn 1.014175 -0.913168 0.000000 -v 11.909829 25.877853 5.000000 -vn 0.153154 -0.137900 0.000000 -v 13.308694 27.431448 -5.000000 -vn 1.167328 -1.051069 0.000000 -v 13.308694 27.431448 5.000000 -vn 1.014175 -0.913168 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.153154 -0.137900 0.000000 -v 11.909829 25.877853 5.000000 -vn 1.167328 -1.051069 0.000000 -v 11.909829 25.877853 -5.000000 -vn 0.042848 0.201585 0.000000 -v 18.954716 10.054781 -5.000000 -vn 0.283738 1.334885 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.326586 1.536471 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.042848 0.201585 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.283738 1.334885 0.000000 -v 18.954716 10.054781 -5.000000 -vn 0.326586 1.536471 0.000000 -v 16.909828 10.489434 -5.000000 -vn -1.297914 0.421717 0.000000 -v 29.135454 15.932633 -5.000000 -vn -0.196002 0.063685 0.000000 -v 29.781475 17.920883 5.000000 -vn -1.493916 0.485402 0.000000 -v 29.781475 17.920883 -5.000000 -vn -1.297914 0.421717 0.000000 -v 29.781475 17.920883 5.000000 -vn -0.196002 0.063685 0.000000 -v 29.135454 15.932633 -5.000000 -vn -1.493916 0.485402 0.000000 -v 29.135454 15.932633 5.000000 -vn 0.083824 0.188271 0.000000 -v 16.909828 10.489434 -5.000000 -vn 0.555077 1.246722 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.638901 1.434994 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.083824 0.188271 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.555077 1.246722 0.000000 -v 16.909828 10.489434 -5.000000 -vn 0.638901 1.434994 0.000000 -v 15.000000 11.339746 -5.000000 -vn -0.042848 -0.201585 0.000000 -v 21.045284 29.945217 -5.000000 -vn -0.283738 -1.334886 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.326586 -1.536471 0.000000 -v 21.045284 29.945217 5.000000 -vn -0.042848 -0.201585 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.283738 -1.334886 0.000000 -v 21.045284 29.945217 -5.000000 -vn -0.326586 -1.536471 0.000000 -v 23.090170 29.510565 -5.000000 -vn 0.000000 -0.206089 0.000000 -v 18.954716 29.945217 -5.000000 -vn 0.000000 -1.364708 0.000000 -v 21.045284 29.945217 5.000000 -vn 0.000000 -1.570796 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.000000 -0.206089 0.000000 -v 21.045284 29.945217 5.000000 -vn 0.000000 -1.364708 0.000000 -v 18.954716 29.945217 -5.000000 -vn 0.000000 -1.570796 0.000000 -v 21.045284 29.945217 -5.000000 -vn -1.181872 0.682353 0.000000 -v 28.090170 14.122147 -5.000000 -vn -0.178478 0.103044 0.000000 -v 29.135454 15.932633 5.000000 -vn -1.360350 0.785398 0.000000 -v 29.135454 15.932633 -5.000000 -vn -1.181872 0.682353 0.000000 -v 29.135454 15.932633 5.000000 -vn -0.178478 0.103044 0.000000 -v 28.090170 14.122147 -5.000000 -vn -1.360350 0.785398 0.000000 -v 28.090170 14.122147 5.000000 -vn 1.297914 -0.421718 0.000000 -v 10.218524 22.079117 5.000000 -vn 0.196002 -0.063685 0.000000 -v 10.864545 24.067366 -5.000000 -vn 1.493916 -0.485403 0.000000 -v 10.864545 24.067366 5.000000 -vn 1.297914 -0.421718 0.000000 -v 10.864545 24.067366 -5.000000 -vn 0.196002 -0.063685 0.000000 -v 10.218524 22.079117 5.000000 -vn 1.493916 -0.485403 0.000000 -v 10.218524 22.079117 -5.000000 -vn 1.014175 0.913168 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.153154 0.137900 0.000000 -v 11.909829 14.122147 -5.000000 -vn 1.167329 1.051068 0.000000 -v 11.909829 14.122147 5.000000 -vn 1.014175 0.913168 0.000000 -v 11.909829 14.122147 -5.000000 -vn 0.153154 0.137900 0.000000 -v 13.308694 12.568551 5.000000 -vn 1.167329 1.051068 0.000000 -v 13.308694 12.568551 -5.000000 -vn -1.014175 -0.913168 0.000000 -v 28.090170 25.877853 -5.000000 -vn -0.153154 -0.137900 0.000000 -v 26.691305 27.431448 5.000000 -vn -1.167328 -1.051069 0.000000 -v 26.691305 27.431448 -5.000000 -vn -1.014175 -0.913168 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.153154 -0.137900 0.000000 -v 28.090170 25.877853 -5.000000 -vn -1.167328 -1.051069 0.000000 -v 28.090170 25.877853 5.000000 -vn 1.357232 -0.142651 0.000000 -v 10.000000 20.000000 5.000000 -vn 0.204960 -0.021542 0.000000 -v 10.218524 22.079117 -5.000000 -vn 1.562191 -0.164193 0.000000 -v 10.218524 22.079117 5.000000 -vn 1.357232 -0.142651 0.000000 -v 10.218524 22.079117 -5.000000 -vn 0.204960 -0.021542 0.000000 -v 10.000000 20.000000 5.000000 -vn 1.562191 -0.164193 0.000000 -v 10.000000 20.000000 -5.000000 -vn 0.042848 -0.201585 0.000000 -v 16.909828 29.510565 -5.000000 -vn 0.283737 -1.334885 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.326586 -1.536471 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.042848 -0.201585 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.283737 -1.334885 0.000000 -v 16.909828 29.510565 -5.000000 -vn 0.326586 -1.536471 0.000000 -v 18.954716 29.945217 -5.000000 -vn -0.121136 -0.166729 0.000000 -v 25.000000 28.660254 -5.000000 -vn -0.802155 -1.104072 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.923291 -1.270801 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.121136 -0.166729 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.802155 -1.104072 0.000000 -v 25.000000 28.660254 -5.000000 -vn -0.923291 -1.270801 0.000000 -v 26.691305 27.431448 -5.000000 -vn 0.083824 -0.188271 0.000000 -v 15.000000 28.660254 -5.000000 -vn 0.555077 -1.246722 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.638901 -1.434994 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.083824 -0.188271 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.555077 -1.246722 0.000000 -v 15.000000 28.660254 -5.000000 -vn 0.638901 -1.434994 0.000000 -v 16.909828 29.510565 -5.000000 -vn -1.014175 0.913168 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.153154 0.137900 0.000000 -v 28.090170 14.122147 5.000000 -vn -1.167329 1.051068 0.000000 -v 28.090170 14.122147 -5.000000 -vn -1.014175 0.913168 0.000000 -v 28.090170 14.122147 5.000000 -vn -0.153154 0.137900 0.000000 -v 26.691305 12.568551 -5.000000 -vn -1.167329 1.051068 0.000000 -v 26.691305 12.568551 5.000000 -vn -0.083824 -0.188272 0.000000 -v 23.090170 29.510565 -5.000000 -vn -0.555077 -1.246722 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.638901 -1.434994 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.083824 -0.188272 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.555077 -1.246722 0.000000 -v 23.090170 29.510565 -5.000000 -vn -0.638901 -1.434994 0.000000 -v 25.000000 28.660254 -5.000000 -vn 0.121136 0.166729 0.000000 -v 15.000000 11.339746 -5.000000 -vn 0.802155 1.104072 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.923291 1.270801 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.121136 0.166729 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.802155 1.104072 0.000000 -v 15.000000 11.339746 -5.000000 -vn 0.923291 1.270801 0.000000 -v 13.308694 12.568551 -5.000000 -vn -0.121136 0.166729 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.802155 1.104072 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.923291 1.270801 0.000000 -v 26.691305 12.568551 5.000000 -vn -0.121136 0.166729 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.802155 1.104072 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.923291 1.270801 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.042848 0.201585 0.000000 -v 23.090170 10.489434 -5.000000 -vn -0.283738 1.334885 0.000000 -v 21.045284 10.054781 5.000000 -vn -0.326587 1.536471 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.042848 0.201585 0.000000 -v 21.045284 10.054781 5.000000 -vn -0.283738 1.334885 0.000000 -v 23.090170 10.489434 -5.000000 -vn -0.326587 1.536471 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.121136 -0.166729 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.802155 -1.104072 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.923291 -1.270801 0.000000 -v 13.308694 27.431448 5.000000 -vn 0.121136 -0.166729 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.802155 -1.104072 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.923291 -1.270801 0.000000 -v 15.000000 28.660254 -5.000000 -vn 1.181872 0.682353 0.000000 -v 11.909829 14.122147 5.000000 -vn 0.178478 0.103044 0.000000 -v 10.864545 15.932633 -5.000000 -vn 1.360350 0.785398 0.000000 -v 10.864545 15.932633 5.000000 -vn 1.181872 0.682353 0.000000 -v 10.864545 15.932633 -5.000000 -vn 0.178478 0.103044 0.000000 -v 11.909829 14.122147 5.000000 -vn 1.360350 0.785398 0.000000 -v 11.909829 14.122147 -5.000000 -vn 1.357232 0.142651 0.000000 -v 10.218524 17.920883 5.000000 -vn 0.204960 0.021542 0.000000 -v 10.000000 20.000000 -5.000000 -vn 1.562191 0.164193 0.000000 -v 10.000000 20.000000 5.000000 -vn 1.357232 0.142651 0.000000 -v 10.000000 20.000000 -5.000000 -vn 0.204960 0.021542 0.000000 -v 10.218524 17.920883 5.000000 -vn 1.562191 0.164193 0.000000 -v 10.218524 17.920883 -5.000000 -vn 1.181872 -0.682353 0.000000 -v 10.864545 24.067366 5.000000 -vn 0.178478 -0.103044 0.000000 -v 11.909829 25.877853 -5.000000 -vn 1.360350 -0.785397 0.000000 -v 11.909829 25.877853 5.000000 -vn 1.181872 -0.682353 0.000000 -v 11.909829 25.877853 -5.000000 -vn 0.178478 -0.103044 0.000000 -v 10.864545 24.067366 5.000000 -vn 1.360350 -0.785397 0.000000 -v 10.864545 24.067366 -5.000000 -# 720 vertices, 0 vertices normals - -f 1//1 2//2 3//3 -f 4//4 5//5 6//6 -f 7//7 8//8 9//9 -f 10//10 11//11 12//12 -f 13//13 14//14 15//15 -f 16//16 17//17 18//18 -f 19//19 20//20 21//21 -f 22//22 23//23 24//24 -f 25//25 26//26 27//27 -f 28//28 29//29 30//30 -f 31//31 32//32 33//33 -f 34//34 35//35 36//36 -f 37//37 38//38 39//39 -f 40//40 41//41 42//42 -f 43//43 44//44 45//45 -f 46//46 47//47 48//48 -f 49//49 50//50 51//51 -f 52//52 53//53 54//54 -f 55//55 56//56 57//57 -f 58//58 59//59 60//60 -f 61//61 62//62 63//63 -f 64//64 65//65 66//66 -f 67//67 68//68 69//69 -f 70//70 71//71 72//72 -f 73//73 74//74 75//75 -f 76//76 77//77 78//78 -f 79//79 80//80 81//81 -f 82//82 83//83 84//84 -f 85//85 86//86 87//87 -f 88//88 89//89 90//90 -f 91//91 92//92 93//93 -f 94//94 95//95 96//96 -f 97//97 98//98 99//99 -f 100//100 101//101 102//102 -f 103//103 104//104 105//105 -f 106//106 107//107 108//108 -f 109//109 110//110 111//111 -f 112//112 113//113 114//114 -f 115//115 116//116 117//117 -f 118//118 119//119 120//120 -f 121//121 122//122 123//123 -f 124//124 125//125 126//126 -f 127//127 128//128 129//129 -f 130//130 131//131 132//132 -f 133//133 134//134 135//135 -f 136//136 137//137 138//138 -f 139//139 140//140 141//141 -f 142//142 143//143 144//144 -f 145//145 146//146 147//147 -f 148//148 149//149 150//150 -f 151//151 152//152 153//153 -f 154//154 155//155 156//156 -f 157//157 158//158 159//159 -f 160//160 161//161 162//162 -f 163//163 164//164 165//165 -f 166//166 167//167 168//168 -f 169//169 170//170 171//171 -f 172//172 173//173 174//174 -f 175//175 176//176 177//177 -f 178//178 179//179 180//180 -f 181//181 182//182 183//183 -f 184//184 185//185 186//186 -f 187//187 188//188 189//189 -f 190//190 191//191 192//192 -f 193//193 194//194 195//195 -f 196//196 197//197 198//198 -f 199//199 200//200 201//201 -f 202//202 203//203 204//204 -f 205//205 206//206 207//207 -f 208//208 209//209 210//210 -f 211//211 212//212 213//213 -f 214//214 215//215 216//216 -f 217//217 218//218 219//219 -f 220//220 221//221 222//222 -f 223//223 224//224 225//225 -f 226//226 227//227 228//228 -f 229//229 230//230 231//231 -f 232//232 233//233 234//234 -f 235//235 236//236 237//237 -f 238//238 239//239 240//240 -f 241//241 242//242 243//243 -f 244//244 245//245 246//246 -f 247//247 248//248 249//249 -f 250//250 251//251 252//252 -f 253//253 254//254 255//255 -f 256//256 257//257 258//258 -f 259//259 260//260 261//261 -f 262//262 263//263 264//264 -f 265//265 266//266 267//267 -f 268//268 269//269 270//270 -f 271//271 272//272 273//273 -f 274//274 275//275 276//276 -f 277//277 278//278 279//279 -f 280//280 281//281 282//282 -f 283//283 284//284 285//285 -f 286//286 287//287 288//288 -f 289//289 290//290 291//291 -f 292//292 293//293 294//294 -f 295//295 296//296 297//297 -f 298//298 299//299 300//300 -f 301//301 302//302 303//303 -f 304//304 305//305 306//306 -f 307//307 308//308 309//309 -f 310//310 311//311 312//312 -f 313//313 314//314 315//315 -f 316//316 317//317 318//318 -f 319//319 320//320 321//321 -f 322//322 323//323 324//324 -f 325//325 326//326 327//327 -f 328//328 329//329 330//330 -f 331//331 332//332 333//333 -f 334//334 335//335 336//336 -f 337//337 338//338 339//339 -f 340//340 341//341 342//342 -f 343//343 344//344 345//345 -f 346//346 347//347 348//348 -f 349//349 350//350 351//351 -f 352//352 353//353 354//354 -f 355//355 356//356 357//357 -f 358//358 359//359 360//360 -f 361//361 362//362 363//363 -f 364//364 365//365 366//366 -f 367//367 368//368 369//369 -f 370//370 371//371 372//372 -f 373//373 374//374 375//375 -f 376//376 377//377 378//378 -f 379//379 380//380 381//381 -f 382//382 383//383 384//384 -f 385//385 386//386 387//387 -f 388//388 389//389 390//390 -f 391//391 392//392 393//393 -f 394//394 395//395 396//396 -f 397//397 398//398 399//399 -f 400//400 401//401 402//402 -f 403//403 404//404 405//405 -f 406//406 407//407 408//408 -f 409//409 410//410 411//411 -f 412//412 413//413 414//414 -f 415//415 416//416 417//417 -f 418//418 419//419 420//420 -f 421//421 422//422 423//423 -f 424//424 425//425 426//426 -f 427//427 428//428 429//429 -f 430//430 431//431 432//432 -f 433//433 434//434 435//435 -f 436//436 437//437 438//438 -f 439//439 440//440 441//441 -f 442//442 443//443 444//444 -f 445//445 446//446 447//447 -f 448//448 449//449 450//450 -f 451//451 452//452 453//453 -f 454//454 455//455 456//456 -f 457//457 458//458 459//459 -f 460//460 461//461 462//462 -f 463//463 464//464 465//465 -f 466//466 467//467 468//468 -f 469//469 470//470 471//471 -f 472//472 473//473 474//474 -f 475//475 476//476 477//477 -f 478//478 479//479 480//480 -f 481//481 482//482 483//483 -f 484//484 485//485 486//486 -f 487//487 488//488 489//489 -f 490//490 491//491 492//492 -f 493//493 494//494 495//495 -f 496//496 497//497 498//498 -f 499//499 500//500 501//501 -f 502//502 503//503 504//504 -f 505//505 506//506 507//507 -f 508//508 509//509 510//510 -f 511//511 512//512 513//513 -f 514//514 515//515 516//516 -f 517//517 518//518 519//519 -f 520//520 521//521 522//522 -f 523//523 524//524 525//525 -f 526//526 527//527 528//528 -f 529//529 530//530 531//531 -f 532//532 533//533 534//534 -f 535//535 536//536 537//537 -f 538//538 539//539 540//540 -f 541//541 542//542 543//543 -f 544//544 545//545 546//546 -f 547//547 548//548 549//549 -f 550//550 551//551 552//552 -f 553//553 554//554 555//555 -f 556//556 557//557 558//558 -f 559//559 560//560 561//561 -f 562//562 563//563 564//564 -f 565//565 566//566 567//567 -f 568//568 569//569 570//570 -f 571//571 572//572 573//573 -f 574//574 575//575 576//576 -f 577//577 578//578 579//579 -f 580//580 581//581 582//582 -f 583//583 584//584 585//585 -f 586//586 587//587 588//588 -f 589//589 590//590 591//591 -f 592//592 593//593 594//594 -f 595//595 596//596 597//597 -f 598//598 599//599 600//600 -f 601//601 602//602 603//603 -f 604//604 605//605 606//606 -f 607//607 608//608 609//609 -f 610//610 611//611 612//612 -f 613//613 614//614 615//615 -f 616//616 617//617 618//618 -f 619//619 620//620 621//621 -f 622//622 623//623 624//624 -f 625//625 626//626 627//627 -f 628//628 629//629 630//630 -f 631//631 632//632 633//633 -f 634//634 635//635 636//636 -f 637//637 638//638 639//639 -f 640//640 641//641 642//642 -f 643//643 644//644 645//645 -f 646//646 647//647 648//648 -f 649//649 650//650 651//651 -f 652//652 653//653 654//654 -f 655//655 656//656 657//657 -f 658//658 659//659 660//660 -f 661//661 662//662 663//663 -f 664//664 665//665 666//666 -f 667//667 668//668 669//669 -f 670//670 671//671 672//672 -f 673//673 674//674 675//675 -f 676//676 677//677 678//678 -f 679//679 680//680 681//681 -f 682//682 683//683 684//684 -f 685//685 686//686 687//687 -f 688//688 689//689 690//690 -f 691//691 692//692 693//693 -f 694//694 695//695 696//696 -f 697//697 698//698 699//699 -f 700//700 701//701 702//702 -f 703//703 704//704 705//705 -f 706//706 707//707 708//708 -f 709//709 710//710 711//711 -f 712//712 713//713 714//714 -f 715//715 716//716 717//717 -f 718//718 719//719 720//720 -# 240 faces, 0 coords texture - -# End of File From 878f3b30ddb41b6ffee37c96a4ff1e220b2d3951 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 17:46:27 +0100 Subject: [PATCH 57/86] wip adding separate config values for support tree algorithms --- src/libslic3r/Preset.cpp | 20 ++ src/libslic3r/PrintConfig.cpp | 366 +++++++++++++------------- src/libslic3r/PrintConfig.hpp | 57 ++++ src/libslic3r/SLAPrint.cpp | 80 ++++-- src/slic3r/GUI/ConfigManipulation.cpp | 55 ++-- src/slic3r/GUI/Tab.cpp | 96 +++++-- src/slic3r/GUI/Tab.hpp | 5 +- 7 files changed, 432 insertions(+), 247 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ce3c27bf64..9dad49d4c4 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -495,6 +495,7 @@ static std::vector s_Preset_sla_print_options { "faded_layers", "supports_enable", "support_tree_type", + "support_head_front_diameter", "support_head_penetration", "support_head_width", @@ -512,6 +513,25 @@ static std::vector s_Preset_sla_print_options { "support_max_bridge_length", "support_max_pillar_link_distance", "support_object_elevation", + + "branchingsupport_head_front_diameter", + "branchingsupport_head_penetration", + "branchingsupport_head_width", + "branchingsupport_pillar_diameter", + "branchingsupport_small_pillar_diameter_percent", + "branchingsupport_max_bridges_on_pillar", + "branchingsupport_max_weight_on_model", + "branchingsupport_pillar_connection_mode", + "branchingsupport_buildplate_only", + "branchingsupport_pillar_widening_factor", + "branchingsupport_base_diameter", + "branchingsupport_base_height", + "branchingsupport_base_safety_distance", + "branchingsupport_critical_angle", + "branchingsupport_max_bridge_length", + "branchingsupport_max_pillar_link_distance", + "branchingsupport_object_elevation", + "support_points_density_relative", "support_points_minimal_distance", "slice_closing_radius", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ebdfd2ff68..f39857759e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3273,6 +3273,191 @@ void PrintConfigDef::init_extruder_option_keys() assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); } +void PrintConfigDef::init_sla_support_params(const std::string &prefix) +{ + ConfigOptionDef* def; + + def = this->add(prefix + "support_head_front_diameter", coFloat); + def->label = L("Pinhead front diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter of the pointing side of the head"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add(prefix + "support_head_penetration", coFloat); + def->label = L("Head penetration"); + def->category = L("Supports"); + def->tooltip = L("How much the pinhead has to penetrate the model surface"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.2)); + + def = this->add(prefix + "support_head_width", coFloat); + def->label = L("Pinhead width"); + def->category = L("Supports"); + def->tooltip = L("Width from the back sphere center to the front sphere center"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 20; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add(prefix + "support_pillar_diameter", coFloat); + def->label = L("Pillar diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter in mm of the support pillars"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 15; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add(prefix + "support_small_pillar_diameter_percent", coPercent); + def->label = L("Small pillar diameter percent"); + def->category = L("Supports"); + def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter " + "which are used in problematic areas where a normal pilla cannot fit."); + def->sidetext = L("%"); + def->min = 1; + def->max = 100; + def->mode = comExpert; + def->set_default_value(new ConfigOptionPercent(50)); + + def = this->add(prefix + "support_max_bridges_on_pillar", coInt); + def->label = L("Max bridges on a pillar"); + def->tooltip = L( + "Maximum number of bridges that can be placed on a pillar. Bridges " + "hold support point pinheads and connect to pillars as small branches."); + def->min = 0; + def->max = 50; + def->mode = comExpert; + def->set_default_value(new ConfigOptionInt(3)); + + def = this->add(prefix + "support_max_weight_on_model", coFloat); + def->label = L("Max weight on model"); + def->category = L("Supports"); + def->tooltip = L( + "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " + "branches emanating from the endpoint."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(10.)); + + def = this->add(prefix + "support_pillar_connection_mode", coEnum); + def->label = L("Pillar connection mode"); + def->tooltip = L("Controls the bridge type between two neighboring pillars." + " Can be zig-zag, cross (double zig-zag) or dynamic which" + " will automatically switch between the first two depending" + " on the distance of the two pillars."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = ConfigOptionEnum::get_enum_names(); + def->enum_labels = ConfigOptionEnum::get_enum_names(); + def->enum_labels[0] = L("Zig-Zag"); + def->enum_labels[1] = L("Cross"); + def->enum_labels[2] = L("Dynamic"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic)); + + def = this->add(prefix + "support_buildplate_only", coBool); + def->label = L("Support on build plate only"); + def->category = L("Supports"); + def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add(prefix + "support_pillar_widening_factor", coFloat); + def->label = L("Pillar widening factor"); + def->category = L("Supports"); + def->tooltip = L( + "Merging bridges or pillars into another pillars can " + "increase the radius. Zero means no increase, one means " + "full increase. The exact amount of increase is unspecified and can " + "change in the future. What is garanteed is that thickness will not " + "exceed \"support_base_diameter\""); + + def->min = 0; + def->max = 1; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.5)); + + def = this->add(prefix + "support_base_diameter", coFloat); + def->label = L("Support base diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter in mm of the pillar base"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 30; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(4.0)); + + def = this->add(prefix + "support_base_height", coFloat); + def->label = L("Support base height"); + def->category = L("Supports"); + def->tooltip = L("The height of the pillar base cone"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add(prefix + "support_base_safety_distance", coFloat); + def->label = L("Support base safety distance"); + def->category = L("Supports"); + def->tooltip = L( + "The minimum distance of the pillar base from the model in mm. " + "Makes sense in zero elevation mode where a gap according " + "to this parameter is inserted between the model and the pad."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1)); + + def = this->add(prefix + "support_critical_angle", coFloat); + def->label = L("Critical angle"); + def->category = L("Supports"); + def->tooltip = L("The default angle for connecting support sticks and junctions."); + def->sidetext = L("°"); + def->min = 0; + def->max = 90; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(45)); + + def = this->add(prefix + "support_max_bridge_length", coFloat); + def->label = L("Max bridge length"); + def->category = L("Supports"); + def->tooltip = L("The max length of a bridge"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(15.0)); + + def = this->add(prefix + "support_max_pillar_link_distance", coFloat); + def->label = L("Max pillar linking distance"); + def->category = L("Supports"); + def->tooltip = L("The max distance of two pillars to get linked with each other." + " A zero value will prohibit pillar cascading."); + def->sidetext = L("mm"); + def->min = 0; // 0 means no linking + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(10.0)); + + def = this->add(prefix + "support_object_elevation", coFloat); + def->label = L("Object elevation"); + def->category = L("Supports"); + def->tooltip = L("How much the supports should lift up the supported object. " + "If \"Pad around object\" is enabled, this value is ignored."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 150; // This is the max height of print on SL1 + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.0)); +} + void PrintConfigDef::init_sla_params() { ConfigOptionDef* def; @@ -3619,185 +3804,8 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); - def = this->add("support_head_front_diameter", coFloat); - def->label = L("Pinhead front diameter"); - def->category = L("Supports"); - def->tooltip = L("Diameter of the pointing side of the head"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.4)); - - def = this->add("support_head_penetration", coFloat); - def->label = L("Head penetration"); - def->category = L("Supports"); - def->tooltip = L("How much the pinhead has to penetrate the model surface"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionFloat(0.2)); - - def = this->add("support_head_width", coFloat); - def->label = L("Pinhead width"); - def->category = L("Supports"); - def->tooltip = L("Width from the back sphere center to the front sphere center"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 20; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_pillar_diameter", coFloat); - def->label = L("Pillar diameter"); - def->category = L("Supports"); - def->tooltip = L("Diameter in mm of the support pillars"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 15; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_small_pillar_diameter_percent", coPercent); - def->label = L("Small pillar diameter percent"); - def->category = L("Supports"); - def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter " - "which are used in problematic areas where a normal pilla cannot fit."); - def->sidetext = L("%"); - def->min = 1; - def->max = 100; - def->mode = comExpert; - def->set_default_value(new ConfigOptionPercent(50)); - - def = this->add("support_max_bridges_on_pillar", coInt); - def->label = L("Max bridges on a pillar"); - def->tooltip = L( - "Maximum number of bridges that can be placed on a pillar. Bridges " - "hold support point pinheads and connect to pillars as small branches."); - def->min = 0; - def->max = 50; - def->mode = comExpert; - def->set_default_value(new ConfigOptionInt(3)); - - def = this->add("support_max_weight_on_model", coFloat); - def->label = L("Max weight on model"); - def->category = L("Supports"); - def->tooltip = L( - "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " - "branches emanating from the endpoint."); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(10.)); - - def = this->add("support_pillar_connection_mode", coEnum); - def->label = L("Pillar connection mode"); - def->tooltip = L("Controls the bridge type between two neighboring pillars." - " Can be zig-zag, cross (double zig-zag) or dynamic which" - " will automatically switch between the first two depending" - " on the distance of the two pillars."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values = ConfigOptionEnum::get_enum_names(); - def->enum_labels = ConfigOptionEnum::get_enum_names(); - def->enum_labels[0] = L("Zig-Zag"); - def->enum_labels[1] = L("Cross"); - def->enum_labels[2] = L("Dynamic"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic)); - - def = this->add("support_buildplate_only", coBool); - def->label = L("Support on build plate only"); - def->category = L("Supports"); - def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print."); - def->mode = comSimple; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("support_pillar_widening_factor", coFloat); - def->label = L("Pillar widening factor"); - def->category = L("Supports"); - def->tooltip = L( - "Merging bridges or pillars into another pillars can " - "increase the radius. Zero means no increase, one means " - "full increase. The exact amount of increase is unspecified and can " - "change in the future. What is garanteed is that thickness will not " - "exceed \"support_base_diameter\""); - - def->min = 0; - def->max = 1; - def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(0.15)); - - def = this->add("support_base_diameter", coFloat); - def->label = L("Support base diameter"); - def->category = L("Supports"); - def->tooltip = L("Diameter in mm of the pillar base"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 30; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(4.0)); - - def = this->add("support_base_height", coFloat); - def->label = L("Support base height"); - def->category = L("Supports"); - def->tooltip = L("The height of the pillar base cone"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); - - def = this->add("support_base_safety_distance", coFloat); - def->label = L("Support base safety distance"); - def->category = L("Supports"); - def->tooltip = L( - "The minimum distance of the pillar base from the model in mm. " - "Makes sense in zero elevation mode where a gap according " - "to this parameter is inserted between the model and the pad."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 10; - def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(1)); - - def = this->add("support_critical_angle", coFloat); - def->label = L("Critical angle"); - def->category = L("Supports"); - def->tooltip = L("The default angle for connecting support sticks and junctions."); - def->sidetext = L("°"); - def->min = 0; - def->max = 90; - def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(45)); - - def = this->add("support_max_bridge_length", coFloat); - def->label = L("Max bridge length"); - def->category = L("Supports"); - def->tooltip = L("The max length of a bridge"); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(15.0)); - - def = this->add("support_max_pillar_link_distance", coFloat); - def->label = L("Max pillar linking distance"); - def->category = L("Supports"); - def->tooltip = L("The max distance of two pillars to get linked with each other." - " A zero value will prohibit pillar cascading."); - def->sidetext = L("mm"); - def->min = 0; // 0 means no linking - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(10.0)); - - def = this->add("support_object_elevation", coFloat); - def->label = L("Object elevation"); - def->category = L("Supports"); - def->tooltip = L("How much the supports should lift up the supported object. " - "If \"Pad around object\" is enabled, this value is ignored."); - def->sidetext = L("mm"); - def->min = 0; - def->max = 150; // This is the max height of print on SL1 - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(5.0)); + init_sla_support_params(""); + init_sla_support_params("branching"); def = this->add("support_points_density_relative", coInt); def->label = L("Support points density"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 47c43148a3..5dee704ba4 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -185,6 +185,7 @@ private: void init_fff_params(); void init_extruder_option_keys(); void init_sla_params(); + void init_sla_support_params(const std::string &method_prefix); std::vector m_extruder_option_keys; std::vector m_extruder_retract_keys; @@ -891,6 +892,62 @@ PRINT_CONFIG_CLASS_DEFINE( // and the model object's bounding box bottom. Units in mm. ((ConfigOptionFloat, support_object_elevation))/*= 5.0*/ + + // Branching tree + + // Diameter in mm of the pointing side of the head. + ((ConfigOptionFloat, branchingsupport_head_front_diameter))/*= 0.2*/ + + // How much the pinhead has to penetrate the model surface + ((ConfigOptionFloat, branchingsupport_head_penetration))/*= 0.2*/ + + // Width in mm from the back sphere center to the front sphere center. + ((ConfigOptionFloat, branchingsupport_head_width))/*= 1.0*/ + + // Radius in mm of the support pillars. + ((ConfigOptionFloat, branchingsupport_pillar_diameter))/*= 0.8*/ + + // The percentage of smaller pillars compared to the normal pillar diameter + // which are used in problematic areas where a normal pilla cannot fit. + ((ConfigOptionPercent, branchingsupport_small_pillar_diameter_percent)) + + // How much bridge (supporting another pinhead) can be placed on a pillar. + ((ConfigOptionInt, branchingsupport_max_bridges_on_pillar)) + + // How the pillars are bridged together + ((ConfigOptionEnum, branchingsupport_pillar_connection_mode)) + + // Generate only ground facing supports + ((ConfigOptionBool, branchingsupport_buildplate_only)) + + ((ConfigOptionFloat, branchingsupport_max_weight_on_model)) + + ((ConfigOptionFloat, branchingsupport_pillar_widening_factor)) + + // Radius in mm of the pillar base. + ((ConfigOptionFloat, branchingsupport_base_diameter))/*= 2.0*/ + + // The height of the pillar base cone in mm. + ((ConfigOptionFloat, branchingsupport_base_height))/*= 1.0*/ + + // The minimum distance of the pillar base from the model in mm. + ((ConfigOptionFloat, branchingsupport_base_safety_distance)) /*= 1.0*/ + + // The default angle for connecting support sticks and junctions. + ((ConfigOptionFloat, branchingsupport_critical_angle))/*= 45*/ + + // The max length of a bridge in mm + ((ConfigOptionFloat, branchingsupport_max_bridge_length))/*= 15.0*/ + + // The max distance of two pillars to get cross linked. + ((ConfigOptionFloat, branchingsupport_max_pillar_link_distance)) + + // The elevation in Z direction upwards. This is the space between the pad + // and the model object's bounding box bottom. Units in mm. + ((ConfigOptionFloat, branchingsupport_object_elevation))/*= 5.0*/ + + + /////// Following options influence automatic support points placement: ((ConfigOptionInt, support_points_density_relative)) ((ConfigOptionFloat, support_points_minimal_distance)) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 21895f9431..6bdf415f9a 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -44,29 +44,63 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) scfg.enabled = c.supports_enable.getBool(); scfg.tree_type = c.support_tree_type.value; - scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); - double pillar_r = 0.5 * c.support_pillar_diameter.getFloat(); - scfg.head_back_radius_mm = pillar_r; - scfg.head_fallback_radius_mm = - 0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r; - scfg.head_penetration_mm = c.support_head_penetration.getFloat(); - scfg.head_width_mm = c.support_head_width.getFloat(); - scfg.object_elevation_mm = is_zero_elevation(c) ? - 0. : c.support_object_elevation.getFloat(); - scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; - scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); - scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); - scfg.pillar_connection_mode = c.support_pillar_connection_mode.value; - scfg.ground_facing_only = c.support_buildplate_only.getBool(); - scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); - scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); - scfg.base_height_mm = c.support_base_height.getFloat(); - scfg.pillar_base_safety_distance_mm = - c.support_base_safety_distance.getFloat() < EPSILON ? - scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); - - scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt()); - scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat(); + + switch(scfg.tree_type) { + case sla::SupportTreeType::Default: { + scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); + double pillar_r = 0.5 * c.support_pillar_diameter.getFloat(); + scfg.head_back_radius_mm = pillar_r; + scfg.head_fallback_radius_mm = + 0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r; + scfg.head_penetration_mm = c.support_head_penetration.getFloat(); + scfg.head_width_mm = c.support_head_width.getFloat(); + scfg.object_elevation_mm = is_zero_elevation(c) ? + 0. : c.support_object_elevation.getFloat(); + scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; + scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); + scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); + scfg.pillar_connection_mode = c.support_pillar_connection_mode.value; + scfg.ground_facing_only = c.support_buildplate_only.getBool(); + scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); + scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); + scfg.base_height_mm = c.support_base_height.getFloat(); + scfg.pillar_base_safety_distance_mm = + c.support_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); + + scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt()); + scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat(); + break; + } + case sla::SupportTreeType::Branching: + [[fallthrough]]; + case sla::SupportTreeType::Organic:{ + scfg.head_front_radius_mm = 0.5*c.branchingsupport_head_front_diameter.getFloat(); + double pillar_r = 0.5 * c.branchingsupport_pillar_diameter.getFloat(); + scfg.head_back_radius_mm = pillar_r; + scfg.head_fallback_radius_mm = + 0.01 * c.branchingsupport_small_pillar_diameter_percent.getFloat() * pillar_r; + scfg.head_penetration_mm = c.branchingsupport_head_penetration.getFloat(); + scfg.head_width_mm = c.branchingsupport_head_width.getFloat(); + scfg.object_elevation_mm = is_zero_elevation(c) ? + 0. : c.branchingsupport_object_elevation.getFloat(); + scfg.bridge_slope = c.branchingsupport_critical_angle.getFloat() * PI / 180.0 ; + scfg.max_bridge_length_mm = c.branchingsupport_max_bridge_length.getFloat(); + scfg.max_pillar_link_distance_mm = c.branchingsupport_max_pillar_link_distance.getFloat(); + scfg.pillar_connection_mode = c.branchingsupport_pillar_connection_mode.value; + scfg.ground_facing_only = c.branchingsupport_buildplate_only.getBool(); + scfg.pillar_widening_factor = c.branchingsupport_pillar_widening_factor.getFloat(); + scfg.base_radius_mm = 0.5*c.branchingsupport_base_diameter.getFloat(); + scfg.base_height_mm = c.branchingsupport_base_height.getFloat(); + scfg.pillar_base_safety_distance_mm = + c.branchingsupport_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.branchingsupport_base_safety_distance.getFloat(); + + scfg.max_bridges_on_pillar = unsigned(c.branchingsupport_max_bridges_on_pillar.getInt()); + scfg.max_weight_on_model_support = c.branchingsupport_max_weight_on_model.getFloat(); + break; + } + } return scfg; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 952d453a2e..9dd854029c 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -374,25 +374,45 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) bool is_default_tree = treetype == sla::SupportTreeType::Default; bool is_branching_tree = treetype == sla::SupportTreeType::Branching; - toggle_field("support_head_front_diameter", supports_en); - toggle_field("support_head_penetration", supports_en); - toggle_field("support_head_width", supports_en); - toggle_field("support_pillar_diameter", supports_en); - toggle_field("support_small_pillar_diameter_percent", supports_en); + toggle_field("support_tree_type", supports_en); + + toggle_field("support_head_front_diameter", supports_en && is_default_tree); + toggle_field("support_head_penetration", supports_en && is_default_tree); + toggle_field("support_head_width", supports_en && is_default_tree); + toggle_field("support_pillar_diameter", supports_en && is_default_tree); + toggle_field("support_small_pillar_diameter_percent", supports_en && is_default_tree); toggle_field("support_max_bridges_on_pillar", supports_en && is_default_tree); toggle_field("support_pillar_connection_mode", supports_en && is_default_tree); - toggle_field("support_tree_type", supports_en); - toggle_field("support_buildplate_only", supports_en); - toggle_field("support_base_diameter", supports_en); - toggle_field("support_base_height", supports_en); - toggle_field("support_base_safety_distance", supports_en); - toggle_field("support_critical_angle", supports_en); - toggle_field("support_max_bridge_length", supports_en); + toggle_field("support_buildplate_only", supports_en && is_default_tree); + toggle_field("support_base_diameter", supports_en && is_default_tree); + toggle_field("support_base_height", supports_en && is_default_tree); + toggle_field("support_base_safety_distance", supports_en && is_default_tree); + toggle_field("support_critical_angle", supports_en && is_default_tree); + toggle_field("support_max_bridge_length", supports_en && is_default_tree); toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree); - toggle_field("support_pillar_widening_factor", supports_en && is_branching_tree); - toggle_field("support_max_weight_on_model", supports_en && is_branching_tree); - toggle_field("support_points_density_relative", supports_en); - toggle_field("support_points_minimal_distance", supports_en); + toggle_field("support_pillar_widening_factor", supports_en && is_default_tree); + toggle_field("support_max_weight_on_model", supports_en && is_default_tree); + toggle_field("support_points_density_relative", supports_en && is_default_tree); + toggle_field("support_points_minimal_distance", supports_en && is_default_tree); + + toggle_field("branchingsupport_head_front_diameter", supports_en && is_branching_tree); + toggle_field("branchingsupport_head_penetration", supports_en && is_branching_tree); + toggle_field("branchingsupport_head_width", supports_en && is_branching_tree); + toggle_field("branchingsupport_pillar_diameter", supports_en && is_branching_tree); + toggle_field("branchingsupport_small_pillar_diameter_percent", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_bridges_on_pillar", supports_en && is_branching_tree); + toggle_field("branchingsupport_pillar_connection_mode", supports_en && is_branching_tree); + toggle_field("branchingsupport_buildplate_only", supports_en && is_branching_tree); + toggle_field("branchingsupport_base_diameter", supports_en && is_branching_tree); + toggle_field("branchingsupport_base_height", supports_en && is_branching_tree); + toggle_field("branchingsupport_base_safety_distance", supports_en && is_branching_tree); + toggle_field("branchingsupport_critical_angle", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_bridge_length", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_pillar_link_distance", supports_en && is_branching_tree); + toggle_field("branchingsupport_pillar_widening_factor", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree); + toggle_field("branchingsupport_points_density_relative", supports_en && is_branching_tree); + toggle_field("branchingsupport_points_minimal_distance", supports_en && is_branching_tree); bool pad_en = config->opt_bool("pad_enable"); @@ -407,7 +427,8 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) bool zero_elev = config->opt_bool("pad_around_object") && pad_en; - toggle_field("support_object_elevation", supports_en && !zero_elev); + toggle_field("support_object_elevation", supports_en && is_default_tree && !zero_elev); + toggle_field("branchingsupport_object_elevation", supports_en && is_branching_tree && !zero_elev); toggle_field("pad_object_gap", zero_elev); toggle_field("pad_around_object_everywhere", zero_elev); toggle_field("pad_object_connector_stride", zero_elev); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 67f6034348..60742dd4d3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4821,6 +4821,43 @@ void TabSLAMaterial::update() wxGetApp().mainframe->on_config_changed(m_config); } +void TabSLAPrint::build_sla_support_params(const std::string &prefix, + const Slic3r::GUI::PageShp &page) +{ + auto optgroup = page->new_optgroup(L("Support head")); + optgroup->append_single_option_line(prefix + "support_head_front_diameter"); + optgroup->append_single_option_line(prefix + "support_head_penetration"); + optgroup->append_single_option_line(prefix + "support_head_width"); + + optgroup = page->new_optgroup(L("Support pillar")); + optgroup->append_single_option_line(prefix + "support_pillar_diameter"); + optgroup->append_single_option_line(prefix + "support_small_pillar_diameter_percent"); + optgroup->append_single_option_line(prefix + "support_max_bridges_on_pillar"); + + optgroup->append_single_option_line(prefix + "support_pillar_connection_mode"); + optgroup->append_single_option_line(prefix + "support_buildplate_only"); + optgroup->append_single_option_line(prefix + "support_pillar_widening_factor"); + optgroup->append_single_option_line(prefix + "support_max_weight_on_model"); + optgroup->append_single_option_line(prefix + "support_base_diameter"); + optgroup->append_single_option_line(prefix + "support_base_height"); + optgroup->append_single_option_line(prefix + "support_base_safety_distance"); + + // Mirrored parameter from Pad page for toggling elevation on the same page + optgroup->append_single_option_line(prefix + "support_object_elevation"); + + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_support_object_elevation_description_line); + }; + optgroup->append_line(line); + + optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); + optgroup->append_single_option_line(prefix + "support_critical_angle"); + optgroup->append_single_option_line(prefix + "support_max_bridge_length"); + optgroup->append_single_option_line(prefix + "support_max_pillar_link_distance"); +} + void TabSLAPrint::build() { m_presets = &m_preset_bundle->sla_prints; @@ -4833,42 +4870,47 @@ void TabSLAPrint::build() optgroup->append_single_option_line("faded_layers"); page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); + +// page = add_options_page(L("Branching"), "supports"); + optgroup = page->new_optgroup(L("Supports")); optgroup->append_single_option_line("supports_enable"); optgroup->append_single_option_line("support_tree_type"); - optgroup = page->new_optgroup(L("Support head")); - optgroup->append_single_option_line("support_head_front_diameter"); - optgroup->append_single_option_line("support_head_penetration"); - optgroup->append_single_option_line("support_head_width"); + build_sla_support_params("", page); + build_sla_support_params("branching", page); +// optgroup = page->new_optgroup(L("Support head")); +// optgroup->append_single_option_line("support_head_front_diameter"); +// optgroup->append_single_option_line("support_head_penetration"); +// optgroup->append_single_option_line("support_head_width"); - optgroup = page->new_optgroup(L("Support pillar")); - optgroup->append_single_option_line("support_pillar_diameter"); - optgroup->append_single_option_line("support_small_pillar_diameter_percent"); - optgroup->append_single_option_line("support_max_bridges_on_pillar"); +// optgroup = page->new_optgroup(L("Support pillar")); +// optgroup->append_single_option_line("support_pillar_diameter"); +// optgroup->append_single_option_line("support_small_pillar_diameter_percent"); +// optgroup->append_single_option_line("support_max_bridges_on_pillar"); - optgroup->append_single_option_line("support_pillar_connection_mode"); - optgroup->append_single_option_line("support_buildplate_only"); - optgroup->append_single_option_line("support_pillar_widening_factor"); - optgroup->append_single_option_line("support_max_weight_on_model"); - optgroup->append_single_option_line("support_base_diameter"); - optgroup->append_single_option_line("support_base_height"); - optgroup->append_single_option_line("support_base_safety_distance"); +// optgroup->append_single_option_line("support_pillar_connection_mode"); +// optgroup->append_single_option_line("support_buildplate_only"); +// optgroup->append_single_option_line("support_pillar_widening_factor"); +// optgroup->append_single_option_line("support_max_weight_on_model"); +// optgroup->append_single_option_line("support_base_diameter"); +// optgroup->append_single_option_line("support_base_height"); +// optgroup->append_single_option_line("support_base_safety_distance"); - // Mirrored parameter from Pad page for toggling elevation on the same page - optgroup->append_single_option_line("support_object_elevation"); +// // Mirrored parameter from Pad page for toggling elevation on the same page +// optgroup->append_single_option_line("support_object_elevation"); - Line line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_support_object_elevation_description_line); - }; - optgroup->append_line(line); +// Line line{ "", "" }; +// line.full_width = 1; +// line.widget = [this](wxWindow* parent) { +// return description_line_widget(parent, &m_support_object_elevation_description_line); +// }; +// optgroup->append_line(line); - optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); - optgroup->append_single_option_line("support_critical_angle"); - optgroup->append_single_option_line("support_max_bridge_length"); - optgroup->append_single_option_line("support_max_pillar_link_distance"); +// optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); +// optgroup->append_single_option_line("support_critical_angle"); +// optgroup->append_single_option_line("support_max_bridge_length"); +// optgroup->append_single_option_line("support_max_pillar_link_distance"); optgroup = page->new_optgroup(L("Automatic generation")); optgroup->append_single_option_line("support_points_density_relative"); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index f5dd4c5225..e95050f338 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -339,7 +339,7 @@ public: void on_roll_back_value(const bool to_sys = false); - PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); + PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); static wxString translate_category(const wxString& title, Preset::Type preset_type); virtual void OnActivate(); @@ -526,6 +526,9 @@ public: class TabSLAPrint : public Tab { + void build_sla_support_params(const std::string &prefix, + const Slic3r::GUI::PageShp &page); + public: TabSLAPrint(wxBookCtrlBase* parent) : Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {} From 6238595ac6c80fb57961ee5ed34418b7040d5e8b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 14 Dec 2022 13:14:25 +0100 Subject: [PATCH 58/86] adding separate config values for support tree algorithms Realize config matrix on supports tab --- src/libslic3r/PrintConfig.cpp | 52 ++++++++++++---- src/libslic3r/libslic3r.h | 2 + src/slic3r/GUI/ConfigManipulation.cpp | 17 +++-- src/slic3r/GUI/Tab.cpp | 90 +++++++++++---------------- src/slic3r/GUI/Tab.hpp | 5 +- 5 files changed, 90 insertions(+), 76 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f39857759e..926cfd1868 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3277,6 +3277,9 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) { ConfigOptionDef* def; + constexpr const char * pretext_unavailable = L("Unavailable for this method.\n"); + std::string pretext; + def = this->add(prefix + "support_head_front_diameter", coFloat); def->label = L("Pinhead front diameter"); def->category = L("Supports"); @@ -3326,20 +3329,28 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->mode = comExpert; def->set_default_value(new ConfigOptionPercent(50)); + pretext = ""; + if (prefix == "branching") + pretext = pretext_unavailable; + def = this->add(prefix + "support_max_bridges_on_pillar", coInt); def->label = L("Max bridges on a pillar"); - def->tooltip = L( + def->tooltip = pretext + L( "Maximum number of bridges that can be placed on a pillar. Bridges " "hold support point pinheads and connect to pillars as small branches."); def->min = 0; def->max = 50; def->mode = comExpert; - def->set_default_value(new ConfigOptionInt(3)); + def->set_default_value(new ConfigOptionInt(prefix == "branching" ? 2 : 3)); + + pretext = ""; + if (prefix.empty()) + pretext = pretext_unavailable; def = this->add(prefix + "support_max_weight_on_model", coFloat); def->label = L("Max weight on model"); def->category = L("Supports"); - def->tooltip = L( + def->tooltip = pretext + L( "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " "branches emanating from the endpoint."); def->sidetext = L("mm"); @@ -3347,9 +3358,13 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(10.)); + pretext = ""; + if (prefix == "branching") + pretext = pretext_unavailable; + def = this->add(prefix + "support_pillar_connection_mode", coEnum); def->label = L("Pillar connection mode"); - def->tooltip = L("Controls the bridge type between two neighboring pillars." + def->tooltip = pretext + L("Controls the bridge type between two neighboring pillars." " Can be zig-zag, cross (double zig-zag) or dynamic which" " will automatically switch between the first two depending" " on the distance of the two pillars."); @@ -3373,12 +3388,16 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def = this->add(prefix + "support_pillar_widening_factor", coFloat); def->label = L("Pillar widening factor"); def->category = L("Supports"); - def->tooltip = L( - "Merging bridges or pillars into another pillars can " + + pretext = ""; + if (prefix.empty()) + pretext = pretext_unavailable; + + def->tooltip = pretext + + L("Merging bridges or pillars into another pillars can " "increase the radius. Zero means no increase, one means " "full increase. The exact amount of increase is unspecified and can " - "change in the future. What is garanteed is that thickness will not " - "exceed \"support_base_diameter\""); + "change in the future."); def->min = 0; def->max = 1; @@ -3434,13 +3453,22 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(15.0)); + + double default_val = 15.0; + if (prefix == "branching") + default_val = 5.0; + + def->set_default_value(new ConfigOptionFloat(default_val)); + + pretext = ""; + if (prefix == "branching") + pretext = pretext_unavailable; def = this->add(prefix + "support_max_pillar_link_distance", coFloat); def->label = L("Max pillar linking distance"); def->category = L("Supports"); - def->tooltip = L("The max distance of two pillars to get linked with each other." - " A zero value will prohibit pillar cascading."); + def->tooltip = pretext + L("The max distance of two pillars to get linked with each other." + " A zero value will prohibit pillar cascading."); def->sidetext = L("mm"); def->min = 0; // 0 means no linking def->mode = comAdvanced; @@ -3801,7 +3829,7 @@ void PrintConfigDef::init_sla_params() def->enum_labels[0] = L("Default"); def->enum_labels[1] = L("Branching"); // TODO: def->enum_labels[2] = L("Organic"); - def->mode = comAdvanced; + def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); init_sla_support_params(""); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index d5a21cf219..79945867bf 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -380,6 +380,8 @@ inline IntegerOnly fast_round_up(double a) return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5)); } +template using SamePair = std::pair; + } // namespace Slic3r #endif // _libslic3r_h_ diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 9dd854029c..85fe3e53e4 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -390,29 +390,28 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) toggle_field("support_critical_angle", supports_en && is_default_tree); toggle_field("support_max_bridge_length", supports_en && is_default_tree); toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree); - toggle_field("support_pillar_widening_factor", supports_en && is_default_tree); - toggle_field("support_max_weight_on_model", supports_en && is_default_tree); - toggle_field("support_points_density_relative", supports_en && is_default_tree); - toggle_field("support_points_minimal_distance", supports_en && is_default_tree); + toggle_field("support_pillar_widening_factor", false); + toggle_field("support_max_weight_on_model", false); toggle_field("branchingsupport_head_front_diameter", supports_en && is_branching_tree); toggle_field("branchingsupport_head_penetration", supports_en && is_branching_tree); toggle_field("branchingsupport_head_width", supports_en && is_branching_tree); toggle_field("branchingsupport_pillar_diameter", supports_en && is_branching_tree); toggle_field("branchingsupport_small_pillar_diameter_percent", supports_en && is_branching_tree); - toggle_field("branchingsupport_max_bridges_on_pillar", supports_en && is_branching_tree); - toggle_field("branchingsupport_pillar_connection_mode", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_bridges_on_pillar", false); + toggle_field("branchingsupport_pillar_connection_mode", false); toggle_field("branchingsupport_buildplate_only", supports_en && is_branching_tree); toggle_field("branchingsupport_base_diameter", supports_en && is_branching_tree); toggle_field("branchingsupport_base_height", supports_en && is_branching_tree); toggle_field("branchingsupport_base_safety_distance", supports_en && is_branching_tree); toggle_field("branchingsupport_critical_angle", supports_en && is_branching_tree); toggle_field("branchingsupport_max_bridge_length", supports_en && is_branching_tree); - toggle_field("branchingsupport_max_pillar_link_distance", supports_en && is_branching_tree); + toggle_field("branchingsupport_max_pillar_link_distance", false); toggle_field("branchingsupport_pillar_widening_factor", supports_en && is_branching_tree); toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree); - toggle_field("branchingsupport_points_density_relative", supports_en && is_branching_tree); - toggle_field("branchingsupport_points_minimal_distance", supports_en && is_branching_tree); + + toggle_field("support_points_density_relative", supports_en); + toggle_field("support_points_minimal_distance", supports_en); bool pad_en = config->opt_bool("pad_enable"); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 60742dd4d3..481b16222c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4821,29 +4821,46 @@ void TabSLAMaterial::update() wxGetApp().mainframe->on_config_changed(m_config); } -void TabSLAPrint::build_sla_support_params(const std::string &prefix, +static void add_options_into_line(ConfigOptionsGroupShp &optgroup, + const std::vector> &prefixes, + const std::string &optkey) +{ + auto opt = optgroup->get_option(prefixes.front().first + optkey); + Line line{ opt.opt.label, "" }; + line.full_width = 1; + for (auto &prefix : prefixes) { + opt = optgroup->get_option(prefix.first + optkey); + opt.opt.label = prefix.second; + opt.opt.width = 12; // TODO + line.append_option(opt); + } + optgroup->append_line(line); +} + +void TabSLAPrint::build_sla_support_params(const std::vector> &prefixes, const Slic3r::GUI::PageShp &page) { + auto optgroup = page->new_optgroup(L("Support head")); - optgroup->append_single_option_line(prefix + "support_head_front_diameter"); - optgroup->append_single_option_line(prefix + "support_head_penetration"); - optgroup->append_single_option_line(prefix + "support_head_width"); + add_options_into_line(optgroup, prefixes, "support_head_front_diameter"); + add_options_into_line(optgroup, prefixes, "support_head_penetration"); + add_options_into_line(optgroup, prefixes, "support_head_width"); optgroup = page->new_optgroup(L("Support pillar")); - optgroup->append_single_option_line(prefix + "support_pillar_diameter"); - optgroup->append_single_option_line(prefix + "support_small_pillar_diameter_percent"); - optgroup->append_single_option_line(prefix + "support_max_bridges_on_pillar"); + add_options_into_line(optgroup, prefixes, "support_pillar_diameter"); + add_options_into_line(optgroup, prefixes, "support_small_pillar_diameter_percent"); + add_options_into_line(optgroup, prefixes, "support_max_bridges_on_pillar"); - optgroup->append_single_option_line(prefix + "support_pillar_connection_mode"); - optgroup->append_single_option_line(prefix + "support_buildplate_only"); - optgroup->append_single_option_line(prefix + "support_pillar_widening_factor"); - optgroup->append_single_option_line(prefix + "support_max_weight_on_model"); - optgroup->append_single_option_line(prefix + "support_base_diameter"); - optgroup->append_single_option_line(prefix + "support_base_height"); - optgroup->append_single_option_line(prefix + "support_base_safety_distance"); + add_options_into_line(optgroup, prefixes, "support_pillar_connection_mode"); + add_options_into_line(optgroup, prefixes, "support_buildplate_only"); + add_options_into_line(optgroup, prefixes, "support_pillar_widening_factor"); + add_options_into_line(optgroup, prefixes, "support_max_weight_on_model"); + add_options_into_line(optgroup, prefixes, "support_base_diameter"); + add_options_into_line(optgroup, prefixes, "support_base_height"); + add_options_into_line(optgroup, prefixes, "support_base_safety_distance"); // Mirrored parameter from Pad page for toggling elevation on the same page - optgroup->append_single_option_line(prefix + "support_object_elevation"); + add_options_into_line(optgroup, prefixes, "support_object_elevation"); Line line{ "", "" }; line.full_width = 1; @@ -4853,9 +4870,9 @@ void TabSLAPrint::build_sla_support_params(const std::string &prefix, optgroup->append_line(line); optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); - optgroup->append_single_option_line(prefix + "support_critical_angle"); - optgroup->append_single_option_line(prefix + "support_max_bridge_length"); - optgroup->append_single_option_line(prefix + "support_max_pillar_link_distance"); + add_options_into_line(optgroup, prefixes, "support_critical_angle"); + add_options_into_line(optgroup, prefixes, "support_max_bridge_length"); + add_options_into_line(optgroup, prefixes, "support_max_pillar_link_distance"); } void TabSLAPrint::build() @@ -4871,46 +4888,11 @@ void TabSLAPrint::build() page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); -// page = add_options_page(L("Branching"), "supports"); - optgroup = page->new_optgroup(L("Supports")); optgroup->append_single_option_line("supports_enable"); optgroup->append_single_option_line("support_tree_type"); - build_sla_support_params("", page); - build_sla_support_params("branching", page); -// optgroup = page->new_optgroup(L("Support head")); -// optgroup->append_single_option_line("support_head_front_diameter"); -// optgroup->append_single_option_line("support_head_penetration"); -// optgroup->append_single_option_line("support_head_width"); - -// optgroup = page->new_optgroup(L("Support pillar")); -// optgroup->append_single_option_line("support_pillar_diameter"); -// optgroup->append_single_option_line("support_small_pillar_diameter_percent"); -// optgroup->append_single_option_line("support_max_bridges_on_pillar"); - -// optgroup->append_single_option_line("support_pillar_connection_mode"); -// optgroup->append_single_option_line("support_buildplate_only"); -// optgroup->append_single_option_line("support_pillar_widening_factor"); -// optgroup->append_single_option_line("support_max_weight_on_model"); -// optgroup->append_single_option_line("support_base_diameter"); -// optgroup->append_single_option_line("support_base_height"); -// optgroup->append_single_option_line("support_base_safety_distance"); - -// // Mirrored parameter from Pad page for toggling elevation on the same page -// optgroup->append_single_option_line("support_object_elevation"); - -// Line line{ "", "" }; -// line.full_width = 1; -// line.widget = [this](wxWindow* parent) { -// return description_line_widget(parent, &m_support_object_elevation_description_line); -// }; -// optgroup->append_line(line); - -// optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); -// optgroup->append_single_option_line("support_critical_angle"); -// optgroup->append_single_option_line("support_max_bridge_length"); -// optgroup->append_single_option_line("support_max_pillar_link_distance"); + build_sla_support_params({{"", "Default"}, {"branching", "Branching"}}, page); optgroup = page->new_optgroup(L("Automatic generation")); optgroup->append_single_option_line("support_points_density_relative"); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index e95050f338..c060eb7fdd 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -526,7 +526,10 @@ public: class TabSLAPrint : public Tab { - void build_sla_support_params(const std::string &prefix, + // Methods are a vector of method prefix -> method label pairs + // method prefix is the prefix whith which all the config values are prefixed + // for a particular method. The label is the friendly name for the method + void build_sla_support_params(const std::vector> &methods, const Slic3r::GUI::PageShp &page); public: From 234167534bcc0597b02feed48530ff7972469c63 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 16:25:31 +0100 Subject: [PATCH 59/86] wip on branching tree avoidance again --- src/libslic3r/BranchingTree/BranchingTree.cpp | 12 +++++- src/libslic3r/BranchingTree/BranchingTree.hpp | 6 +++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 43 +++++++++++++++++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 19 ++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 72ecf8c86c..8f1d322a1d 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,7 +76,17 @@ void build_tree(PointCloud &nodes, Builder &builder) switch (type) { case BED: { closest_node.weight = w; - if ((routed = builder.add_ground_bridge(node, closest_node))) { + if (closest_it->dst_branching > nodes.properties().max_branch_length()) { + auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; + Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; + new_node.id = int(nodes.next_junction_id()); + new_node.weight = nodes.get(node_id).weight + hl_br_len; + new_node.left = node.id; + if ((routed = builder.add_bridge(node, new_node))) { + size_t new_idx = nodes.insert_junction(new_node); + ptsqueue.push(new_idx); + } + } else if ((routed = builder.add_ground_bridge(node, closest_node))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; nodes.mark_unreachable(closest_node_id); diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 2d372452ba..06ee3ee146 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -105,6 +105,12 @@ public: // Add an anchor bridge to the model body virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0; + virtual std::optional suggest_avoidance(const Node &from, + float max_bridge_len) + { + return {}; + } + // Report nodes that can not be routed to an endpoint (model or ground) virtual void report_unroutable(const Node &j) = 0; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index d88bdb1b05..d4778a0ac8 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -29,7 +29,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour - static constexpr double WIDENING_SCALE = 0.02; + static constexpr double WIDENING_SCALE = 0.05; double get_radius(const branchingtree::Node &j) const { @@ -99,9 +99,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { int suppid_left = branchingtree::Node::ID_NONE; int suppid_right = branchingtree::Node::ID_NONE; + double glvl = ground_level(m_sm); branchingtree::Node dst = node; - dst.weight += node.pos.z(); - dst.Rmin = std::max(node.Rmin, dst.Rmin); + dst.pos.z() = glvl; + dst.weight += node.pos.z() - glvl; if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst)) ret.to_left = false; @@ -144,8 +145,18 @@ public: bool add_mesh_bridge(const branchingtree::Node &from, const branchingtree::Node &to) override; + std::optional suggest_avoidance(const branchingtree::Node &from, + float max_bridge_len) override;; + void report_unroutable(const branchingtree::Node &j) override { + double glvl = ground_level(m_sm); + branchingtree::Node dst = j; + dst.pos.z() = glvl; + dst.weight += j.pos.z() - glvl; + if (add_ground_bridge(j, dst)) + return; + BOOST_LOG_TRIVIAL(warning) << "Cannot route junction at " << j.pos.x() << " " << j.pos.y() << " " << j.pos.z(); @@ -279,6 +290,32 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, return bool(anchor); } +static std::optional get_avoidance(const GroundConnection &conn, + float maxdist) +{ + return {}; +} + +std::optional BranchingTreeBuilder::suggest_avoidance( + const branchingtree::Node &from, float max_bridge_len) +{ + double glvl = ground_level(m_sm); + branchingtree::Node dst = from; + dst.pos.z() = glvl; + dst.weight += from.pos.z() - glvl; + bool succ = add_ground_bridge(from, dst); + + std::optional ret; + + if (succ) { + auto it = m_gnd_connections.find(from.id); + if (it != m_gnd_connections.end()) + ret = get_avoidance(it->second, max_bridge_len); + } + + return ret; +} + inline void build_pillars(SupportTreeBuilder &builder, BranchingTreeBuilder &vbuilder, const SupportableMesh &sm) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index a47f8d6620..6c17079530 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -547,7 +547,7 @@ Vec3d check_ground_route( auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { + if (gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 6bdf415f9a..d991dfe05f 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -862,6 +862,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector Date: Mon, 2 Jan 2023 17:48:41 +0100 Subject: [PATCH 60/86] Still WIP on branching tree avoidance --- src/libslic3r/BranchingTree/BranchingTree.cpp | 24 ++++++-- src/libslic3r/SLA/BranchingTreeSLA.cpp | 55 ++++++++++++++++--- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 8f1d322a1d..01f4b27244 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,16 +76,28 @@ void build_tree(PointCloud &nodes, Builder &builder) switch (type) { case BED: { closest_node.weight = w; - if (closest_it->dst_branching > nodes.properties().max_branch_length()) { - auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; - Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; - new_node.id = int(nodes.next_junction_id()); - new_node.weight = nodes.get(node_id).weight + hl_br_len; - new_node.left = node.id; + double max_br_len = nodes.properties().max_branch_length(); + if (closest_it->dst_branching > max_br_len) { + std::optional avo = builder.suggest_avoidance(node, max_br_len); + if (!avo) + break; + + Node new_node {*avo, node.Rmin}; + new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).squaredNorm(); + new_node.left = node.id; if ((routed = builder.add_bridge(node, new_node))) { size_t new_idx = nodes.insert_junction(new_node); ptsqueue.push(new_idx); } +// auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; +// Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; +// new_node.id = int(nodes.next_junction_id()); +// new_node.weight = nodes.get(node_id).weight + hl_br_len; +// new_node.left = node.id; +// if ((routed = builder.add_bridge(node, new_node))) { +// size_t new_idx = nodes.insert_junction(new_node); +// ptsqueue.push(new_idx); +// } } else if ((routed = builder.add_ground_bridge(node, closest_node))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index d4778a0ac8..7f4b9853cb 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -146,7 +146,7 @@ public: const branchingtree::Node &to) override; std::optional suggest_avoidance(const branchingtree::Node &from, - float max_bridge_len) override;; + float max_bridge_len) override; void report_unroutable(const branchingtree::Node &j) override { @@ -293,27 +293,64 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, static std::optional get_avoidance(const GroundConnection &conn, float maxdist) { - return {}; + std::optional ret; + + if (conn) { + if (conn.path.size() > 1) { + ret = conn.path[1].pos.cast(); + } else { + Vec3f pbeg = conn.path[0].pos.cast(); + Vec3f pend = conn.pillar_base->pos.cast(); + pbeg.z() = std::max(pbeg.z() - maxdist, pend.z()); + ret = pbeg; + } + } + + return ret; } std::optional BranchingTreeBuilder::suggest_avoidance( const branchingtree::Node &from, float max_bridge_len) { + std::optional ret; + double glvl = ground_level(m_sm); branchingtree::Node dst = from; dst.pos.z() = glvl; dst.weight += from.pos.z() - glvl; - bool succ = add_ground_bridge(from, dst); + sla::Junction j{from.pos.cast(), get_radius(from)}; - std::optional ret; +// auto found_it = m_ground_mem.find(from.id); +// if (found_it != m_ground_mem.end()) { +// // TODO look up the conn object +// } +// else if (auto conn = deepsearch_ground_connection( +// beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN)) { +// ret = get_avoidance(conn, max_bridge_len); +// } - if (succ) { - auto it = m_gnd_connections.find(from.id); - if (it != m_gnd_connections.end()) - ret = get_avoidance(it->second, max_bridge_len); - } + auto conn = deepsearch_ground_connection( + beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); + + ret = get_avoidance(conn, max_bridge_len); return ret; + +// double glvl = ground_level(m_sm); +// branchingtree::Node dst = from; +// dst.pos.z() = glvl; +// dst.weight += from.pos.z() - glvl; +// bool succ = add_ground_bridge(from, dst); + +// std::optional ret; + +// if (succ) { +// auto it = m_gnd_connections.find(m_pillars.size() - 1); +// if (it != m_gnd_connections.end()) +// ret = get_avoidance(it->second, max_bridge_len); +// } + +// return ret; } inline void build_pillars(SupportTreeBuilder &builder, From 816371f37ce7d8abdf6385df82f01b4938f230d7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 3 Jan 2023 17:51:20 +0100 Subject: [PATCH 61/86] Use avoidance suggestion when ground point is too far --- src/libslic3r/BranchingTree/BranchingTree.cpp | 11 +--- src/libslic3r/SLA/BranchingTreeSLA.cpp | 63 ++++++------------- src/libslic3r/SLA/SupportTreeUtils.hpp | 3 +- 3 files changed, 23 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 01f4b27244..98261311b1 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -83,21 +83,12 @@ void build_tree(PointCloud &nodes, Builder &builder) break; Node new_node {*avo, node.Rmin}; - new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).squaredNorm(); + new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).norm(); new_node.left = node.id; if ((routed = builder.add_bridge(node, new_node))) { size_t new_idx = nodes.insert_junction(new_node); ptsqueue.push(new_idx); } -// auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f; -// Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin}; -// new_node.id = int(nodes.next_junction_id()); -// new_node.weight = nodes.get(node_id).weight + hl_br_len; -// new_node.left = node.id; -// if ((routed = builder.add_bridge(node, new_node))) { -// size_t new_idx = nodes.insert_junction(new_node); -// ptsqueue.push(new_idx); -// } } else if ((routed = builder.add_ground_bridge(node, closest_node))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 7f4b9853cb..e851fd437c 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,12 +20,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { const SupportableMesh &m_sm; const branchingtree::PointCloud &m_cloud; - std::set m_ground_mem; - std::vector m_pillars; // to put an index over them // cache succesfull ground connections - std::map m_gnd_connections; + std::map m_gnd_connections; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -162,6 +160,7 @@ public: // Discard all the support points connecting to this branch. discard_subtree_rescure(j.id); +// discard_subtree(j.id); } const std::vector& unroutable_pinheads() const @@ -177,7 +176,7 @@ public: { const GroundConnection *ret = nullptr; - auto it = m_gnd_connections.find(pillar); + auto it = m_gnd_connections.find(m_pillars[pillar].id); if (it != m_gnd_connections.end()) ret = &it->second; @@ -230,25 +229,23 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, namespace bgi = boost::geometry::index; - auto it = m_ground_mem.find(from.id); - if (it == m_ground_mem.end()) { + auto it = m_gnd_connections.find(from.id); + if (it == m_gnd_connections.end()) { sla::Junction j{from.pos.cast(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j, get_radius(to), init_dir); - if (conn) { - m_pillars.emplace_back(from); - m_gnd_connections[m_pillars.size() - 1] = conn; - - ret = true; - } - // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because // it is unlikely that search_ground_route would find a better solution - m_ground_mem.insert(from.id); + m_gnd_connections[from.id] = conn; + + if (conn) { + m_pillars.emplace_back(from); + ret = true; + } } if (ret) { @@ -320,37 +317,17 @@ std::optional BranchingTreeBuilder::suggest_avoidance( dst.weight += from.pos.z() - glvl; sla::Junction j{from.pos.cast(), get_radius(from)}; -// auto found_it = m_ground_mem.find(from.id); -// if (found_it != m_ground_mem.end()) { -// // TODO look up the conn object -// } -// else if (auto conn = deepsearch_ground_connection( -// beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN)) { -// ret = get_avoidance(conn, max_bridge_len); -// } - - auto conn = deepsearch_ground_connection( - beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); - - ret = get_avoidance(conn, max_bridge_len); + auto found_it = m_gnd_connections.find(from.id); + if (found_it != m_gnd_connections.end()) { + ret = get_avoidance(found_it->second, max_bridge_len); + } else { + auto conn = deepsearch_ground_connection( + beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); + m_gnd_connections[from.id] = conn; + ret = get_avoidance(conn, max_bridge_len); + } return ret; - -// double glvl = ground_level(m_sm); -// branchingtree::Node dst = from; -// dst.pos.z() = glvl; -// dst.weight += from.pos.z() - glvl; -// bool succ = add_ground_bridge(from, dst); - -// std::optional ret; - -// if (succ) { -// auto it = m_gnd_connections.find(m_pillars.size() - 1); -// if (it != m_gnd_connections.end()) -// ret = get_avoidance(it->second, max_bridge_len); -// } - -// return ret; } inline void build_pillars(SupportTreeBuilder &builder, diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 6c17079530..8f27ab914d 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -646,6 +646,7 @@ GroundConnection deepsearch_ground_connection( // Extract and apply the result auto [plr, azm, bridge_l] = oresult.optimum; Vec3d n = spheric_to_dir(plr, azm); + assert(std::abs(n.norm() - 1.) < EPSILON); // Now the optimizer gave a possible route to ground with a bridge direction // and length. This length can be shortened further by brute-force queries @@ -654,7 +655,7 @@ GroundConnection deepsearch_ground_connection( // constraint, but it would not find quickly enough an accurate solution, // and it would be very hard to define a stop score which is very useful in // terminating the search as soon as the ground is found. - double l = 0., l_max = bridge_l; + double l = 0., l_max = sm.cfg.max_bridge_length_mm; double zlvl = std::numeric_limits::infinity(); while(zlvl > gndlvl && l <= l_max) { From 9a33537b1d3e0f3a1f8d58b2b859d694dd4a80cb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 4 Jan 2023 11:27:24 +0100 Subject: [PATCH 62/86] Slight performance improvement With parallel avoidance search for leaf nodes --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/SLA/BranchingTreeSLA.cpp | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 06ee3ee146..7e59e6f1ae 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -106,7 +106,7 @@ public: virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0; virtual std::optional suggest_avoidance(const Node &from, - float max_bridge_len) + float max_bridge_len) const { return {}; } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index e851fd437c..b3c7d29449 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -23,7 +23,8 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::vector m_pillars; // to put an index over them // cache succesfull ground connections - std::map m_gnd_connections; + mutable std::map m_gnd_connections; + mutable execution::SpinningMutex m_gnd_connections_mtx; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -144,7 +145,7 @@ public: const branchingtree::Node &to) override; std::optional suggest_avoidance(const branchingtree::Node &from, - float max_bridge_len) override; + float max_bridge_len) const override; void report_unroutable(const branchingtree::Node &j) override { @@ -307,7 +308,7 @@ static std::optional get_avoidance(const GroundConnection &conn, } std::optional BranchingTreeBuilder::suggest_avoidance( - const branchingtree::Node &from, float max_bridge_len) + const branchingtree::Node &from, float max_bridge_len) const { std::optional ret; @@ -317,13 +318,23 @@ std::optional BranchingTreeBuilder::suggest_avoidance( dst.weight += from.pos.z() - glvl; sla::Junction j{from.pos.cast(), get_radius(from)}; - auto found_it = m_gnd_connections.find(from.id); + auto found_it = m_gnd_connections.end(); + { + std::lock_guard lk{m_gnd_connections_mtx}; + found_it = m_gnd_connections.find(from.id); + } + if (found_it != m_gnd_connections.end()) { ret = get_avoidance(found_it->second, max_bridge_len); } else { auto conn = deepsearch_ground_connection( beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); - m_gnd_connections[from.id] = conn; + + { + std::lock_guard lk{m_gnd_connections_mtx}; + m_gnd_connections[from.id] = conn; + } + ret = get_avoidance(conn, max_bridge_len); } @@ -394,6 +405,15 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s std::move(leafs), props}; BranchingTreeBuilder vbuilder{builder, sm, nodes}; + + execution::for_each(ex_tbb, + size_t(0), + nodes.get_leafs().size(), + [&nodes, &vbuilder](size_t leaf_idx) { + vbuilder.suggest_avoidance(nodes.get_leafs()[leaf_idx], + nodes.properties().max_branch_length()); + }); + branchingtree::build_tree(nodes, vbuilder); build_pillars(builder, vbuilder, sm); From 32e323c64ca7e81d8eff0fd3da5198a01b484539 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 4 Jan 2023 12:59:05 +0100 Subject: [PATCH 63/86] Fix supports below ground --- src/libslic3r/SLA/SupportTreeUtils.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 8f27ab914d..a7220ce0e6 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -517,6 +517,11 @@ Vec3d check_ground_route( const auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); + // Intersection of the suggested bridge with ground plane. If the bridge + // spans below ground, stop it at ground level. + double t = (gndlvl - source.pos.z()) / dir.z(); + bridge_len = std::min(t, bridge_len); + Vec3d bridge_end = source.pos + bridge_len * dir; double down_l = bridge_end.z() - gndlvl; @@ -648,6 +653,9 @@ GroundConnection deepsearch_ground_connection( Vec3d n = spheric_to_dir(plr, azm); assert(std::abs(n.norm() - 1.) < EPSILON); + double t = (gndlvl - source.pos.z()) / n.z(); + bridge_l = std::min(t, bridge_l); + // Now the optimizer gave a possible route to ground with a bridge direction // and length. This length can be shortened further by brute-force queries // of free route straigt down for a possible pillar. @@ -655,7 +663,7 @@ GroundConnection deepsearch_ground_connection( // constraint, but it would not find quickly enough an accurate solution, // and it would be very hard to define a stop score which is very useful in // terminating the search as soon as the ground is found. - double l = 0., l_max = sm.cfg.max_bridge_length_mm; + double l = 0., l_max = bridge_l; double zlvl = std::numeric_limits::infinity(); while(zlvl > gndlvl && l <= l_max) { From aec0c4a0dcc7a0491f88b4cac9a3674212aa9437 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 12:40:09 +0100 Subject: [PATCH 64/86] Fix sidebar combobox behavior for support routing "support_buildplate_only" was toggled only for default supports --- src/libslic3r/PrintConfig.cpp | 17 +++++++++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 7 +++++-- src/slic3r/GUI/Tab.cpp | 7 +++++-- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 926cfd1868..cf2753b183 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4802,6 +4802,23 @@ Points get_bed_shape(const PrintConfig &cfg) Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.bed_shape.values); } +std::string get_sla_suptree_prefix(const DynamicPrintConfig &config) +{ + const auto *suptreetype = config.option>("support_tree_type"); + std::string slatree = ""; + if (suptreetype) { + auto ttype = static_cast(suptreetype->getInt()); + switch (ttype) { + case sla::SupportTreeType::Branching: slatree = "branching"; break; + case sla::SupportTreeType::Organic: slatree = "organic"; break; + default: + ; + } + } + + return slatree; +} + } // namespace Slic3r #include diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5dee704ba4..ca66790515 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1167,6 +1167,8 @@ Points get_bed_shape(const DynamicPrintConfig &cfg); Points get_bed_shape(const PrintConfig &cfg); Points get_bed_shape(const SLAPrinterConfig &cfg); +std::string get_sla_suptree_prefix(const DynamicPrintConfig &config); + // ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. // Each change of ModelConfig is tracked by assigning a new timestamp from a global counter. // The counter is used for faster synchronization of the background slicing thread diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1ab23b3442..0da9e5b834 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -553,10 +553,13 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : const bool supports_enable = selection == _("None") ? false : true; new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable)); + std::string treetype = get_sla_suptree_prefix(new_conf); + if (selection == _("Everywhere")) - new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false)); + new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(false)); else if (selection == _("Support on build plate only")) - new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true)); + new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(true)); + } tab->load_config(new_conf); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 481b16222c..ad7b92e2e6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1038,8 +1038,11 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff) { + std::string slatree = is_fff ? "" : get_sla_suptree_prefix(config); + const std::string support = is_fff ? "support_material" : "supports_enable"; - const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : "support_buildplate_only"; + const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : slatree + "support_buildplate_only"; + return ! config.opt_bool(support) ? _("None") : @@ -1082,7 +1085,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (is_fff ? (opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") : - (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) + (opt_key == "supports_enable" || opt_key == "support_tree_type" || opt_key == get_sla_suptree_prefix(*m_config) + "support_buildplate_only")) og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_around_object")) From add0f89728e35aab50aa5784c2c46bd466260eb3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:20:35 +0100 Subject: [PATCH 65/86] Fix floating point divisions by zero when ground route has no bridge --- src/libslic3r/SLA/SupportTreeUtils.hpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index a7220ce0e6..472b9ec40d 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -125,8 +125,11 @@ struct Beam_ { // Defines a set of rays displaced along a cone's surface Beam_(const Ball &src_ball, const Ball &dst_ball) : src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R} { - r2 = src_ball.R - + (dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p); + r2 = src_ball.R; + auto d = distance(src_ball.p, dst_ball.p); + + if (d > EPSILON) + r2 += (dst_ball.R - src_ball.R) / d; } Beam_(const Vec3d &s, const Vec3d &d, double R) @@ -542,7 +545,7 @@ Vec3d check_ground_route( if (brhit_dist < bridge_len) { ret = (source.pos + brhit_dist * dir); - } else { + } else if (down_l > 0.) { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; double end_radius = wideningfn( @@ -565,6 +568,8 @@ Vec3d check_ground_route( } ret = Vec3d{bridge_end.x(), bridge_end.y(), bridge_end.z() - gnd_hit_d}; + } else { + ret = bridge_end; } return ret; @@ -709,6 +714,9 @@ GroundConnection deepsearch_ground_connection(Ex policy, { double gndlvl = ground_level(sm); auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) { + if (len < EPSILON) + return src.R; + Vec3d dst = src.p + len * dir; double widening = end_radius - src.R; double zlen = dst.z() - gndlvl; From f72984f18ef48a544188b980ff32532caa04bb22 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:21:05 +0100 Subject: [PATCH 66/86] Fix broken caching of pillar routes --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index b3c7d29449..bb97126161 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -231,6 +231,8 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, namespace bgi = boost::geometry::index; auto it = m_gnd_connections.find(from.id); + const GroundConnection *connptr = nullptr; + if (it == m_gnd_connections.end()) { sla::Junction j{from.pos.cast(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); @@ -241,15 +243,14 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because // it is unlikely that search_ground_route would find a better solution - m_gnd_connections[from.id] = conn; - - if (conn) { - m_pillars.emplace_back(from); - ret = true; - } + connptr = &(m_gnd_connections[from.id] = conn); + } else { + connptr = &(it->second); } - if (ret) { + if (connptr && *connptr) { + m_pillars.emplace_back(from); + ret = true; build_subtree(from.id); } From 47a824d1310b83ffb7156e98d4698bc4e30eb1a3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:23:55 +0100 Subject: [PATCH 67/86] Remove unused member in DefaultSupportTree Also fix for loop that is copying int vector in each iteration --- src/libslic3r/SLA/DefaultSupportTree.cpp | 3 +-- src/libslic3r/SLA/DefaultSupportTree.hpp | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 53475542ac..a0a12d32b0 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -372,7 +372,6 @@ void DefaultSupportTree::add_pinheads() PtIndices filtered_indices; filtered_indices.reserve(aliases.size()); m_iheads.reserve(aliases.size()); - m_iheadless.reserve(aliases.size()); for(auto& a : aliases) { // Here we keep only the front point of the cluster. filtered_indices.emplace_back(a.front()); @@ -602,7 +601,7 @@ void DefaultSupportTree::routing_to_ground() // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. size_t ci = 0; - for (auto cl : m_pillar_clusters) { + for (const std::vector &cl : m_pillar_clusters) { m_thr(); auto cidx = cl_centroids[ci++]; diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index ee58e9dede..08990a8df9 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -62,7 +62,6 @@ class DefaultSupportTree { PtIndices m_iheads; // support points with pinhead PtIndices m_iheads_onmodel; - PtIndices m_iheadless; // headless support points std::map m_head_to_ground_scans; From 8207433b81e1afe176cca8a7fba8df79590161b4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:24:41 +0100 Subject: [PATCH 68/86] Fix up whitespace for comments in DefaultSupportTree This commit only deals with white space --- src/libslic3r/SLA/DefaultSupportTree.cpp | 143 +++++++++++------------ 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index a0a12d32b0..429cd45c6e 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -365,8 +365,8 @@ void DefaultSupportTree::add_pinheads() // The minimum distance for two support points to remain valid. const double /*constexpr*/ D_SP = 0.1; - // Get the points that are too close to each other and keep only the - // first one + // Get the points that are too close to each other and keep only the + // first one auto aliases = cluster(m_points, D_SP, 2); PtIndices filtered_indices; @@ -377,7 +377,7 @@ void DefaultSupportTree::add_pinheads() filtered_indices.emplace_back(a.front()); } - // calculate the normals to the triangles for filtered points + // calculate the normals to the triangles for filtered points auto nmls = normals(suptree_ex_policy, m_points, m_sm.emesh, m_sm.cfg.head_front_radius_mm, m_thr, filtered_indices); @@ -406,22 +406,22 @@ void DefaultSupportTree::add_pinheads() Vec3d n = nmls.row(Eigen::Index(i)); - // for all normals we generate the spherical coordinates and - // saturate the polar angle to 45 degrees from the bottom then - // convert back to standard coordinates to get the new normal. - // Then we just create a quaternion from the two normals - // (Quaternion::FromTwoVectors) and apply the rotation to the - // arrow head. + // for all normals we generate the spherical coordinates and + // saturate the polar angle to 45 degrees from the bottom then + // convert back to standard coordinates to get the new normal. + // Then we just create a quaternion from the two normals + // (Quaternion::FromTwoVectors) and apply the rotation to the + // arrow head. auto [polar, azimuth] = dir_to_spheric(n); - // skip if the tilt is not sane + // skip if the tilt is not sane if (polar < PI - m_sm.cfg.normal_cutoff_angle) return; - // We saturate the polar angle to 3pi/4 + // We saturate the polar angle to 3pi/4 polar = std::max(polar, PI - m_sm.cfg.bridge_slope); - // save the head (pinpoint) position + // save the head (pinpoint) position Vec3d hp = m_points.row(fidx); double lmin = m_sm.cfg.head_width_mm, lmax = lmin; @@ -430,18 +430,17 @@ void DefaultSupportTree::add_pinheads() lmin = 0., lmax = m_sm.cfg.head_penetration_mm; } - // The distance needed for a pinhead to not collide with model. + // The distance needed for a pinhead to not collide with model. double w = lmin + 2 * back_r + 2 * m_sm.cfg.head_front_radius_mm - m_sm.cfg.head_penetration_mm; double pin_r = double(m_sm.pts[fidx].head_front_radius); - // Reassemble the now corrected normal + // Reassemble the now corrected normal auto nn = spheric_to_dir(polar, azimuth).normalized(); - // check available distance - AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, - back_r, w); + // check available distance + AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, back_r, w); if (t.distance() < w) { // Let's try to optimize this angle, there might be a @@ -511,10 +510,10 @@ void DefaultSupportTree::classify() ground_head_indices.reserve(m_iheads.size()); m_iheads_onmodel.reserve(m_iheads.size()); - // First we decide which heads reach the ground and can be full - // pillars and which shall be connected to the model surface (or - // search a suitable path around the surface that leads to the - // ground -- TODO) + // First we decide which heads reach the ground and can be full + // pillars and which shall be connected to the model surface (or + // search a suitable path around the surface that leads to the + // ground -- TODO) for(unsigned i : m_iheads) { m_thr(); @@ -532,10 +531,10 @@ void DefaultSupportTree::classify() m_head_to_ground_scans[i] = hit; } - // We want to search for clusters of points that are far enough - // from each other in the XY plane to not cross their pillar bases - // These clusters of support points will join in one pillar, - // possibly in their centroid support point. + // We want to search for clusters of points that are far enough + // from each other in the XY plane to not cross their pillar bases + // These clusters of support points will join in one pillar, + // possibly in their centroid support point. auto pointfn = [this](unsigned i) { return m_builder.head(i).junction_point(); @@ -561,13 +560,13 @@ void DefaultSupportTree::routing_to_ground() for (auto &cl : m_pillar_clusters) { m_thr(); - // place all the centroid head positions into the index. We - // will query for alternative pillar positions. If a sidehead - // cannot connect to the cluster centroid, we have to search - // for another head with a full pillar. Also when there are two - // elements in the cluster, the centroid is arbitrary and the - // sidehead is allowed to connect to a nearby pillar to - // increase structural stability. + // place all the centroid head positions into the index. We + // will query for alternative pillar positions. If a sidehead + // cannot connect to the cluster centroid, we have to search + // for another head with a full pillar. Also when there are two + // elements in the cluster, the centroid is arbitrary and the + // sidehead is allowed to connect to a nearby pillar to + // increase structural stability. if (cl.empty()) continue; @@ -597,9 +596,9 @@ void DefaultSupportTree::routing_to_ground() } } - // now we will go through the clusters ones again and connect the - // sidepoints with the cluster centroid (which is a ground pillar) - // or a nearby pillar if the centroid is unreachable. + // now we will go through the clusters ones again and connect the + // sidepoints with the cluster centroid (which is a ground pillar) + // or a nearby pillar if the centroid is unreachable. size_t ci = 0; for (const std::vector &cl : m_pillar_clusters) { m_thr(); @@ -665,10 +664,10 @@ bool DefaultSupportTree::connect_to_model_body(Head &head) zangle = std::max(zangle, PI/4); double h = std::sin(zangle) * head.fullwidth(); - // The width of the tail head that we would like to have... + // The width of the tail head that we would like to have... h = std::min(hit.distance() - head.r_back_mm, h); - // If this is a mini pillar dont bother with the tail width, can be 0. + // If this is a mini pillar dont bother with the tail width, can be 0. if (head.r_back_mm < m_sm.cfg.head_back_radius_mm) h = std::max(h, 0.); else if (h <= 0.) return false; @@ -774,23 +773,23 @@ void DefaultSupportTree::interconnect_pillars() // Ideally every pillar should be connected with at least one of its // neighbors if that neighbor is within max_pillar_link_distance - // Pillars with height exceeding H1 will require at least one neighbor - // to connect with. Height exceeding H2 require two neighbors. + // Pillars with height exceeding H1 will require at least one neighbor + // to connect with. Height exceeding H2 require two neighbors. double H1 = m_sm.cfg.max_solo_pillar_height_mm; double H2 = m_sm.cfg.max_dual_pillar_height_mm; double d = m_sm.cfg.max_pillar_link_distance_mm; - //A connection between two pillars only counts if the height ratio is - // bigger than 50% + // A connection between two pillars only counts if the height ratio is + // bigger than 50% double min_height_ratio = 0.5; std::set pairs; - // A function to connect one pillar with its neighbors. THe number of - // neighbors is given in the configuration. This function if called - // for every pillar in the pillar index. A pair of pillar will not - // be connected multiple times this is ensured by the 'pairs' set which - // remembers the processed pillar pairs + // A function to connect one pillar with its neighbors. THe number of + // neighbors is given in the configuration. This function if called + // for every pillar in the pillar index. A pair of pillar will not + // be connected multiple times this is ensured by the 'pairs' set which + // remembers the processed pillar pairs auto cascadefn = [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) { @@ -798,10 +797,10 @@ void DefaultSupportTree::interconnect_pillars() const Pillar& pillar = m_builder.pillar(el.second); // actual pillar - // Get the max number of neighbors a pillar should connect to + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_sm.cfg.pillar_cascade_neighbors; - // connections are already enough for the pillar + // connections are already enough for the pillar if(pillar.links >= neighbors) return; double max_d = d * pillar.r_start / m_sm.cfg.head_back_radius_mm; @@ -810,7 +809,7 @@ void DefaultSupportTree::interconnect_pillars() return distance(e.first, qp) < max_d; }); - // sort the result by distance (have to check if this is needed) + // sort the result by distance (have to check if this is needed) std::sort(qres.begin(), qres.end(), [qp](const PointIndexEl& e1, const PointIndexEl& e2){ return distance(e1.first, qp) < distance(e2.first, qp); @@ -822,23 +821,23 @@ void DefaultSupportTree::interconnect_pillars() auto a = el.second, b = re.second; - // Get unique hash for the given pair (order doesn't matter) + // Get unique hash for the given pair (order doesn't matter) auto hashval = pairhash(a, b); - // Search for the pair amongst the remembered pairs + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; const Pillar& neighborpillar = m_builder.pillar(re.second); - // this neighbor is occupied, skip + // this neighbor is occupied, skip if (neighborpillar.links >= neighbors) continue; if (neighborpillar.r_start < pillar.r_start) continue; if(interconnect(pillar, neighborpillar)) { pairs.insert(hashval); - // If the interconnection length between the two pillars is - // less than 50% of the longer pillar's height, don't count + // If the interconnection length between the two pillars is + // less than 50% of the longer pillar's height, don't count if(pillar.height < H1 || neighborpillar.height / pillar.height > min_height_ratio) m_builder.increment_links(pillar); @@ -849,30 +848,30 @@ void DefaultSupportTree::interconnect_pillars() } - // connections are enough for one pillar + // connections are enough for one pillar if(pillar.links >= neighbors) break; } }; - // Run the cascade for the pillars in the index + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - // We would be done here if we could allow some pillars to not be - // connected with any neighbors. But this might leave the support tree - // unprintable. - // - // The current solution is to insert additional pillars next to these - // lonely pillars. One or even two additional pillar might get inserted - // depending on the length of the lonely pillar. + // We would be done here if we could allow some pillars to not be + // connected with any neighbors. But this might leave the support tree + // unprintable. + // + // The current solution is to insert additional pillars next to these + // lonely pillars. One or even two additional pillar might get inserted + // depending on the length of the lonely pillar. size_t pillarcount = m_builder.pillarcount(); - // Again, go through all pillars, this time in the whole support tree - // not just the index. + // Again, go through all pillars, this time in the whole support tree + // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_builder.pillar(pid); }; - // Decide how many additional pillars will be needed: + // Decide how many additional pillars will be needed: unsigned needpillars = 0; if (pillar().bridges > m_sm.cfg.max_bridges_on_pillar) @@ -888,17 +887,17 @@ void DefaultSupportTree::interconnect_pillars() needpillars = std::max(pillar().links, needpillars) - pillar().links; if (needpillars == 0) continue; - // Search for new pillar locations: + // Search for new pillar locations: bool found = false; double alpha = 0; // goes to 2Pi double r = 2 * m_sm.cfg.base_radius_mm; Vec3d pillarsp = pillar().startpoint(); - // temp value for starting point detection + // temp value for starting point detection Vec3d sp(pillarsp.x(), pillarsp.y(), pillarsp.z() - r); - // A vector of bool for placement feasbility + // A vector of bool for placement feasbility std::vector canplace(needpillars, false); std::vector spts(needpillars); // vector of starting points @@ -917,12 +916,12 @@ void DefaultSupportTree::interconnect_pillars() s.y() += std::sin(a) * r; spts[n] = s; - // Check the path vertically down + // Check the path vertically down Vec3d check_from = s + Vec3d{0., 0., pillar().r_start}; auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r_start); Vec3d gndsp{s.x(), s.y(), gnd}; - // If the path is clear, check for pillar base collisions + // If the path is clear, check for pillar base collisions canplace[n] = std::isinf(hr.distance()) && std::sqrt(m_sm.emesh.squared_distance(gndsp)) > min_dist; @@ -931,7 +930,7 @@ void DefaultSupportTree::interconnect_pillars() found = std::all_of(canplace.begin(), canplace.end(), [](bool v) { return v; }); - // 20 angles will be tried... + // 20 angles will be tried... alpha += 0.1 * PI; } From 4620dd5a3d37d4f48a85c5ee2a7f52a2305c820e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 16:04:48 +0100 Subject: [PATCH 69/86] WIP on small pillar fixes --- src/libslic3r/SLA/SupportTreeUtils.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 472b9ec40d..e4c03da6cc 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -469,6 +469,9 @@ inline long build_ground_connection(SupportTreeBuilder &builder, gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); + if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) + h += sm.pad_cfg.wall_thickness_mm; + // TODO: does not work yet // if (conn.path.back().id < 0) { // // this is a head @@ -477,7 +480,8 @@ inline long build_ground_connection(SupportTreeBuilder &builder, // } else ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); - builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); + if (conn.pillar_base->r_top >= sm.cfg.head_back_radius_mm) + builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); return ret; } @@ -555,7 +559,7 @@ Vec3d check_ground_route( auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) { + if (source.r >= sm.cfg.head_back_radius_mm && gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) { // Dealing with zero elevation mode, to not route pillars // into the gap between the optional pad and the model double gap = std::sqrt(sm.emesh.squared_distance(gp)); From d6fe5767e0d59e56c643707102fb8259733664f9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 17 Jan 2023 16:41:27 +0100 Subject: [PATCH 70/86] Small supports now go through the pad to always reach the bed They will not hang in the air if they end up in the gap between the "around object pad" and the object --- src/libslic3r/SLA/SupportTreeUtils.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index e4c03da6cc..93f370c325 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -469,8 +469,10 @@ inline long build_ground_connection(SupportTreeBuilder &builder, gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); - if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) + if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) { h += sm.pad_cfg.wall_thickness_mm; + gp.z() -= sm.pad_cfg.wall_thickness_mm; + } // TODO: does not work yet // if (conn.path.back().id < 0) { @@ -478,7 +480,8 @@ inline long build_ground_connection(SupportTreeBuilder &builder, // long head_id = std::abs(conn.path.back().id); // ret = builder.add_pillar(head_id, h); // } else - ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); + + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); if (conn.pillar_base->r_top >= sm.cfg.head_back_radius_mm) builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); From 8ab0c2cb3d08ad2b14fb5000d9151e2d2c346701 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 18 Jan 2023 10:08:38 +0100 Subject: [PATCH 71/86] Suppressed tree_supports_show_error() in production code. Changed error strings to string_view literals. --- src/libslic3r/TreeModelVolumes.cpp | 16 ++++++++++------ src/libslic3r/TreeSupport.cpp | 26 +++++++++++++------------- src/libslic3r/TreeSupport.hpp | 5 ++--- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index b416db634f..e8ab377d70 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -18,6 +18,8 @@ #include "PrintConfig.hpp" #include "Utils.hpp" +#include + #include #include @@ -26,6 +28,8 @@ namespace Slic3r::FFFTreeSupport { +using namespace std::literals; + // or warning // had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL() #define error_level_not_in_cache error @@ -336,7 +340,7 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Collision requested.", false); + tree_supports_show_error("Not precalculated Collision requested."sv, false); } const_cast(this)->calculateCollision(radius, layer_idx); return getCollision(orig_radius, layer_idx, min_xy_dist); @@ -351,7 +355,7 @@ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerInde return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Holefree Collision requested.", false); + tree_supports_show_error("Not precalculated Holefree Collision requested."sv, false); } const_cast(this)->calculateCollisionHolefree({ radius, layer_idx }); return getCollisionHolefree(radius, layer_idx); @@ -375,10 +379,10 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI if (m_precalculated) { if (to_model) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Avoidance(to model) requested.", false); + tree_supports_show_error("Not precalculated Avoidance(to model) requested."sv, false); } else { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested.", false); + tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested."sv, false); } } const_cast(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model); @@ -396,7 +400,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - tree_supports_show_error("Not precalculated Placeable areas requested.", false); + tree_supports_show_error("Not precalculated Placeable areas requested."sv, false); } const_cast(this)->calculatePlaceables(radius, layer_idx); return getPlaceableAreas(orig_radius, layer_idx); @@ -422,7 +426,7 @@ const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius, tree_supports_show_error( min_xy_dist ? "Not precalculated Wall restriction of minimum xy distance requested )." : - "Not precalculated Wall restriction requested )." + "Not precalculated Wall restriction requested )."sv , false); } const_cast(this)->calculateWallRestrictions({ radius, layer_idx }); diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 1f0aa00b23..8363cd6953 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -58,6 +58,7 @@ enum class LineStatus using LineInformation = std::vector>; using LineInformations = std::vector; +using namespace std::literals; static inline void validate_range(const Point &pt) { @@ -191,10 +192,9 @@ static std::vector>> group_me } #endif -//todo Remove! Only relevant for public BETA! static bool inline g_showed_critical_error = false; static bool inline g_showed_performance_warning = false; -void tree_supports_show_error(std::string message, bool critical) +void tree_supports_show_error(std::string_view message, bool critical) { // todo Remove! ONLY FOR PUBLIC BETA!! #if defined(_WIN32) && defined(TREE_SUPPORT_SHOW_ERRORS) @@ -204,7 +204,7 @@ void tree_supports_show_error(std::string message, bool critical) bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning); (critical ? g_showed_critical_error : g_showed_performance_warning) = true; if (show) - MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + message + "\n" + bugtype).c_str(), + MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + std::string(message) + "\n" + bugtype).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); #endif // WIN32 } @@ -572,7 +572,7 @@ static std::optional> polyline_sample_next_point_at_dis // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << ") is smaller than 100"; - tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing.", true); + tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing."sv, true); if (next_distance > 2 * current_distance) // This case should never happen, but better safe than sorry. break; @@ -759,7 +759,7 @@ static std::optional> polyline_sample_next_point_at_dis return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); if (safe_step_size < 0 || last_step_offset_without_check < 0) { BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; - tree_supports_show_error("Negative offset distance... How did you manage this ?", true); + tree_supports_show_error("Negative offset distance... How did you manage this ?"sv, true); return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); } @@ -951,7 +951,7 @@ static void generate_initial_areas( bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; if (!mesh_config.support_rests_on_model && !to_bp) { BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; - tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly.", true); + tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); return; } Polygons circle{ base_circle }; @@ -1517,7 +1517,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (area(check_layer_data) < tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << volumes.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance); - tree_supports_show_error("Area lost catching up radius. May not cause visible malformation.", true); + tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true); } } } @@ -1783,7 +1783,7 @@ static void increase_areas_one_layer( "Parent " << &parent << ": Radius: " << config.getCollisionRadius(parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; - tree_supports_show_error("Potentially lost branch!", true); + tree_supports_show_error("Potentially lost branch!"sv, true); } else result = increase_single_area(volumes, config, settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); @@ -2270,7 +2270,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) { if (area(elem.areas.influence_areas) < tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; - tree_supports_show_error("Insert error of area after bypassing merge.\n", true); + tree_supports_show_error("Insert error of area after bypassing merge.\n"sv, true); } // Move the area to output. this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(elem.areas.influence_areas)); @@ -2303,7 +2303,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp Polygons new_area = safe_union(elem.areas.influence_areas); if (area(new_area) < tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate; - tree_supports_show_error("Insert error of area after merge.\n", true); + tree_supports_show_error("Insert error of area after merge.\n"sv, true); } this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(new_area)); } @@ -2331,7 +2331,7 @@ static void set_points_on_areas(const SupportElement &elem, SupportElements *lay // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. if (! elem.state.result_on_layer_is_set()) { BOOST_LOG_TRIVIAL(error) << "Uninitialized support element"; - tree_supports_show_error("Uninitialized support element. A branch may be missing.\n", true); + tree_supports_show_error("Uninitialized support element. A branch may be missing.\n"sv, true); return; } @@ -2394,7 +2394,7 @@ static void set_to_model_contact_to_model_gracious( // Could not find valid placement, even though it should exist => error handling if (last_successfull_layer == nullptr) { BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << first_elem.state.layer_idx; - tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); + tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches."sv, true); first_elem.state.to_model_gracious = false; set_to_model_contact_simple(first_elem); } else { @@ -2494,7 +2494,7 @@ static void create_nodes_from_area( if (elem.state.to_buildplate) { BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() << ") " "at target_height: " << elem.state.target_height << " layer: " << layer_idx; - tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially.", true); + tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially."sv, true); } // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set elem.state.deleted = true; diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 0d5e967d96..b0ab46891a 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -17,7 +17,7 @@ #include "BoundingBox.hpp" #include "Utils.hpp" - #define TREE_SUPPORT_SHOW_ERRORS +// #define TREE_SUPPORT_SHOW_ERRORS #ifdef SLIC3R_TREESUPPORTS_PROGRESS // The various stages of the process can be weighted differently in the progress bar. @@ -576,8 +576,7 @@ public: } }; -// todo Remove! ONLY FOR PUBLIC BETA!! -void tree_supports_show_error(std::string message, bool critical); +void tree_supports_show_error(std::string_view message, bool critical); } // namespace FFFTreeSupport From f656b2e62e439649da8af3f4d8d51af003ddb0af Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 17 Jan 2023 14:09:42 +0100 Subject: [PATCH 72/86] downloader: empty file fix --- src/slic3r/GUI/DownloaderFileGet.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp index deac45ad55..2e591a75a0 100644 --- a/src/slic3r/GUI/DownloaderFileGet.cpp +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -163,6 +163,8 @@ void FileGet::priv::get_perform() m_stopped = true; fclose(file); cancel = true; + if (m_written == 0) + std::remove(m_tmp_path.string().c_str()); wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED); evt->SetInt(m_id); m_evt_handler->QueueEvent(evt); From d9c7c675c467d23249d0b46852bb5061c8373caf Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 19 Jan 2022 17:10:37 +0100 Subject: [PATCH 73/86] download missing resources --- src/libslic3r/Preset.cpp | 23 +++++++++++ src/libslic3r/Preset.hpp | 1 + src/slic3r/Utils/PresetUpdater.cpp | 65 +++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 9dad49d4c4..8e7e53efc9 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2099,6 +2099,29 @@ namespace PresetUtils { } return out; } + + bool vendor_profile_has_all_resources(const VendorProfile& vp, bool in_cache) + { + std::string vendor_folder = Slic3r::data_dir() + "/vendor/" + vp.id + "/"; + std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/"; + std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/"; + for (const VendorProfile::PrinterModel& model : vp.models) + { + if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.bed_texture)) + && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.bed_texture)) + && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.bed_texture)))) + return false; + if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.bed_model)) + && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.bed_model)) + && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.bed_model)))) + return false; + if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.id + "_thumbnail.png")) + && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.id + "_thumbnail.png")) + && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.id + "_thumbnail.png")))) + return false; + } + return true; + } } // namespace PresetUtils } // namespace Slic3r diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 74ba2e0f5b..9d52801912 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -619,6 +619,7 @@ namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); std::string system_printer_bed_model(const Preset& preset); std::string system_printer_bed_texture(const Preset& preset); + bool vendor_profile_has_all_resources(const VendorProfile& vp, bool in_cache); } // namespace PresetUtils diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index f4863ff20a..1da8075f86 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -321,6 +321,31 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) const auto bundle_path = cache_path / (vendor.id + ".ini"); if (! get_file(bundle_url, bundle_path)) { continue; } if (cancel) { return; } + + // check the newly downloaded bundle for missing resources + // for that, the ini file must be parsed + auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const std::string& url, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path){ + if (!boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) + && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { + BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; + // testing url (to be removed) + //const auto resource_url = format("https://raw.githubusercontent.com/prusa3d/PrusaSlicer/master/resources/profiles/%1%/%2%", vendor, filename); + const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); + const auto resource_path = (cache_path / (vendor + "/" + filename)); + if (!fs::exists(resource_path.parent_path())) + fs::create_directory(resource_path.parent_path()); + self.get_file(resource_url, resource_path); + } + }; + auto vp = VendorProfile::from_ini(bundle_path, true); + for (const auto& model : vp.models) { + check_and_get_resource(vp.id, model.bed_texture, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + if (cancel) { return; } + check_and_get_resource(vp.id, model.bed_model, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + if (cancel) { return; } + check_and_get_resource(vp.id, model.id +"_thumbnail.png", vendor.config_update_url, vendor_path, rsrc_path, cache_path); + if (cancel) { return; } + } } } @@ -443,14 +468,21 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version fs::path bundle_path_idx_to_install; if (fs::exists(path_in_cache)) { try { - VendorProfile new_vp = VendorProfile::from_ini(path_in_cache, false); + VendorProfile new_vp = VendorProfile::from_ini(path_in_cache, true); if (new_vp.config_version == recommended->config_version) { // The config bundle from the cache directory matches the recommended version of the index from the cache directory. // This is the newest known recommended config. Use it. - new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); - // and install the config index from the cache into vendor's directory. - bundle_path_idx_to_install = idx.path(); - found = true; + if (PresetUtils::vendor_profile_has_all_resources(new_vp, true)) { + // All resources for the profile in cache dir are existing (either in resources or data_dir/vendor or waiting to be copied to vendor from cache) + // This final check is not performed for updates from resources dir below. + new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); + // and install the config index from the cache into vendor's directory. + bundle_path_idx_to_install = idx.path(); + found = true; + } else { + throw std::exception("Some resources are missing."); + } + } } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(info) << format("Failed to load the config bundle `%1%`: %2%", path_in_cache.string(), ex.what()); @@ -601,6 +633,29 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_prints) { obsolete_remover("sla_print", name); } for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } + + auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path) { + if ( !boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) + && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { + + if (!boost::filesystem::exists((cache_path / (vendor + "/" + filename)))) { + BOOST_LOG_TRIVIAL(error) << "Resources missing in cache directory: " << vendor << " / " << filename; + return; + } + if (!fs::exists((vendor_path / vendor))) + fs::create_directory((vendor_path / vendor)); + + copy_file_fix((cache_path / (vendor + "/" + filename)), (vendor_path / (vendor + "/" + filename))); + } + }; + // check if any resorces of installed bundle are missing. If so, new ones should be already downloaded at cache/vendor_id/ + auto vp = VendorProfile::from_ini(update.target, true); + for (const auto& model : vp.models) { + copy_missing_resource(vp.id, model.bed_texture, vendor_path, rsrc_path, cache_path); + copy_missing_resource(vp.id, model.bed_model, vendor_path, rsrc_path, cache_path); + copy_missing_resource(vp.id, model.id + "_thumbnail.png", vendor_path, rsrc_path, cache_path); + } + } } From 72540232c8857ce5819417fba9aec21748e86417 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 21 Feb 2022 15:08:55 +0100 Subject: [PATCH 74/86] Template filaments bundle with filament profiles available for all printers Profiles are ment to be adjusted and saved as user profile. Selectable in wizard under (Templates). Installed automatically even when profile with same alias is selected. Special category in combo boxes. no_templates option for disabling this. --- resources/profiles/TemplateFilaments.idx | 2 + resources/profiles/TemplateFilaments.ini | 1410 ++++++++++++++++++++++ src/libslic3r/AppConfig.cpp | 2 + src/libslic3r/Preset.cpp | 35 +- src/libslic3r/Preset.hpp | 1 + src/libslic3r/PresetBundle.cpp | 7 +- src/slic3r/GUI/ConfigWizard.cpp | 333 +++-- src/slic3r/GUI/ConfigWizard_private.hpp | 152 +-- src/slic3r/GUI/Preferences.cpp | 10 + src/slic3r/GUI/PresetComboBoxes.cpp | 57 +- src/slic3r/GUI/SavePresetDialog.cpp | 28 +- src/slic3r/GUI/SavePresetDialog.hpp | 9 +- src/slic3r/GUI/Tab.cpp | 29 +- src/slic3r/Utils/PresetUpdater.cpp | 2 +- 14 files changed, 1881 insertions(+), 196 deletions(-) create mode 100644 resources/profiles/TemplateFilaments.idx create mode 100644 resources/profiles/TemplateFilaments.ini diff --git a/resources/profiles/TemplateFilaments.idx b/resources/profiles/TemplateFilaments.idx new file mode 100644 index 0000000000..94223722f8 --- /dev/null +++ b/resources/profiles/TemplateFilaments.idx @@ -0,0 +1,2 @@ +min_slic3r_version = 2.4.0-rc +0.0.1 Initial diff --git a/resources/profiles/TemplateFilaments.ini b/resources/profiles/TemplateFilaments.ini new file mode 100644 index 0000000000..000e25f34c --- /dev/null +++ b/resources/profiles/TemplateFilaments.ini @@ -0,0 +1,1410 @@ +# Print profiles for the Prusa Research printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Templates Filaments +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the PrusaSlicer configuration to be downgraded. +config_version = 0.0.1 +# Where to get the updates from? +#config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ +# changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% +templates_profile = 1 + +## XXX--- Generic filament profiles ---XXX + +[filament:*common*] +cooling = 1 +compatible_printers = +compatible_printers_condition = +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = "" +filament_soluble = 0 +min_print_speed = 15 +slowdown_below_layer_time = 15 +start_filament_gcode = "; Filament gcode" + +[filament:*PLA*] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF8000 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:*PET*] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 50 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_type = PETG +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 0 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #FFF2EC +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*ABSC*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 1 +disable_fan_first_layers = 4 +fan_always_on = 0 +fan_below_layer_time = 30 +slowdown_below_layer_time = 20 +filament_colour = #FFF2EC +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 15 +min_fan_speed = 15 +min_print_speed = 15 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bed_temperature = 50 +bridge_fan_speed = 80 +cooling = 0 +disable_fan_first_layers = 3 +extrusion_multiplier = 1.15 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #008000 +filament_max_volumetric_speed = 1.5 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 240 +max_fan_speed = 90 +min_fan_speed = 70 +temperature = 240 + +[filament:ColorFabb bronzeFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.2 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #804040 + +[filament:ColorFabb steelFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.2 +filament_density = 3.13 +filament_spool_weight = 236 +filament_colour = #808080 + +[filament:ColorFabb copperFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.2 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #82603E + +[filament:ColorFabb HT] +inherits = *PET* +filament_vendor = ColorFabb +bed_temperature = 100 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_density = 1.18 +filament_spool_weight = 236 +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* +filament_vendor = ColorFabb +filament_density = 1.24 +filament_spool_weight = 236 + +[filament:ColorFabb woodFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_density = 1.15 +filament_spool_weight = 236 +filament_colour = #dfc287 +filament_max_volumetric_speed = 9 +first_layer_temperature = 200 +temperature = 200 + +[filament:ColorFabb corkFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_density = 1.18 +filament_spool_weight = 236 +filament_colour = #634d33 +first_layer_temperature = 220 +temperature = 220 + +[filament:ColorFabb XT] +inherits = *PET* +filament_vendor = ColorFabb +filament_density = 1.27 +filament_spool_weight = 236 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +filament_vendor = ColorFabb +extrusion_multiplier = 1.05 +filament_density = 1.35 +filament_spool_weight = 236 +filament_colour = #804040 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +filament_vendor = ColorFabb +filament_density = 1.2 +filament_spool_weight = 236 +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:ColorFabb nGen flex] +inherits = *FLEX* +filament_vendor = ColorFabb +filament_density = 1 +filament_spool_weight = 236 +bed_temperature = 85 +bridge_fan_speed = 40 +cooling = 1 +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +fan_below_layer_time = 10 +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +max_fan_speed = 35 +min_fan_speed = 20 +temperature = 260 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Kimya PETG Carbon] +inherits = *PET* +filament_vendor = Kimya +extrusion_multiplier = 1.05 +filament_density = 1.317 +filament_colour = #804040 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 240 +filament_retract_length = nil + +[filament:Kimya ABS Carbon] +inherits = *ABSC* +filament_vendor = Kimya +filament_density = 1.032 +filament_colour = #804040 +first_layer_temperature = 260 +temperature = 260 + +[filament:Kimya ABS Kevlar] +inherits = Kimya ABS Carbon +filament_vendor = Kimya +filament_density = 1.037 + +[filament:E3D Edge] +inherits = *PET* +filament_vendor = E3D +filament_density = 1.26 +filament_type = EDGE + +[filament:E3D PC-ABS] +inherits = *ABS* +filament_vendor = E3D +filament_type = PC +filament_density = 1.05 +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum PLA] +inherits = *PLA* +filament_vendor = Fillamentum +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Fillamentum ABS] +inherits = *ABSC* +filament_vendor = Fillamentum +filament_density = 1.04 +filament_spool_weight = 230 +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +filament_vendor = Fillamentum +filament_density = 1.07 +filament_spool_weight = 230 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 260 +temperature = 260 +filament_type = ASA + +[filament:Prusament ASA] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_density = 1.07 +filament_spool_weight = 201 +fan_always_on = 1 +first_layer_temperature = 260 +first_layer_bed_temperature = 105 +temperature = 260 +bed_temperature = 110 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 15 +disable_fan_first_layers = 4 +filament_type = ASA +filament_colour = #FFF2EC + +[filament:Prusament PC Blend] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_density = 1.22 +filament_spool_weight = 201 +fan_always_on = 0 +first_layer_temperature = 275 +first_layer_bed_temperature = 110 +temperature = 275 +bed_temperature = 115 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 20 +disable_fan_first_layers = 4 +fan_below_layer_time = 30 +filament_type = PC +filament_colour = #DEE0E6 + +[filament:Prusament PC Blend Carbon Fiber] +inherits = Prusament PC Blend +filament_density = 1.16 +extrusion_multiplier = 1.04 +first_layer_temperature = 285 +temperature = 285 +disable_fan_first_layers = 4 +fan_below_layer_time = 10 +filament_colour = #BBBBBB + +[filament:Fillamentum CPE] +inherits = *PET* +filament_vendor = Fillamentum +filament_density = 1.25 +filament_spool_weight = 230 +filament_type = CPE +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +min_fan_speed = 30 +max_fan_speed = 50 +disable_fan_first_layers = 3 +full_fan_speed_layer = 5 +temperature = 275 + +[filament:Fillamentum Timberfill] +inherits = *PLA* +filament_vendor = Fillamentum +extrusion_multiplier = 1.05 +filament_density = 1.15 +filament_spool_weight = 230 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 190 +temperature = 190 + +[filament:Smartfil Wood] +inherits = *PLA* +filament_vendor = Smart Materials 3D +extrusion_multiplier = 1.05 +filament_density = 1.58 +filament_colour = #804040 +first_layer_temperature = 220 +temperature = 220 + +[filament:Generic ABS] +inherits = *ABSC* +filament_vendor = Generic +filament_density = 1.04 + +[filament:Esun ABS] +inherits = *ABSC* +filament_vendor = Esun +filament_density = 1.01 +filament_spool_weight = 265 + +[filament:Hatchbox ABS] +inherits = *ABSC* +filament_vendor = Hatchbox +filament_density = 1.04 +filament_spool_weight = 245 + +[filament:Filament PM ABS] +inherits = *ABSC* +filament_vendor = Filament PM +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Verbatim ABS] +inherits = *ABSC* +filament_vendor = Verbatim +filament_density = 1.05 +filament_spool_weight = 235 + +[filament:Generic PETG] +inherits = *PET* +filament_vendor = Generic +filament_density = 1.27 + +[filament:Extrudr DuraPro ASA] +inherits = Fillamentum ASA +filament_vendor = Extrudr +bed_temperature = 90 +filament_density = 1.05 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=120" +first_layer_bed_temperature = 90 +first_layer_temperature = 220 +temperature = 220 +filament_spool_weight = 230 + +[filament:Extrudr PETG] +inherits = *PET* +filament_vendor = Extrudr +bed_temperature = 70 +filament_density = 1.29 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=94" +first_layer_bed_temperature = 70 +first_layer_temperature = 220 +temperature = 220 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 +full_fan_speed_layer = 0 + +[filament:Extrudr XPETG CF] +inherits = Extrudr PETG +filament_density = 1.41 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=198" +first_layer_temperature = 235 +temperature = 235 +compatible_printers_condition = nozzle_diameter[0]>=0.4 +filament_spool_weight = 230 + +[filament:Extrudr XPETG Matt] +inherits = Extrudr PETG +filament_density = 1.41 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=199" +first_layer_temperature = 230 +temperature = 230 + +[filament:Extrudr BioFusion] +inherits = *PLA* +filament_vendor = Extrudr +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_density = 1.25 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=121" +first_layer_temperature = 220 +temperature = 220 +max_fan_speed = 45 +min_fan_speed = 25 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr Flax] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.45 +filament_notes = "High Performance Filament for decorative parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=131" +first_layer_temperature = 190 +temperature = 190 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr GreenTEC] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=106" +first_layer_temperature = 208 +temperature = 208 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr GreenTEC Pro] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.35 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=134" +temperature = 215 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr GreenTEC Pro Carbon] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.2 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher stregnth and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=138" +first_layer_temperature = 225 +max_fan_speed = 80 +min_fan_speed = 30 +temperature = 225 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Extrudr PLA NX1] +inherits = *PLA* +filament_vendor = Extrudr +filament_density = 1.24 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=97" +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 0 +max_fan_speed = 90 +min_fan_speed = 30 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr PLA NX2] +inherits = Extrudr PLA NX1 +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=128" + +[filament:Extrudr Flex Hard] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +filament_density = 1.2 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex Medium] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +filament_density = 1.19 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex SemiSoft] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +filament_density = 1.18 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:addnorth Adamant S1] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_density = 1.22 +temperature = 250 +bed_temperature = 50 +first_layer_temperature = 245 +first_layer_bed_temperature = 50 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 40 +max_fan_speed = 70 +bridge_fan_speed = 60 +filament_spool_weight = 0 + +[filament:addnorth Adura X] +inherits = *PET* +filament_vendor = addnorth +filament_density = 1.27 +filament_type = NYLON +extrusion_multiplier = 1 +bed_temperature = 60 +first_layer_bed_temperature = 60 +first_layer_temperature = 265 +temperature = 270 +fan_always_on = 0 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +min_print_speed = 20 +fan_below_layer_time = 10 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:addnorth E-PLA] +inherits = *PLA* +filament_vendor = addnorth +filament_density = 1.24 +extrusion_multiplier = 1 +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +filament_spool_weight = 0 + +[filament:addnorth ESD-PETG] +inherits = *PET* +filament_vendor = addnorth +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 245 +temperature = 265 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 30 +bridge_fan_speed = 35 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 8 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:addnorth OBC Polyethylene] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_density = 1.22 +temperature = 200 +bed_temperature = 100 +first_layer_temperature = 195 +first_layer_bed_temperature = 100 +slowdown_below_layer_time = 5 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 30 +bridge_fan_speed = 40 +min_print_speed = 20 +filament_spool_weight = 0 +filament_notes = "Use Magigoo PP bed adhesive or PP packing tape (on a cold printbed)." + +[filament:addnorth PETG] +inherits = *PET* +filament_vendor = addnorth +filament_density = 1.27 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 250 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 40 +bridge_fan_speed = 50 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 15 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:addnorth Rigid X] +inherits = *PET* +filament_vendor = addnorth +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 85 +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +temperature = 260 +fan_always_on = 1 +min_fan_speed = 20 +max_fan_speed = 60 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +fan_below_layer_time = 20 +min_print_speed = 20 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 +filament_notes = "Please use a nozzle that is resistant to abrasive filaments, like hardened steel." +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:addnorth Textura] +inherits = *PLA* +filament_vendor = addnorth +filament_density = 1.24 +extrusion_multiplier = 0.95 +temperature = 215 +bed_temperature = 65 +first_layer_temperature = 215 +first_layer_bed_temperature = 65 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 60 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 15 +min_print_speed = 20 +filament_spool_weight = 0 +filament_retract_length = 1 + +[filament:Filamentworld ABS] +inherits = *ABSC* +filament_vendor = Filamentworld +filament_density = 1.04 +temperature = 230 +bed_temperature = 95 +first_layer_temperature = 240 +first_layer_bed_temperature = 100 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 20 +disable_fan_first_layers = 3 +fan_below_layer_time = 60 +slowdown_below_layer_time = 15 +bridge_fan_speed = 20 + +[filament:Filamentworld PETG] +inherits = *PET* +filament_vendor = Filamentworld +filament_density = 1.27 +bed_temperature = 70 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 235 +fan_always_on = 1 +min_fan_speed = 25 +max_fan_speed = 55 +bridge_fan_speed = 55 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 35 +disable_fan_first_layers = 2 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:Filamentworld PLA] +inherits = *PLA* +filament_vendor = Filamentworld +filament_density = 1.24 +temperature = 205 +bed_temperature = 55 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 10 +filament_spool_weight = 0 +min_print_speed = 20 + +[filament:Filament PM PETG] +inherits = *PET* +filament_vendor = Filament PM +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Generic PLA] +inherits = *PLA* +filament_vendor = Generic +filament_density = 1.24 + +[filament:Devil Design PLA] +inherits = *PLA* +filament_vendor = Devil Design +filament_density = 1.24 +filament_spool_weight = 250 + +[filament:Devil Design PETG] +inherits = *PET* +filament_vendor = Devil Design +filament_density = 1.23 +filament_spool_weight = 250 +first_layer_temperature = 230 +first_layer_bed_temperature = 85 +temperature = 230 +bed_temperature = 90 + +[filament:Spectrum PLA] +inherits = *PLA* +filament_vendor = Spectrum +filament_density = 1.24 + +[filament:Generic FLEX] +inherits = *FLEX* +filament_vendor = Generic +filament_density = 1.22 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil + +[filament:Fillamentum Flexfill 92A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_density = 1.20 +filament_spool_weight = 230 +filament_deretract_speed = 20 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 + +[filament:AmazonBasics TPU] +inherits = *FLEX* +filament_vendor = AmazonBasics +fan_always_on = 1 +extrusion_multiplier = 1.1 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 235 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_density = 1.21 +disable_fan_first_layers = 4 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:SainSmart TPU] +inherits = *FLEX* +filament_vendor = SainSmart +fan_always_on = 1 +filament_max_volumetric_speed = 2.5 +extrusion_multiplier = 1.1 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_density = 1.21 +disable_fan_first_layers = 4 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:Filatech FilaFlex40] +inherits = *FLEX* +filament_vendor = Filatech +fan_always_on = 1 +extrusion_multiplier = 1.1 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 50 +min_fan_speed = 50 +filament_density = 1.22 +disable_fan_first_layers = 4 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:Filatech FilaFlex30] +inherits = Filatech FilaFlex40 +temperature = 225 +filament_density = 1.15 +extrusion_multiplier = 1.1 + +[filament:Filatech FilaFlex55] +inherits = Filatech FilaFlex40 +temperature = 230 +filament_density = 1.18 +bed_temperature = 60 +fan_always_on = 0 +fan_below_layer_time = 60 +first_layer_temperature = 235 +extrusion_multiplier = 1 + +[filament:Filatech TPE] +inherits = Filatech FilaFlex40 +first_layer_temperature = 230 +temperature = 225 +filament_density = 1.2 +fan_below_layer_time = 60 +max_fan_speed = 80 +min_fan_speed = 80 +fan_always_on = 1 + +[filament:Filatech TPU] +inherits = Filatech FilaFlex40 +first_layer_temperature = 230 +filament_density = 1.2 +fan_below_layer_time = 60 +max_fan_speed = 80 +min_fan_speed = 80 +fan_always_on = 1 +temperature = 235 + +[filament:Filatech ABS] +inherits = *ABSC* +filament_vendor = Filatech +extrusion_multiplier = 0.95 +filament_density = 1.05 + +[filament:Filatech FilaCarbon] +inherits = *ABSC* +filament_vendor = Filatech +extrusion_multiplier = 0.95 +filament_density = 1.1 +first_layer_bed_temperature = 105 +bed_temperature = 100 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Filatech FilaPLA] +inherits = *PLA* +filament_vendor = Filatech +filament_density = 1.3 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 55 + +[filament:Filatech PLA] +inherits = *PLA* +filament_vendor = Filatech +filament_density = 1.25 +first_layer_temperature = 215 +temperature = 210 + +[filament:Filatech PLA+] +inherits = Filatech PLA +filament_density = 1.24 + +[filament:Filatech FilaTough] +inherits = Filatech ABS +extrusion_multiplier = 0.95 +filament_density = 1.29 +first_layer_temperature = 245 +first_layer_bed_temperature = 80 +temperature = 240 +bed_temperature = 90 +cooling = 0 + +[filament:Filatech HIPS] +inherits = Prusa HIPS +filament_vendor = Filatech +filament_density = 1.07 +filament_spool_weight = +first_layer_temperature = 230 +first_layer_bed_temperature = 100 +temperature = 225 +bed_temperature = 110 + +[filament:Filatech PA] +inherits = *ABSC* +filament_vendor = Filatech +filament_density = 1.1 +first_layer_temperature = 275 +first_layer_bed_temperature = 110 +temperature = 275 +bed_temperature = 115 +fan_always_on = 0 +cooling = 0 +bridge_fan_speed = 25 +filament_type = NYLON + +[filament:Filatech PC] +inherits = Filatech PA +first_layer_bed_temperature = 110 +bed_temperature = 115 +filament_density = 1.2 +filament_type = PC + +[filament:Filatech PC-ABS] +inherits = Filatech PC +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_density = 1.08 +filament_type = PC +fan_always_on = 0 +cooling = 1 +extrusion_multiplier = 0.95 +disable_fan_first_layers = 6 + +[filament:Filatech PETG] +inherits = *PET* +filament_vendor = Filatech +filament_density = 1.27 +first_layer_temperature = 240 +first_layer_bed_temperature = 75 +temperature = 245 +bed_temperature = 80 +extrusion_multiplier = 0.95 +fan_always_on = 0 + +[filament:Filatech Wood-PLA] +inherits = Filatech PLA +filament_density = 1.05 +first_layer_temperature = 210 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Ultrafuse PET] +inherits = *PET* +filament_vendor = BASF +filament_density = 1.33 +first_layer_temperature = 220 +first_layer_bed_temperature = 70 +temperature = 215 +bed_temperature = 70 +fan_below_layer_time = 10 +min_fan_speed = 75 +max_fan_speed = 100 +bridge_fan_speed = 100 +filament_type = PET +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +filament_notes = "BASF Forward AM Ultrafuse PET\nMaterial profile version 1.0\n\nMaterial Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion.\n" + +[filament:Ultrafuse PRO1] +inherits = Prusament PLA +filament_vendor = BASF +filament_density = 1.25 +filament_spool_weight = 0 +filament_colour = #FFFFFF +filament_notes = "BASF Forward AM Ultrafuse PLA PRO1\nMaterial profile version 1.0\n\nMaterial Description\nPLA PRO1 is an extremely versatile tough PLA filament made for professionals. It reduces your printing time by 30% – 80%, (subject to printer and object limitations) and the strength exceeds overall mechanical properties of printed ABS parts. Printer settings can be tuned to achieve blazing fast speeds or an unrivaled surface finish. The excellent quality control ensures the highest levels of consistency between colors and batches, it will perform as expected, every time.\n\nPrinting Recommendations:\nUltrafuse PLA PRO1 can be printed directly onto a clean build plate.\n" + +[filament:Ultrafuse ABS] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 1.04 +min_fan_speed = 10 +max_fan_speed = 20 +bed_temperature = 100 +disable_fan_first_layers = 3 +filament_colour = #FFFFFF +filament_notes = "BASF Forward AM Ultrafuse ABS\nMaterial profile version 1.0\n\nMaterial Description\nABS is the second most used 3D printing material. It is strong, flexible and has a high heat resistance. ABS is a preferred plastic for engineers and professional applications. ABS can be smoothened with acetone. To make a proper 3D print with ABS you will need a heated print bed. The filament is available in 9 colors.\n\nPrinting Recommendations:\n\nApply Tape, adhesion spray or glue to a clean build plate to improve adhesion.\n" + +[filament:Ultrafuse Metal] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 4.5 +extrusion_multiplier = 1.08 +first_layer_temperature = 250 +first_layer_bed_temperature = 100 +temperature = 250 +bed_temperature = 100 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 0 +cooling = 0 +fan_always_on = 0 +filament_max_volumetric_speed = 4 +filament_type = METAL +compatible_printers_condition = nozzle_diameter[0]>=0.4 +filament_colour = #FFFFFF + +[filament:Polymaker PC-Max] +inherits = *ABS* +filament_vendor = Polymaker +filament_density = 1.20 +filament_type = PC +bed_temperature = 115 +filament_colour = #FFF2EC +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 +bridge_fan_speed = 0 + +[filament:PrimaSelect PVA+] +inherits = *PLA* +filament_vendor = PrimaSelect +filament_density = 1.23 +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +temperature = 195 + +[filament:Prusa ABS] +inherits = *ABSC* +filament_vendor = Made for Prusa +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Prusa HIPS] +inherits = *ABS* +filament_vendor = Made for Prusa +filament_density = 1.04 +filament_spool_weight = 230 +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 220 +max_fan_speed = 20 +min_fan_speed = 20 +temperature = 220 + +[filament:Generic HIPS] +inherits = *ABS* +filament_vendor = Generic +filament_density = 1.04 +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 230 +max_fan_speed = 20 +min_fan_speed = 20 +temperature = 230 + +[filament:Prusa PETG] +inherits = *PET* +filament_vendor = Made for Prusa +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Verbatim PETG] +inherits = *PET* +filament_vendor = Verbatim +filament_density = 1.27 +filament_spool_weight = 235 + +[filament:Fiberlogy PETG] +inherits = *PET* +filament_vendor = Fiberlogy +filament_density = 1.27 + +[filament:Prusament PETG] +inherits = *PET* +filament_vendor = Prusa Polymers +first_layer_temperature = 240 +temperature = 250 +filament_density = 1.27 +filament_spool_weight = 201 +filament_type = PETG + +[filament:Prusa PLA] +inherits = *PLA* +filament_vendor = Made for Prusa +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Fiberlogy PLA] +inherits = *PLA* +filament_vendor = Fiberlogy +filament_density = 1.24 + +[filament:Filament PM PLA] +inherits = *PLA* +filament_vendor = Filament PM +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:AmazonBasics PLA] +inherits = *PLA* +filament_vendor = AmazonBasics +filament_density = 1.24 + +[filament:Overture PLA] +inherits = *PLA* +filament_vendor = Overture +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Hatchbox PLA] +inherits = *PLA* +filament_vendor = Hatchbox +filament_density = 1.27 +filament_spool_weight = 245 + +[filament:Esun PLA] +inherits = *PLA* +filament_vendor = Esun +filament_density = 1.24 +filament_spool_weight = 265 + +[filament:Das Filament PLA] +inherits = *PLA* +filament_vendor = Das Filament +filament_density = 1.24 + +[filament:EUMAKERS PLA] +inherits = *PLA* +filament_vendor = EUMAKERS +filament_density = 1.24 + +[filament:Floreon3D PLA] +inherits = *PLA* +filament_vendor = Floreon3D +filament_density = 1.24 + +[filament:Prusament PLA] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +filament_density = 1.24 +filament_spool_weight = 201 +filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" +compatible_printers_condition = nozzle_diameter[0]!=0.8 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) + +[filament:Prusament PVB] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +bed_temperature = 75 +first_layer_bed_temperature = 75 +filament_density = 1.09 +filament_spool_weight = 201 +filament_type = PVB +filament_soluble = 1 +filament_colour = #FFFF6F +slowdown_below_layer_time = 20 + +[filament:Fillamentum Flexfill 98A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_density = 1.23 +filament_spool_weight = 230 +extrusion_multiplier = 1.1 +filament_max_volumetric_speed = 1.35 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 + +[filament:Taulman Bridge] +inherits = *common* +filament_vendor = Taulman +filament_density = 1.13 +bed_temperature = 90 +bridge_fan_speed = 40 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = NYLON +first_layer_bed_temperature = 60 +first_layer_temperature = 240 +max_fan_speed = 0 +min_fan_speed = 0 +temperature = 250 + +[filament:Fillamentum Nylon FX256] +inherits = *common* +filament_vendor = Fillamentum +filament_density = 1.01 +filament_spool_weight = 230 +bed_temperature = 90 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 6 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 6 +filament_soluble = 0 +filament_type = NYLON +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +max_fan_speed = 0 +min_fan_speed = 0 +temperature = 250 + +[filament:Fiberthree F3 PA Pure Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_density = 1.2 +bed_temperature = 70 +first_layer_bed_temperature = 75 +first_layer_temperature = 270 +temperature = 270 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = NYLON +max_fan_speed = 20 +min_fan_speed = 20 + +[filament:Fiberthree F3 PA-CF Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_density = 1.25 +bed_temperature = 70 +first_layer_bed_temperature = 75 +first_layer_temperature = 275 +temperature = 275 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = NYLON +max_fan_speed = 0 +min_fan_speed = 0 +compatible_printers_condition = nozzle_diameter[0]>=0.4 + +[filament:Fiberthree F3 PA-GF Pro] +inherits = Fiberthree F3 PA-CF Pro +filament_vendor = Fiberthree +filament_density = 1.27 +fan_always_on = 1 +max_fan_speed = 15 +min_fan_speed = 15 + +[filament:Taulman T-Glase] +inherits = *PET* +filament_vendor = Taulman +filament_density = 1.27 +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 + +[filament:Verbatim PLA] +inherits = *PLA* +filament_vendor = Verbatim +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Verbatim BVOH] +inherits = *common* +filament_vendor = Verbatim +filament_density = 1.14 +filament_spool_weight = 235 +bed_temperature = 60 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +filament_vendor = Verbatim +filament_density = 0.89 +filament_spool_weight = 235 +bed_temperature = 100 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_type = PP +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 220 + diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 8c879cde05..4029444e2f 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -68,6 +68,8 @@ void AppConfig::set_defaults() // If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. if (get("no_defaults").empty()) set("no_defaults", "1"); + if (get("no_templates").empty()) + set("no_templates", "0"); if (get("show_incompatible_presets").empty()) set("show_incompatible_presets", "0"); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 8e7e53efc9..486453bc60 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -146,6 +146,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem res.changelog_url = changelog_url->second.data(); } + const auto templates_profile = vendor_section.find("templates_profile"); + if (templates_profile != vendor_section.not_found()) { + res.templates_profile = templates_profile->second.data() == "1"; + } + if (! load_all) { return res; } @@ -336,7 +341,8 @@ std::string Preset::label() const bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer) { - if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) + // templates_profile vendor profiles should be decided as same vendor profiles + if (preset.vendor != nullptr && preset.vendor != active_printer.vendor && !preset.vendor->templates_profile) // The current profile has a vendor assigned and it is different from the active print's vendor. return false; auto &condition = preset.preset.compatible_prints_condition(); @@ -358,7 +364,8 @@ bool is_compatible_with_print(const PresetWithVendorProfile &preset, const Prese bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config) { - if (preset.vendor != nullptr && preset.vendor != active_printer.vendor) + // templates_profile vendor profiles should be decided as same vendor profiles + if (preset.vendor != nullptr && preset.vendor != active_printer.vendor && !preset.vendor->templates_profile) // The current profile has a vendor assigned and it is different from the active print's vendor. return false; auto &condition = preset.preset.compatible_printers_condition(); @@ -1185,6 +1192,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil if (opt) config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); bool some_compatible = false; + std::vector indices_of_template_presets; for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) { bool selected = idx_preset == m_idx_selected; Preset &preset_selected = m_presets[idx_preset]; @@ -1201,7 +1209,30 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil m_idx_selected = size_t(-1); if (selected) preset_selected.is_compatible = preset_edited.is_compatible; + if (preset_edited.vendor && preset_edited.vendor->templates_profile) { + indices_of_template_presets.push_back(idx_preset); + } } + // filter out template profiles where profile with same alias and compability exists + if (!indices_of_template_presets.empty()) { + for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++idx_preset) { + if (m_presets[idx_preset].vendor && !m_presets[idx_preset].vendor->templates_profile && m_presets[idx_preset].is_compatible) { + std::string preset_alias = m_presets[idx_preset].alias; + for (size_t idx_in_templates = 0; idx_in_templates < indices_of_template_presets.size(); ++idx_in_templates) { + size_t idx_of_template_in_presets = indices_of_template_presets[idx_in_templates]; + if (m_presets[idx_of_template_in_presets].alias == preset_alias) { + // unselect selected template filament if there is non-template alias compatible + if (idx_of_template_in_presets == m_idx_selected && (unselect_if_incompatible == PresetSelectCompatibleType::Always || unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible)) { + m_idx_selected = size_t(-1); + } + m_presets[idx_of_template_in_presets].is_compatible = false; + break; + } + } + } + } + } + // Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile. if (m_idx_selected >= m_num_default_presets && m_default_suppressed) for (size_t i = 0; i < m_num_default_presets; ++ i) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 9d52801912..35d4b5e843 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -34,6 +34,7 @@ public: Semver config_version; std::string config_update_url; std::string changelog_url; + bool templates_profile { false }; struct PrinterVariant { PrinterVariant() {} diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index b783c8aebc..f3fd10a403 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1314,10 +1314,10 @@ std::pair PresetBundle::load_configbundle( const VendorProfile *vendor_profile = nullptr; if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) { auto vp = VendorProfile::from_ini(tree, path); - if (vp.models.size() == 0) { + if (vp.models.size() == 0 && !vp.templates_profile) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; return std::make_pair(PresetsConfigSubstitutions{}, 0); - } else if (vp.num_variants() == 0) { + } else if (vp.num_variants() == 0 && !vp.templates_profile) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path; return std::make_pair(PresetsConfigSubstitutions{}, 0); } @@ -1359,6 +1359,9 @@ std::pair PresetBundle::load_configbundle( } else if (boost::starts_with(section.first, "filament:")) { presets = &this->filaments; preset_name = section.first.substr(9); + if (vendor_profile->templates_profile) { + preset_name += " @Template"; + } } else if (boost::starts_with(section.first, "sla_print:")) { presets = &this->sla_prints; preset_name = section.first.substr(10); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index ef6f53bf08..38a4f7e361 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -620,6 +620,7 @@ void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) const std::string PageMaterials::EMPTY; +const std::string PageMaterials::TEMPLATES = "templates"; PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) : ConfigWizardPage(parent, std::move(title), std::move(shortname)) @@ -727,6 +728,11 @@ void PageMaterials::reload_presets() clear(); list_printer->append(_L("(All)"), &EMPTY); + + const AppConfig* app_config = wxGetApp().app_config; + if (materials->technology == T_FFF && app_config->get("no_templates") == "0") + list_printer->append(_L("(Templates)"), &TEMPLATES); + //list_printer->SetLabelMarkup("bald"); for (const Preset* printer : materials->printers) { list_printer->append(printer->name, &printer->name); @@ -758,67 +764,74 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector* 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 - ); + if (materials->technology == T_FFF && template_shown) { + text = format_wxstr(_L("%1% visible for (\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); } 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( - "" - ""); - } + 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")); + + 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" + "
%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
" + "" + "" + "" + "" + ); } - text += wxString::Format( - "" - "" - "
" - "
" - "" - "" - ); } + wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); const int fs = font.GetPointSize(); @@ -863,6 +876,8 @@ void PageMaterials::on_material_highlighted(int sel_material) std::vector names; for (const Preset* printer : materials->printers) { for (const Preset* material : matching_materials) { + if (material->vendor && material->vendor->templates_profile) + continue; if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { names.push_back(printer->name); break; @@ -880,6 +895,8 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected wxArrayInt sel_printers; int sel_printers_count = list_printer->GetSelections(sel_printers); + bool templates_available = list_printer->size() > 1 && list_printer->get_data(1) == TEMPLATES; + // Does our wxWidgets version support operator== for wxArrayInt ? // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 #if wxCHECK_VERSION(3, 1, 1) @@ -895,13 +912,14 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected }; if (!are_equal(sel_printers, sel_printers_prev)) { #endif - + template_shown = false; // Refresh type list list_type->Clear(); list_type->append(_L("(All)"), &EMPTY); - if (sel_printers_count > 0) { + if (sel_printers_count > 1) { // If all is selected with other printers // unselect "all" or all printers depending on last value + // same with "templates" if (sel_printers[0] == 0 && sel_printers_count > 1) { if (last_selected_printer == 0) { list_printer->SetSelection(wxNOT_FOUND); @@ -911,38 +929,63 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected 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); + if (materials->technology == T_FFF && templates_available && (sel_printers[0] == 1 || sel_printers[1] == 1) && sel_printers_count > 1) { + if (last_selected_printer == 1) { + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(1); + } + else if (last_selected_printer != 0) { + list_printer->SetSelection(1, false); + sel_printers_count = list_printer->GetSelections(sel_printers); + } + } + } + if (sel_printers_count > 0 && sel_printers[0] != 0 && ((materials->technology == T_FFF && templates_available && sel_printers[0] != 1) || materials->technology != T_FFF || !templates_available)) { + 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, printer_name, 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 if (sel_printers_count > 0 && last_selected_printer == 0) { + //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); - } + materials->filter_presets(nullptr, EMPTY, 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 if (materials->technology == T_FFF && templates_available && sel_printers_count > 0 && last_selected_printer == 1) { + //clear selection except "TEMPLATES" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(1); + sel_printers_count = list_printer->GetSelections(sel_printers); + template_shown = true; + materials->filter_presets(nullptr, TEMPLATES, 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; @@ -971,7 +1014,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected break; } } - materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) { + materials->filter_presets(printer, printer_name, 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); @@ -996,7 +1039,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected 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 + // first 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]); @@ -1007,15 +1050,14 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected break; } } - - materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) { + materials->filter_presets(printer, printer_name, 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); + cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) || template_shown ? "" : " *"), &p->alias); to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); } else { @@ -1053,10 +1095,15 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat std::vector> prusa_profiles; std::vector> other_profiles; + bool add_TEMPLATES_item = false; for (int i = 0 ; i < list->size(); ++i) { const std::string& data = list->get_data(i); - if (data == EMPTY) // do not sort item + if (data == EMPTY) // do not sort item continue; + if (data == TEMPLATES) {// do not sort item + add_TEMPLATES_item = true; + continue; + } if (!material_type_ordering && data.find("Prusa") != std::string::npos) prusa_profiles.push_back(data); else @@ -1095,10 +1142,13 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat list->Clear(); if (add_All_item) list->append(_L("(All)"), &EMPTY); + if (materials->technology == T_FFF && add_TEMPLATES_item) + list->append(_L("(Templates)"), &TEMPLATES); 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) @@ -1125,11 +1175,11 @@ void PageMaterials::sort_list_data(PresetList* list, const std::vectorClear(); 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->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent || template_shown ? "" : " *"), &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->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast(other_profiles[i].name.get())); list->Check(i + prusa_profiles.size(), other_profiles[i].checked); } } @@ -1139,6 +1189,15 @@ void PageMaterials::select_material(int i) const bool checked = list_profile->IsChecked(i); const std::string& alias_key = list_profile->get_data(i); + if (checked && template_shown && !notification_shown) { + notification_shown = true; + wxString message = _L("You have selelected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); + MessageDialog msg(this, message, _L("Notice"), wxYES_NO); + if (msg.ShowModal() == wxID_NO) { + list_profile->Check(i, false); + return; + } + } wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); } @@ -2197,13 +2256,19 @@ void ConfigWizard::priv::load_pages() index->add_page(page_temps); } - // Filaments & Materials + // Filaments & Materials if (any_fff_selected) { index->add_page(page_filaments); } + // Filaments page if only custom printer is selected + const AppConfig* app_config = wxGetApp().app_config; + if (!any_fff_selected && (custom_printer_selected || custom_printer_in_bundle) && (app_config->get("no_templates") == "0")) { + update_materials(T_ANY); + 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); + btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected || custom_printer_in_bundle); index->add_page(page_update); index->add_page(page_downloader); @@ -2265,6 +2330,13 @@ void ConfigWizard::priv::load_vendors() } } + for (const auto& printer : wxGetApp().preset_bundle->printers) { + if (!printer.is_default && !printer.is_system && printer.is_visible) { + custom_printer_in_bundle = true; + break; + } + } + // Initialize the is_visible flag in printer Presets for (auto &pair : bundles) { pair.second.preset_bundle->load_installed_printers(appconfig_new); @@ -2386,7 +2458,7 @@ void ConfigWizard::priv::set_run_reason(RunReason run_reason) void ConfigWizard::priv::update_materials(Technology technology) { - if (any_fff_selected && (technology & T_FFF)) { + if ((any_fff_selected || custom_printer_in_bundle || custom_printer_selected) && (technology & T_FFF)) { filaments.clear(); aliases_fff.clear(); // Iterate filaments in all bundles @@ -2409,11 +2481,22 @@ void ConfigWizard::priv::update_materials(Technology technology) filaments.add_printer(&printer); } } - + // template filament bundle has no printers - filament would be never added + if(pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) + { + if (!filaments.containts(&filament)) { + filaments.push(&filament); + if (!filament.alias.empty()) + aliases_fff[filament.alias].insert(filament.name); + } + } } } // count compatible printers for (const auto& preset : filaments.presets) { + // skip template filaments + if (preset->vendor && preset->vendor->templates_profile) + continue; const auto filter = [preset](const std::pair element) { return preset->alias == element.first; @@ -2421,17 +2504,19 @@ void ConfigWizard::priv::update_materials(Technology technology) if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { continue; } + // find all aliases (except templates) std::vector idx_with_same_alias; for (size_t i = 0; i < filaments.presets.size(); ++i) { - if (preset->alias == filaments.presets[i]->alias) + if (preset->alias == filaments.presets[i]->alias && ((filaments.presets[i]->vendor && !filaments.presets[i]->vendor->templates_profile) || !filaments.presets[i]->vendor)) idx_with_same_alias.push_back(i); } + // check compatibility with each printer 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 + // Test other 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; @@ -2694,6 +2779,21 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo has_material = true; break; } + + // find if preset.first is part of the templates profile (up is searching if preset.first is part of printer vendor preset) + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material && is_compatible_with_printer(PresetWithVendorProfile(*template_material, &bp.second.preset_bundle->vendors.begin()->second), PresetWithVendorProfile(printer, nullptr))) { + has_material = true; + break; + } + } + } + if (has_material) + break; + } } if (! has_material) @@ -2702,6 +2802,23 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo } } } + // template_profile_selected check + template_profile_selected = false; + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + for (const auto& preset : appconfig_presets) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material){ + template_profile_selected = true; + break; + } + } + if (template_profile_selected) { + break; + } + } + } 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; }; @@ -2865,7 +2982,13 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } const auto vendor = enabled_vendors.find(pair.first); - if (vendor == enabled_vendors.end()) { continue; } + if (vendor == enabled_vendors.end() && ((pair.second.vendor_profile && !pair.second.vendor_profile->templates_profile) || !pair.second.vendor_profile) ) { continue; } + + if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile && vendor == enabled_vendors.end()) { + // Templates vendor needs to be installed + install_bundles.emplace_back(pair.first); + continue; + } size_t size_sum = 0; for (const auto &model : vendor->second) { size_sum += model.second.size(); } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index a8ac09d9b1..0d63de95d9 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -84,74 +84,8 @@ struct BundleMap : std::map const Bundle& prusa_bundle() const; }; -struct Materials -{ - Technology technology; - // use vector for the presets to purpose of save of presets sorting in the bundle - std::vector presets; - // String is alias of material, size_t number of compatible counters - std::vector> compatibility_counter; - std::set types; - std::set printers; +struct Materials; - Materials(Technology technology) : technology(technology) {} - - void push(const Preset *preset); - void add_printer(const Preset* preset); - void clear(); - bool containts(const Preset *preset) const { - //return std::find(presets.begin(), presets.end(), preset) != presets.end(); - return std::find_if(presets.begin(), presets.end(), - [preset](const Preset* element) { return element == preset; }) != presets.end(); - - } - - bool get_omnipresent(const Preset* preset) { - return get_printer_counter(preset) == printers.size(); - } - - const std::vector get_presets_by_alias(const std::string name) { - std::vector ret_vec; - for (auto it = presets.begin(); it != presets.end(); ++it) { - if ((*it)->alias == name) - ret_vec.push_back((*it)); - } - return ret_vec; - } - - - - size_t get_printer_counter(const Preset* preset) { - for (auto it : compatibility_counter) { - if (it.first == preset->alias) - return it.second; - } - return 0; - } - - const std::string& appconfig_section() const; - const std::string& get_type(const Preset *preset) const; - const std::string& get_vendor(const Preset *preset) const; - - template void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) { - for (auto preset : presets) { - const Preset& prst = *(preset); - const Preset& prntr = *printer; - if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) && - (type.empty() || get_type(preset) == type) && - (vendor.empty() || get_vendor(preset) == vendor)) { - - cb(preset); - } - } - } - - static const std::string UNKNOWN; - static const std::string& get_filament_type(const Preset *preset); - static const std::string& get_filament_vendor(const Preset *preset); - static const std::string& get_material_type(const Preset *preset); - static const std::string& get_material_vendor(const Preset *preset); -}; struct PrinterPickerEvent; @@ -344,6 +278,10 @@ struct PageMaterials: ConfigWizardPage std::string empty_printers_label; bool first_paint = { false }; static const std::string EMPTY; + static const std::string TEMPLATES; + // notify user first time they choose template profile + bool template_shown = { false }; + bool notification_shown = { false }; int last_hovered_item = { -1 } ; PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name); @@ -368,6 +306,82 @@ struct PageMaterials: ConfigWizardPage virtual void on_activate() override; }; +struct Materials +{ + Technology technology; + // use vector for the presets to purpose of save of presets sorting in the bundle + std::vector presets; + // String is alias of material, size_t number of compatible counters + std::vector> compatibility_counter; + std::set types; + std::set printers; + + Materials(Technology technology) : technology(technology) {} + + void push(const Preset* preset); + void add_printer(const Preset* preset); + void clear(); + bool containts(const Preset* preset) const { + //return std::find(presets.begin(), presets.end(), preset) != presets.end(); + return std::find_if(presets.begin(), presets.end(), + [preset](const Preset* element) { return element == preset; }) != presets.end(); + + } + + bool get_omnipresent(const Preset* preset) { + return get_printer_counter(preset) == printers.size(); + } + + const std::vector get_presets_by_alias(const std::string name) { + std::vector ret_vec; + for (auto it = presets.begin(); it != presets.end(); ++it) { + if ((*it)->alias == name) + ret_vec.push_back((*it)); + } + return ret_vec; + } + + + + size_t get_printer_counter(const Preset* preset) { + for (auto it : compatibility_counter) { + if (it.first == preset->alias) + return it.second; + } + return 0; + } + + const std::string& appconfig_section() const; + const std::string& get_type(const Preset* preset) const; + const std::string& get_vendor(const Preset* preset) const; + + template void filter_presets(const Preset* printer, const std::string& printer_name, const std::string& type, const std::string& vendor, F cb) { + for (auto preset : presets) { + const Preset& prst = *(preset); + const Preset& prntr = *printer; + if (((printer == nullptr && printer_name == PageMaterials::EMPTY) || (printer != nullptr && is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor)))) && + (type.empty() || get_type(preset) == type) && + (vendor.empty() || get_vendor(preset) == vendor) && + !prst.vendor->templates_profile) { + + cb(preset); + } + else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor->templates_profile && + (type.empty() || get_type(preset) == type) && + (vendor.empty() || get_vendor(preset) == vendor)) { + cb(preset); + } + } + } + + static const std::string UNKNOWN; + static const std::string& get_filament_type(const Preset* preset); + static const std::string& get_filament_vendor(const Preset* preset); + static const std::string& get_material_type(const Preset* preset); + static const std::string& get_material_vendor(const Preset* preset); +}; + + struct PageCustom: ConfigWizardPage { PageCustom(ConfigWizard *parent); @@ -608,9 +622,11 @@ struct ConfigWizard::priv std::unique_ptr custom_config; // Backing for custom printer definition bool any_fff_selected; // Used to decide whether to display Filaments page bool any_sla_selected; // Used to decide whether to display SLA Materials page - bool custom_printer_selected { false }; + bool custom_printer_selected { false }; // New custom printer is requested + bool custom_printer_in_bundle { false }; // Older custom printer already exists when wizard starts // Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers) bool only_sla_mode { false }; + bool template_profile_selected { false }; // This bool has one purpose - to tell that template profile should be installed if its not (because it cannot be added to appconfig) wxScrolledWindow *hscroll = nullptr; wxBoxSizer *hscroll_sizer = nullptr; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 106937c46b..cc57bcffdc 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -301,6 +301,11 @@ void PreferencesDialog::build() L("Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available."), app_config->get("no_defaults") == "1"); + append_bool_option(m_optgroup_general, "no_templates", + L("Suppress \" Template \" filament presets"), + L("Suppress \" Template \" filament presets in configuration wizard and sidebar visibility."), + app_config->get("no_templates") == "1"); + append_bool_option(m_optgroup_general, "show_incompatible_presets", L("Show incompatible print and filament presets"), L("When checked, the print and filament presets are shown in the preset editor " @@ -692,6 +697,8 @@ void PreferencesDialog::accept(wxEvent&) DesktopIntegrationDialog::perform_desktop_integration(true); #endif // __linux__ + bool update_filament_sidebar = (m_values.find("no_templates") != m_values.end()); + std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" }; for (const std::string& option : options_to_recreate_GUI) { @@ -761,6 +768,9 @@ void PreferencesDialog::accept(wxEvent&) wxGetApp().update_ui_from_settings(); clear_cache(); + + if (update_filament_sidebar) + wxGetApp().plater()->sidebar().update_presets(Preset::Type::TYPE_FILAMENT); } void PreferencesDialog::revert(wxEvent&) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index c4d0fc6706..be34f6c5da 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -836,6 +836,7 @@ void PlaterPresetComboBox::update() null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width; std::map nonsys_presets; + std::map template_presets; wxString selected_user_preset; wxString tooltip; @@ -883,10 +884,18 @@ void PlaterPresetComboBox::update() const std::string name = preset.alias.empty() ? preset.name : preset.alias; if (preset.is_default || preset.is_system) { - Append(get_preset_name(preset), *bmp); - validate_selection(is_selected); - if (is_selected) - tooltip = from_u8(preset.name); + if (preset.vendor && preset.vendor->templates_profile) { + template_presets.emplace(get_preset_name(preset), bmp); + if (is_selected) { + selected_user_preset = get_preset_name(preset); + tooltip = from_u8(preset.name); + } + } else { + Append(get_preset_name(preset), *bmp); + validate_selection(is_selected); + if (is_selected) + tooltip = from_u8(preset.name); + } } else { @@ -899,6 +908,15 @@ void PlaterPresetComboBox::update() if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } + + const AppConfig* app_config = wxGetApp().app_config; + if (!template_presets.empty() && app_config->get("no_templates") == "0") { + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + Append(it->first, *it->second); + validate_selection(it->first == selected_user_preset); + } + } if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); @@ -1046,6 +1064,8 @@ void TabPresetComboBox::update() const std::deque& presets = m_collection->get_presets(); std::map> nonsys_presets; + std::map> template_presets; + wxString selected = ""; if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); @@ -1078,11 +1098,19 @@ void TabPresetComboBox::update() auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); - if (preset.is_default || preset.is_system) { - int item_id = Append(get_preset_name(preset), *bmp); - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(i == idx_selected); + if (preset.is_default || preset.is_system) { + if (preset.vendor && preset.vendor->templates_profile) { + template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + if (i == idx_selected) + selected = get_preset_name(preset); + } else { + int item_id = Append(get_preset_name(preset), *bmp); + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(i == idx_selected); + } + + } else { @@ -1094,6 +1122,17 @@ void TabPresetComboBox::update() if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } + const AppConfig* app_config = wxGetApp().app_config; + if (!template_presets.empty() && app_config->get("no_templates") == "0") { + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } + } if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index d9ee5f58c1..d4c2ba58ca 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -285,17 +285,17 @@ void SavePresetDialog::Item::Enable(bool enable /*= true*/) // SavePresetDialog //----------------------------------------------- -SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix) +SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix, bool template_filament) : DPIDialog(parent, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) { - build(std::vector{type}, suffix); + build(std::vector{type}, suffix, template_filament); } -SavePresetDialog::SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix, PresetBundle* preset_bundle/* = nullptr*/) +SavePresetDialog::SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix, bool template_filament/* =false*/, PresetBundle* preset_bundle/* = nullptr*/) : DPIDialog(parent, wxID_ANY, _L("Save presets"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER), m_preset_bundle(preset_bundle) { - build(types, suffix); + build(types, suffix, template_filament); } SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention) @@ -312,7 +312,7 @@ SavePresetDialog::~SavePresetDialog() } } -void SavePresetDialog::build(std::vector types, std::string suffix) +void SavePresetDialog::build(std::vector types, std::string suffix, bool template_filament) { #if defined(__WXMSW__) // ys_FIXME! temporary workaround for correct font scaling @@ -341,6 +341,15 @@ void SavePresetDialog::build(std::vector types, std::string suffix btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(enable_ok_btn()); }); topSizer->Add(m_presets_sizer, 0, wxEXPAND | wxALL, BORDER_W); + + // Add checkbox for Template filament saving + if (template_filament && types.size() == 1 && *types.begin() == Preset::Type::TYPE_FILAMENT) { + m_template_filament_checkbox = new wxCheckBox(this, wxID_ANY, _L("Save as profile derived from current printer only.")); + wxBoxSizer* check_sizer = new wxBoxSizer(wxVERTICAL); + check_sizer->Add(m_template_filament_checkbox); + topSizer->Add(check_sizer, 0, wxEXPAND | wxALL, BORDER_W); + } + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); SetSizer(topSizer); @@ -371,6 +380,15 @@ std::string SavePresetDialog::get_name(Preset::Type type) return ""; } +bool SavePresetDialog::get_template_filament_checkbox() +{ + if (m_template_filament_checkbox) + { + return m_template_filament_checkbox->GetValue(); + } + return false; +} + bool SavePresetDialog::enable_ok_btn() const { for (const Item* item : m_items) diff --git a/src/slic3r/GUI/SavePresetDialog.hpp b/src/slic3r/GUI/SavePresetDialog.hpp index e34ed9e5df..0199e27dec 100644 --- a/src/slic3r/GUI/SavePresetDialog.hpp +++ b/src/slic3r/GUI/SavePresetDialog.hpp @@ -76,6 +76,7 @@ private: wxStaticText* m_label {nullptr}; wxBoxSizer* m_radio_sizer {nullptr}; ActionType m_action {UndefAction}; + wxCheckBox* m_template_filament_checkbox {nullptr}; std::string m_ph_printer_name; std::string m_old_preset_name; @@ -86,10 +87,11 @@ private: public: +<<<<<<< master const wxString& get_info_line_extention() { return m_info_line_extention; } - SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = ""); - SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix = "", PresetBundle* preset_bundle = nullptr); + SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = "", bool template_filament = false); + SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix = "", bool template_filament = false, PresetBundle* preset_bundle = nullptr); SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention); ~SavePresetDialog() override; @@ -105,12 +107,13 @@ public: bool Layout() override; bool is_for_rename() { return m_use_for_rename; } + bool get_template_filament_checkbox(); protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override {} private: - void build(std::vector types, std::string suffix = ""); + void build(std::vector types, std::string suffix = "", bool template_filament = false); void update_physical_printers(const std::string& preset_name); void accept(); }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ad7b92e2e6..a90287689a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3715,11 +3715,25 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // focus currently.is there anything better than this ? //! m_treectrl->OnSetFocus(); + auto& old_preset = m_presets->get_edited_preset(); + bool from_template = false; + std::string edited_printer; + if (m_type == Preset::TYPE_FILAMENT && old_preset.vendor && old_preset.vendor->templates_profile) + { + //TODO: is this really the best way to get "printer_model" option of currently edited printer? + edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt("printer_model")->serialize(); + if (!edited_printer.empty()) + from_template = true; + + } + if (name.empty()) { - SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : ""); + SavePresetDialog dlg(m_parent, m_type, detach ? _u8L("Detached") : "", from_template); if (dlg.ShowModal() != wxID_OK) return; name = dlg.get_name(); + if (from_template) + from_template = dlg.get_template_filament_checkbox(); } if (detach && m_type == Preset::TYPE_PRINTER) @@ -3731,6 +3745,19 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) if (detach && m_type == Preset::TYPE_PRINTER) wxGetApp().mainframe->on_config_changed(m_config); + // Update compatible printers + if (from_template && !edited_printer.empty()) { + auto& new_preset = m_presets->get_edited_preset(); + std::string cond = new_preset.compatible_printers_condition(); + if (!cond.empty()) + cond += " and "; + cond += "printer_model == \""+edited_printer+"\""; + new_preset.config.set("compatible_printers_condition", cond); + new_preset.save(); + m_presets->save_current_preset(name, detach); + load_current_preset(); + } + // Mark the print & filament enabled if they are compatible with the currently selected preset. // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 1da8075f86..cee611bdb4 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -480,7 +480,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version bundle_path_idx_to_install = idx.path(); found = true; } else { - throw std::exception("Some resources are missing."); + throw Slic3r::CriticalException("Some resources are missing."); } } From a15ad698d7b722e36c0d826967bff06b0ce9b192 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 27 Feb 2022 18:30:36 +0100 Subject: [PATCH 75/86] download profile bundles in zip archive --- src/libslic3r/AppConfig.cpp | 12 + src/libslic3r/AppConfig.hpp | 2 + src/libslic3r/PresetBundle.cpp | 1 + src/slic3r/GUI/ConfigWizard.cpp | 6972 ++++++++++++----------- src/slic3r/GUI/ConfigWizard_private.hpp | 11 +- src/slic3r/Utils/PresetUpdater.cpp | 140 +- 6 files changed, 3614 insertions(+), 3524 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 4029444e2f..44658e852e 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -36,6 +36,10 @@ static const std::string MODEL_PREFIX = "model:"; // are phased out, then we will revert to the original name. //static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; +// url to folder with profile archive zip +// TODO: Uncomment and delete 2nd when we have archive online +//static const std::string PROFILE_ARCHIVE_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Archive/Archive.zip"; +static const std::string PROFILE_ARCHIVE_URL = "https://raw.githubusercontent.com/kocikdav/PrusaSlicer-settings/master/live/Bundle/Archive.zip"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; @@ -667,6 +671,14 @@ std::string AppConfig::version_check_url() const return from_settings.empty() ? VERSION_CHECK_URL : from_settings; } +std::string AppConfig::profile_archive_url() const +{ + // Do we want to have settable url? + //auto from_settings = get("profile_archive_url"); + //return from_settings.empty() ? PROFILE_ARCHIVE_URL : from_settings; + return PROFILE_ARCHIVE_URL; +} + bool AppConfig::exists() { return boost::filesystem::exists(config_path()); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 5658f142d5..219a5ff287 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -139,6 +139,8 @@ public: // Get the Slic3r version check url. // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. std::string version_check_url() const; + // Get the Slic3r url to vendor profile archive zip. + std::string profile_archive_url() const; // Returns the original Slic3r version found in the ini file before it was overwritten // by the current version diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f3fd10a403..ed063415f0 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -159,6 +159,7 @@ void PresetBundle::setup_directories() data_dir, data_dir / "vendor", data_dir / "cache", + data_dir / "cache" / "vendor", data_dir / "shapes", #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 38a4f7e361..4d0269a8ec 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1,3483 +1,3489 @@ -// 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 -#include - -#ifdef WIN32 -#include -#include -#include -#endif // WIN32 - -#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" -#include "slic3r/Utils/AppUpdater.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; -const std::string PageMaterials::TEMPLATES = "templates"; - -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); - - const AppConfig* app_config = wxGetApp().app_config; - if (materials->technology == T_FFF && app_config->get("no_templates") == "0") - list_printer->append(_L("(Templates)"), &TEMPLATES); - - //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 text; - if (materials->technology == T_FFF && template_shown) { - text = format_wxstr(_L("%1% visible for (\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - } else { - 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")); - - 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 (material->vendor && material->vendor->templates_profile) - continue; - 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); - - bool templates_available = list_printer->size() > 1 && list_printer->get_data(1) == TEMPLATES; - - // 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 - template_shown = false; - // Refresh type list - list_type->Clear(); - list_type->append(_L("(All)"), &EMPTY); - if (sel_printers_count > 1) { - // If all is selected with other printers - // unselect "all" or all printers depending on last value - // same with "templates" - 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 (materials->technology == T_FFF && templates_available && (sel_printers[0] == 1 || sel_printers[1] == 1) && sel_printers_count > 1) { - if (last_selected_printer == 1) { - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(1); - } - else if (last_selected_printer != 0) { - list_printer->SetSelection(1, false); - sel_printers_count = list_printer->GetSelections(sel_printers); - } - } - } - if (sel_printers_count > 0 && sel_printers[0] != 0 && ((materials->technology == T_FFF && templates_available && sel_printers[0] != 1) || materials->technology != T_FFF || !templates_available)) { - 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, printer_name, 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 if (sel_printers_count > 0 && last_selected_printer == 0) { - //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, 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 if (materials->technology == T_FFF && templates_available && sel_printers_count > 0 && last_selected_printer == 1) { - //clear selection except "TEMPLATES" - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(1); - sel_printers_count = list_printer->GetSelections(sel_printers); - template_shown = true; - materials->filter_presets(nullptr, TEMPLATES, 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, printer_name, 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); - // first 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, printer_name, 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) || template_shown ? "" : " *"), &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; - bool add_TEMPLATES_item = false; - 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 (data == TEMPLATES) {// do not sort item - add_TEMPLATES_item = true; - 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); - if (materials->technology == T_FFF && add_TEMPLATES_item) - list->append(_L("(Templates)"), &TEMPLATES); - 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 || template_shown ? "" : " *"), &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 || template_shown ? "" : " *"), &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); - if (checked && template_shown && !notification_shown) { - notification_shown = true; - wxString message = _L("You have selelected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); - MessageDialog msg(this, message, _L("Notice"), wxYES_NO); - if (msg.ShowModal() == wxID_NO) { - list_profile->Check(i, false); - return; - } - } - 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")); - auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); - - wxBoxSizer* profile_name_sizer = new wxBoxSizer(wxVERTICAL); - profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name }; - profile_name_editor->Enable(false); - - cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { - profile_name_editor->Enable(custom_wanted()); - wizard_p()->on_custom_setup(custom_wanted()); - }); - - append(cb_custom); - append(label); - append(profile_name_sizer); -} - -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(); }); -} - -namespace DownloaderUtils -{ -#ifdef _WIN32 - - wxString get_downloads_path() - { - wxString ret; - PWSTR path = NULL; - HRESULT hr = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path); - if (SUCCEEDED(hr)) { - ret = wxString(path); - } - CoTaskMemFree(path); - return ret; - } - -#elif __APPLE__ - wxString get_downloads_path() - { - // call objective-c implementation - return wxString::FromUTF8(get_downloads_path_mac()); - } -#else - wxString get_downloads_path() - { - wxString command = "xdg-user-dir DOWNLOAD"; - wxArrayString output; - GUI::desktop_execute_get_result(command, output); - if (output.GetCount() > 0) { - return output[0]; - } - return wxString(); - } - -#endif - -Worker::Worker(wxWindow* parent) -: wxBoxSizer(wxHORIZONTAL) -, m_parent(parent) -{ - m_input_path = new wxTextCtrl(m_parent, wxID_ANY); - set_path_name(get_app_config()->get("url_downloader_dest")); - - auto* path_label = new wxStaticText(m_parent, wxID_ANY, _L("Download path") + ":"); - - this->Add(path_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - this->Add(m_input_path, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); - - auto* button_path = new wxButton(m_parent, wxID_ANY, _L("Browse")); - this->Add(button_path, 0, wxEXPAND | wxTOP | wxLEFT, 5); - button_path->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { - boost::filesystem::path chosen_dest(boost::nowide::narrow(m_input_path->GetValue())); - - wxDirDialog dialog(m_parent, L("Choose folder:"), chosen_dest.string() ); - if (dialog.ShowModal() == wxID_OK) - this->m_input_path->SetValue(dialog.GetPath()); - }); - - for (wxSizerItem* item : this->GetChildren()) - if (item->IsWindow()) { - wxWindow* win = item->GetWindow(); - wxGetApp().UpdateDarkUI(win); - } -} - -void Worker::set_path_name(wxString path) -{ - if (path.empty()) - path = boost::nowide::widen(get_app_config()->get("url_downloader_dest")); - - if (path.empty()) { - // What should be default path? Each system has Downloads folder, that could be good one. - // Other would be program location folder - not so good: access rights, apple bin is inside bundle... - // default_path = boost::dll::program_location().parent_path().string(); - path = get_downloads_path(); - } - - m_input_path->SetValue(path); -} - -void Worker::set_path_name(const std::string& name) -{ - if (!m_input_path) - return; - - set_path_name(boost::nowide::widen(name)); -} - -} // DownLoader - -PageDownloader::PageDownloader(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Downloads from URL"), _L("Downloads")) -{ - const AppConfig* app_config = wxGetApp().app_config; - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - append_spacer(VERTICAL_SPACING); - - auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow build-in downloader")); - // TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run. - bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get("downloader_url_registered") == "1" : true); - box_allow_downloads->SetValue(box_allow_value); - append(box_allow_downloads); - - append_text(wxString::Format(_L( - "If enabled, %s registers to start on custom URL on www.printables.com." - " You will be able to use button with %s logo to open models in this %s." - " The model will be downloaded into folder you choose bellow." - ), SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME)); - -#ifdef __linux__ - append_text(wxString::Format(_L( - "On Linux systems the process of registration also creates desktop integration files for this version of application." - ))); -#endif - - box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->downloader->allow(event.IsChecked()); }); - - downloader = new DownloaderUtils::Worker(this); - append(downloader); - downloader->allow(box_allow_value); -} - -bool PageDownloader::on_finish_downloader() const -{ - return downloader->on_finish(); -} - -bool DownloaderUtils::Worker::perform_register() -{ - //boost::filesystem::path chosen_dest/*(path_text_ctrl->GetValue());*/(boost::nowide::narrow(path_text_ctrl->GetValue())); - boost::filesystem::path chosen_dest (GUI::format(path_name())); - boost::system::error_code ec; - if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) { - std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not Exists.") ,chosen_dest.string()); - BOOST_LOG_TRIVIAL(error) << err_msg; - show_error(m_parent, err_msg); - return false; - } - BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string(); - wxGetApp().app_config->set("url_downloader_dest", chosen_dest.string()); -#ifdef _WIN32 - // Registry key creation for "prusaslicer://" URL - - boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location())); - // the path to binary needs to be correctly saved in string with respect to localized characters - wxString wbinary = wxString::FromUTF8(binary_path.string()); - std::string binary_string = (boost::format("%1%") % wbinary).str(); - BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string; - - //std::string key_string = "\"" + binary_string + "\" \"-u\" \"%1\""; - //std::string key_string = "\"" + binary_string + "\" \"%1\""; - std::string key_string = "\"" + binary_string + "\" \"--single-instance\" \"%1\""; - - wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\prusaslicer"); - wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); - if (!key_first.Exists()) { - key_first.Create(false); - } - key_first.SetValue("URL Protocol", ""); - - if (!key_full.Exists()) { - key_full.Create(false); - } - //key_full = "\"C:\\Program Files\\Prusa3D\\PrusaSlicer\\prusa-slicer-console.exe\" \"%1\""; - key_full = key_string; -#elif __APPLE__ - // Apple registers for custom url in info.plist thus it has to be already registered since build. - // The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) -#else - // the performation should be called later during desktop integration - perform_registration_linux = true; -#endif - return true; -} - -void DownloaderUtils::Worker::deregister() -{ -#ifdef _WIN32 - std::string key_string = ""; - wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); - if (!key_full.Exists()) { - return; - } - key_full = key_string; -#elif __APPLE__ - // TODO -#else - BOOST_LOG_TRIVIAL(debug) << "DesktopIntegrationDialog::undo_downloader_registration"; - DesktopIntegrationDialog::undo_downloader_registration(); - perform_registration_linux = false; -#endif -} - -bool DownloaderUtils::Worker::on_finish() { - AppConfig* app_config = wxGetApp().app_config; - bool ac_value = app_config->get("downloader_url_registered") == "1"; - BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked; - if (ac_value && downloader_checked) { - // already registered but we need to do it again - if (!perform_register()) - return false; - app_config->set("downloader_url_registered", "1"); - } else if (!ac_value && downloader_checked) { - // register - if (!perform_register()) - return false; - app_config->set("downloader_url_registered", "1"); - } else if (ac_value && !downloader_checked) { - // deregister, downloads are banned now - deregister(); - app_config->set("downloader_url_registered", "0"); - } /*else if (!ac_value && !downloader_checked) { - // not registered and we dont want to do it - // do not deregister as other instance might be registered - } */ - return true; -} - - -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); } - // Filaments page if only custom printer is selected - const AppConfig* app_config = wxGetApp().app_config; - if (!any_fff_selected && (custom_printer_selected || custom_printer_in_bundle) && (app_config->get("no_templates") == "0")) { - update_materials(T_ANY); - 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 || custom_printer_in_bundle); - - index->add_page(page_update); - index->add_page(page_downloader); - 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); - } - } - - for (const auto& printer : wxGetApp().preset_bundle->printers) { - if (!printer.is_default && !printer.is_system && printer.is_visible) { - custom_printer_in_bundle = true; - break; - } - } - - // 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 || custom_printer_in_bundle || custom_printer_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); - } - } - // template filament bundle has no printers - filament would be never added - if(pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) - { - if (!filaments.containts(&filament)) { - filaments.push(&filament); - if (!filament.alias.empty()) - aliases_fff[filament.alias].insert(filament.name); - } - } - } - } - // count compatible printers - for (const auto& preset : filaments.presets) { - // skip template filaments - if (preset->vendor && preset->vendor->templates_profile) - continue; - - 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; - } - // find all aliases (except templates) - std::vector idx_with_same_alias; - for (size_t i = 0; i < filaments.presets.size(); ++i) { - if (preset->alias == filaments.presets[i]->alias && ((filaments.presets[i]->vendor && !filaments.presets[i]->vendor->templates_profile) || !filaments.presets[i]->vendor)) - idx_with_same_alias.push_back(i); - } - // check compatibility with each printer - size_t counter = 0; - for (const auto& printer : filaments.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) - continue; - bool compatible = false; - // Test other 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; - - if (!page_downloader->on_finish_downloader()) { - index->go_to(page_downloader); - return false; - } - /* 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; - } - - // find if preset.first is part of the templates profile (up is searching if preset.first is part of printer vendor preset) - for (const auto& bp : bundles) { - if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { - const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); - const Preset* template_material = template_materials.find_preset(preset.first, false); - if (template_material && is_compatible_with_printer(PresetWithVendorProfile(*template_material, &bp.second.preset_bundle->vendors.begin()->second), PresetWithVendorProfile(printer, nullptr))) { - has_material = true; - break; - } - } - } - if (has_material) - break; - - } - } - if (! has_material) - printer_models_without_material.insert(printer_model); - } - } - } - } - // template_profile_selected check - template_profile_selected = false; - for (const auto& bp : bundles) { - if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { - for (const auto& preset : appconfig_presets) { - const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); - const Preset* template_material = template_materials.find_preset(preset.first, false); - if (template_material){ - template_profile_selected = true; - break; - } - } - if (template_profile_selected) { - break; - } - } - } - 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() && ((pair.second.vendor_profile && !pair.second.vendor_profile->templates_profile) || !pair.second.vendor_profile) ) { continue; } - - if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile && vendor == enabled_vendors.end()) { - // Templates vendor needs to be installed - install_bundles.emplace_back(pair.first); - 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 - BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->downloader->get_perform_registration_linux(); - if (page_welcome->integrate_desktop() || page_downloader->downloader->get_perform_registration_linux()) - DesktopIntegrationDialog::perform_desktop_integration(page_downloader->downloader->get_perform_registration_linux()); -#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() && page_custom->is_valid_profile_name()) { - // 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_downloader = new PageDownloader(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(); -} - -} -} +// 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 +#include + +#ifdef WIN32 +#include +#include +#include +#endif // WIN32 + +#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" +#include "slic3r/Utils/AppUpdater.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, BundleLocation location, bool ais_prusa_bundle) +{ + this->preset_bundle = std::make_unique(); + this->location = location; + 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) + , location(other.location) + , 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 archive_dir = (boost::filesystem::path(Slic3r::data_dir()) / "cache" / "vendor").make_preferred(); + const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + + // Load Prusa bundle from the datadir/vendor directory or from datadir/cache/vendor (archive) or from resources/profiles. + auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + BundleLocation prusa_bundle_loc = BundleLocation::IN_VENDOR; + if (! boost::filesystem::exists(prusa_bundle_path)) { + prusa_bundle_path = (archive_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + prusa_bundle_loc = BundleLocation::IN_ARCHIVE; + } + if (!boost::filesystem::exists(prusa_bundle_path)) { + prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + prusa_bundle_loc = BundleLocation::IN_RESOURCES; + } + { + Bundle prusa_bundle; + if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_loc, true)) + res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); + } + + // Load the other bundles in the datadir/vendor directory + // and then additionally from datadir/cache/vendor (archive) and resources/profiles. + // Should we concider case where archive has older profiles than resources (shouldnt happen)? + typedef std::pair DirData; + std::vector dir_list { {vendor_dir, BundleLocation::IN_VENDOR}, {archive_dir, BundleLocation::IN_ARCHIVE}, {rsrc_vendor_dir, BundleLocation::IN_RESOURCES} }; + for ( auto dir : dir_list) { + for (const auto &dir_entry : boost::filesystem::directory_iterator(dir.first)) { + 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(), dir.second)) + res.emplace(std::move(id), std::move(bundle)); + } + } + } + + 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; +const std::string PageMaterials::TEMPLATES = "templates"; + +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); + + const AppConfig* app_config = wxGetApp().app_config; + if (materials->technology == T_FFF && app_config->get("no_templates") == "0") + list_printer->append(_L("(Templates)"), &TEMPLATES); + + //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 text; + if (materials->technology == T_FFF && template_shown) { + text = format_wxstr(_L("%1% visible for (\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); + } else { + 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")); + + 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 (material->vendor && material->vendor->templates_profile) + continue; + 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); + + bool templates_available = list_printer->size() > 1 && list_printer->get_data(1) == TEMPLATES; + + // 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 + template_shown = false; + // Refresh type list + list_type->Clear(); + list_type->append(_L("(All)"), &EMPTY); + if (sel_printers_count > 1) { + // If all is selected with other printers + // unselect "all" or all printers depending on last value + // same with "templates" + 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 (materials->technology == T_FFF && templates_available && (sel_printers[0] == 1 || sel_printers[1] == 1) && sel_printers_count > 1) { + if (last_selected_printer == 1) { + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(1); + } + else if (last_selected_printer != 0) { + list_printer->SetSelection(1, false); + sel_printers_count = list_printer->GetSelections(sel_printers); + } + } + } + if (sel_printers_count > 0 && sel_printers[0] != 0 && ((materials->technology == T_FFF && templates_available && sel_printers[0] != 1) || materials->technology != T_FFF || !templates_available)) { + 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, printer_name, 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 if (sel_printers_count > 0 && last_selected_printer == 0) { + //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, 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 if (materials->technology == T_FFF && templates_available && sel_printers_count > 0 && last_selected_printer == 1) { + //clear selection except "TEMPLATES" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(1); + sel_printers_count = list_printer->GetSelections(sel_printers); + template_shown = true; + materials->filter_presets(nullptr, TEMPLATES, 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, printer_name, 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); + // first 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, printer_name, 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) || template_shown ? "" : " *"), &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; + bool add_TEMPLATES_item = false; + 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 (data == TEMPLATES) {// do not sort item + add_TEMPLATES_item = true; + 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); + if (materials->technology == T_FFF && add_TEMPLATES_item) + list->append(_L("(Templates)"), &TEMPLATES); + 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 || template_shown ? "" : " *"), &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 || template_shown ? "" : " *"), &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); + if (checked && template_shown && !notification_shown) { + notification_shown = true; + wxString message = _L("You have selelected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); + MessageDialog msg(this, message, _L("Notice"), wxYES_NO); + if (msg.ShowModal() == wxID_NO) { + list_profile->Check(i, false); + return; + } + } + 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")); + auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); + + wxBoxSizer* profile_name_sizer = new wxBoxSizer(wxVERTICAL); + profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name }; + profile_name_editor->Enable(false); + + cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { + profile_name_editor->Enable(custom_wanted()); + wizard_p()->on_custom_setup(custom_wanted()); + }); + + append(cb_custom); + append(label); + append(profile_name_sizer); +} + +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(); }); +} + +namespace DownloaderUtils +{ +#ifdef _WIN32 + + wxString get_downloads_path() + { + wxString ret; + PWSTR path = NULL; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path); + if (SUCCEEDED(hr)) { + ret = wxString(path); + } + CoTaskMemFree(path); + return ret; + } + +#elif __APPLE__ + wxString get_downloads_path() + { + // call objective-c implementation + return wxString::FromUTF8(get_downloads_path_mac()); + } +#else + wxString get_downloads_path() + { + wxString command = "xdg-user-dir DOWNLOAD"; + wxArrayString output; + GUI::desktop_execute_get_result(command, output); + if (output.GetCount() > 0) { + return output[0]; + } + return wxString(); + } + +#endif + +Worker::Worker(wxWindow* parent) +: wxBoxSizer(wxHORIZONTAL) +, m_parent(parent) +{ + m_input_path = new wxTextCtrl(m_parent, wxID_ANY); + set_path_name(get_app_config()->get("url_downloader_dest")); + + auto* path_label = new wxStaticText(m_parent, wxID_ANY, _L("Download path") + ":"); + + this->Add(path_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + this->Add(m_input_path, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); + + auto* button_path = new wxButton(m_parent, wxID_ANY, _L("Browse")); + this->Add(button_path, 0, wxEXPAND | wxTOP | wxLEFT, 5); + button_path->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { + boost::filesystem::path chosen_dest(boost::nowide::narrow(m_input_path->GetValue())); + + wxDirDialog dialog(m_parent, L("Choose folder:"), chosen_dest.string() ); + if (dialog.ShowModal() == wxID_OK) + this->m_input_path->SetValue(dialog.GetPath()); + }); + + for (wxSizerItem* item : this->GetChildren()) + if (item->IsWindow()) { + wxWindow* win = item->GetWindow(); + wxGetApp().UpdateDarkUI(win); + } +} + +void Worker::set_path_name(wxString path) +{ + if (path.empty()) + path = boost::nowide::widen(get_app_config()->get("url_downloader_dest")); + + if (path.empty()) { + // What should be default path? Each system has Downloads folder, that could be good one. + // Other would be program location folder - not so good: access rights, apple bin is inside bundle... + // default_path = boost::dll::program_location().parent_path().string(); + path = get_downloads_path(); + } + + m_input_path->SetValue(path); +} + +void Worker::set_path_name(const std::string& name) +{ + if (!m_input_path) + return; + + set_path_name(boost::nowide::widen(name)); +} + +} // DownLoader + +PageDownloader::PageDownloader(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Downloads from URL"), _L("Downloads")) +{ + const AppConfig* app_config = wxGetApp().app_config; + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + append_spacer(VERTICAL_SPACING); + + auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow build-in downloader")); + // TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run. + bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get("downloader_url_registered") == "1" : true); + box_allow_downloads->SetValue(box_allow_value); + append(box_allow_downloads); + + append_text(wxString::Format(_L( + "If enabled, %s registers to start on custom URL on www.printables.com." + " You will be able to use button with %s logo to open models in this %s." + " The model will be downloaded into folder you choose bellow." + ), SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME)); + +#ifdef __linux__ + append_text(wxString::Format(_L( + "On Linux systems the process of registration also creates desktop integration files for this version of application." + ))); +#endif + + box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->downloader->allow(event.IsChecked()); }); + + downloader = new DownloaderUtils::Worker(this); + append(downloader); + downloader->allow(box_allow_value); +} + +bool PageDownloader::on_finish_downloader() const +{ + return downloader->on_finish(); +} + +bool DownloaderUtils::Worker::perform_register() +{ + //boost::filesystem::path chosen_dest/*(path_text_ctrl->GetValue());*/(boost::nowide::narrow(path_text_ctrl->GetValue())); + boost::filesystem::path chosen_dest (GUI::format(path_name())); + boost::system::error_code ec; + if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) { + std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not Exists.") ,chosen_dest.string()); + BOOST_LOG_TRIVIAL(error) << err_msg; + show_error(m_parent, err_msg); + return false; + } + BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string(); + wxGetApp().app_config->set("url_downloader_dest", chosen_dest.string()); +#ifdef _WIN32 + // Registry key creation for "prusaslicer://" URL + + boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location())); + // the path to binary needs to be correctly saved in string with respect to localized characters + wxString wbinary = wxString::FromUTF8(binary_path.string()); + std::string binary_string = (boost::format("%1%") % wbinary).str(); + BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string; + + //std::string key_string = "\"" + binary_string + "\" \"-u\" \"%1\""; + //std::string key_string = "\"" + binary_string + "\" \"%1\""; + std::string key_string = "\"" + binary_string + "\" \"--single-instance\" \"%1\""; + + wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\prusaslicer"); + wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); + if (!key_first.Exists()) { + key_first.Create(false); + } + key_first.SetValue("URL Protocol", ""); + + if (!key_full.Exists()) { + key_full.Create(false); + } + //key_full = "\"C:\\Program Files\\Prusa3D\\PrusaSlicer\\prusa-slicer-console.exe\" \"%1\""; + key_full = key_string; +#elif __APPLE__ + // Apple registers for custom url in info.plist thus it has to be already registered since build. + // The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) +#else + // the performation should be called later during desktop integration + perform_registration_linux = true; +#endif + return true; +} + +void DownloaderUtils::Worker::deregister() +{ +#ifdef _WIN32 + std::string key_string = ""; + wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); + if (!key_full.Exists()) { + return; + } + key_full = key_string; +#elif __APPLE__ + // TODO +#else + BOOST_LOG_TRIVIAL(debug) << "DesktopIntegrationDialog::undo_downloader_registration"; + DesktopIntegrationDialog::undo_downloader_registration(); + perform_registration_linux = false; +#endif +} + +bool DownloaderUtils::Worker::on_finish() { + AppConfig* app_config = wxGetApp().app_config; + bool ac_value = app_config->get("downloader_url_registered") == "1"; + BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked; + if (ac_value && downloader_checked) { + // already registered but we need to do it again + if (!perform_register()) + return false; + app_config->set("downloader_url_registered", "1"); + } else if (!ac_value && downloader_checked) { + // register + if (!perform_register()) + return false; + app_config->set("downloader_url_registered", "1"); + } else if (ac_value && !downloader_checked) { + // deregister, downloads are banned now + deregister(); + app_config->set("downloader_url_registered", "0"); + } /*else if (!ac_value && !downloader_checked) { + // not registered and we dont want to do it + // do not deregister as other instance might be registered + } */ + return true; +} + + +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); } + // Filaments page if only custom printer is selected + const AppConfig* app_config = wxGetApp().app_config; + if (!any_fff_selected && (custom_printer_selected || custom_printer_in_bundle) && (app_config->get("no_templates") == "0")) { + update_materials(T_ANY); + 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 || custom_printer_in_bundle); + + index->add_page(page_update); + index->add_page(page_downloader); + 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); + } + } + + for (const auto& printer : wxGetApp().preset_bundle->printers) { + if (!printer.is_default && !printer.is_system && printer.is_visible) { + custom_printer_in_bundle = true; + break; + } + } + + // 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 || custom_printer_in_bundle || custom_printer_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); + } + } + // template filament bundle has no printers - filament would be never added + if(pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) + { + if (!filaments.containts(&filament)) { + filaments.push(&filament); + if (!filament.alias.empty()) + aliases_fff[filament.alias].insert(filament.name); + } + } + } + } + // count compatible printers + for (const auto& preset : filaments.presets) { + // skip template filaments + if (preset->vendor && preset->vendor->templates_profile) + continue; + + 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; + } + // find all aliases (except templates) + std::vector idx_with_same_alias; + for (size_t i = 0; i < filaments.presets.size(); ++i) { + if (preset->alias == filaments.presets[i]->alias && ((filaments.presets[i]->vendor && !filaments.presets[i]->vendor->templates_profile) || !filaments.presets[i]->vendor)) + idx_with_same_alias.push_back(i); + } + // check compatibility with each printer + size_t counter = 0; + for (const auto& printer : filaments.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) + continue; + bool compatible = false; + // Test other 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; + + if (!page_downloader->on_finish_downloader()) { + index->go_to(page_downloader); + return false; + } + /* 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; + } + + // find if preset.first is part of the templates profile (up is searching if preset.first is part of printer vendor preset) + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material && is_compatible_with_printer(PresetWithVendorProfile(*template_material, &bp.second.preset_bundle->vendors.begin()->second), PresetWithVendorProfile(printer, nullptr))) { + has_material = true; + break; + } + } + } + if (has_material) + break; + + } + } + if (! has_material) + printer_models_without_material.insert(printer_model); + } + } + } + } + // template_profile_selected check + template_profile_selected = false; + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + for (const auto& preset : appconfig_presets) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material){ + template_profile_selected = true; + break; + } + } + if (template_profile_selected) { + break; + } + } + } + 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 or cache / vendor if needed: + std::vector install_bundles; + for (const auto &pair : bundles) { + if (pair.second.location == BundleLocation::IN_VENDOR) { 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() && ((pair.second.vendor_profile && !pair.second.vendor_profile->templates_profile) || !pair.second.vendor_profile) ) { continue; } + + if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile && vendor == enabled_vendors.end()) { + // Templates vendor needs to be installed + install_bundles.emplace_back(pair.first); + 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 + BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->downloader->get_perform_registration_linux(); + if (page_welcome->integrate_desktop() || page_downloader->downloader->get_perform_registration_linux()) + DesktopIntegrationDialog::perform_desktop_integration(page_downloader->downloader->get_perform_registration_linux()); +#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 or cache / vendor. + // 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 or cache / vendor"; + } + + 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() && page_custom->is_valid_profile_name()) { + // 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_downloader = new PageDownloader(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 0d63de95d9..0c3fed13f9 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -60,18 +60,25 @@ enum Technology { T_ANY = ~0, }; +enum BundleLocation{ + IN_VENDOR, + IN_ARCHIVE, + IN_RESOURCES +}; + struct Bundle { std::unique_ptr preset_bundle; VendorProfile* vendor_profile{ nullptr }; - bool is_in_resources{ false }; + //bool is_in_resources{ false }; + BundleLocation location; bool is_prusa_bundle{ false }; Bundle() = default; Bundle(Bundle&& other); // Returns false if not loaded. Reason for that is logged as boost::log error. - bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false); + bool load(fs::path source_path, BundleLocation location, bool is_prusa_bundle = false); const std::string& vendor_id() const { return vendor_profile->id; } }; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index cee611bdb4..bb3d91ea56 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -20,6 +20,7 @@ #include "libslic3r/format.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/miniz_extension.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/UpdateDialogs.hpp" @@ -144,6 +145,7 @@ struct PresetUpdater::priv std::string version_check_url; fs::path cache_path; + fs::path cache_vendor_path; fs::path rsrc_path; fs::path vendor_path; @@ -168,6 +170,7 @@ struct PresetUpdater::priv PresetUpdater::priv::priv() : cache_path(fs::path(Slic3r::data_dir()) / "cache") + , cache_vendor_path(cache_path / "vendor") , rsrc_path(fs::path(resources_dir()) / "profiles") , vendor_path(fs::path(Slic3r::data_dir()) / "vendor") , cancel(false) @@ -234,44 +237,82 @@ void PresetUpdater::priv::prune_tmps() const // Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Both are saved in cache. -void PresetUpdater::priv::sync_config(const VendorMap vendors) +void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& profile_archive_url) { BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; if (!enabled_config_update) { return; } - // Donwload vendor preset bundles + // Download profiles archive zip + // dk: Do we want to return here on error? Or skip archive dwnld and unzip and work with previous run state cache / vendor? I think return. + // Any error here also doesnt show any info in UI. Do we want maybe notification? + fs::path archive_path(cache_path / "Archive.zip"); + if (profile_archive_url.empty()) { + BOOST_LOG_TRIVIAL(error) << "Downloading profile archive failed - url has no value."; + return; + } + BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip."; + //check if idx_url is leading to our site + if (!boost::starts_with(profile_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(profile_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + BOOST_LOG_TRIVIAL(error) << "Unsafe url path for vedor profiles archive zip. Download is rejected."; + // TODO: this return must be uncommented when correct address is in use + //return; + } + if (!get_file(profile_archive_url, archive_path)) { + BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed."; + return; + } + if (cancel) { return; } + + // Unzip archive to cache / vendor + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + if (!open_zip_reader(&archive, archive_path.string())) { + BOOST_LOG_TRIVIAL(error) << "Couldn't open zipped bundle."; + return; + } else { + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + // loop the entries + mz_zip_archive_file_stat stat; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + BOOST_LOG_TRIVIAL(error) << "Failed to unzip " << stat.m_filename; + continue; + } + // create file from buffer + fs::path tmp_path(cache_vendor_path / (name + ".tmp")); + fs::path target_path(cache_vendor_path / name); + fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(buffer.c_str(), buffer.size()); + file.close(); + fs::rename(tmp_path, target_path); + } + } + } + close_zip_reader(&archive); + } + + // Update vendor preset bundles if in Vendor // Over all indices from the cache directory: for (auto &index : index_db) { if (cancel) { return; } const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { - BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor(); + BOOST_LOG_TRIVIAL(info) << "No such vendor: " << index.vendor(); continue; } - const VendorProfile &vendor = vendor_it->second; - if (vendor.config_update_url.empty()) { - BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name; - continue; - } - - // Download a fresh index - BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name; - const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); - const std::string idx_path_temp = idx_path + "-update"; - //check if idx_url is leading to our site - if (! boost::starts_with(idx_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - ! boost::starts_with(idx_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) - { - BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url; - continue; - } - if (!get_file(idx_url, idx_path_temp)) { continue; } - if (cancel) { return; } - + const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string(); + // Load the fresh index up { Index new_index; @@ -285,7 +326,8 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) BOOST_LOG_TRIVIAL(warning) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name); continue; } - Slic3r::rename_file(idx_path_temp, idx_path); + copy_file_fix(idx_path_temp, idx_path); + //if we rename path we need to change it in Index object too or create the object again //index = std::move(new_index); try { @@ -314,22 +356,24 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) recommended.to_string()); if (vendor.config_version >= recommended) { continue; } + + const auto path_in_archive = cache_vendor_path / (vendor.id + ".ini"); + const auto path_in_cache = cache_path / (vendor.id + ".ini"); + // Check version + if (!boost::filesystem::exists(path_in_archive)) + continue; + // vp is fully loaded to get all resources + auto vp = VendorProfile::from_ini(path_in_archive, true); + if (vp.config_version != recommended) + continue; + copy_file_fix(path_in_archive, path_in_cache); - // Download a fresh bundle - BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name; - const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string()); - const auto bundle_path = cache_path / (vendor.id + ".ini"); - if (! get_file(bundle_url, bundle_path)) { continue; } - if (cancel) { return; } - - // check the newly downloaded bundle for missing resources - // for that, the ini file must be parsed + // check the fresh bundle for missing resources + // for that, the ini file must be parsed (done above) auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const std::string& url, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path){ if (!boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; - // testing url (to be removed) - //const auto resource_url = format("https://raw.githubusercontent.com/prusa3d/PrusaSlicer/master/resources/profiles/%1%/%2%", vendor, filename); const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); const auto resource_path = (cache_path / (vendor + "/" + filename)); if (!fs::exists(resource_path.parent_path())) @@ -337,7 +381,6 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) self.get_file(resource_url, resource_path); } }; - auto vp = VendorProfile::from_ini(bundle_path, true); for (const auto& model : vp.models) { check_and_get_resource(vp.id, model.bed_texture, vendor.config_update_url, vendor_path, rsrc_path, cache_path); if (cancel) { return; } @@ -426,7 +469,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version bool current_not_supported = false; //if slcr is incompatible but situation is not downgrade, we do forced updated and this bool is information to do it if (ver_current_found && !ver_current->is_current_slic3r_supported()){ - if(ver_current->is_current_slic3r_downgrade()) { + if (ver_current->is_current_slic3r_downgrade()) { // "Reconfigure" situation. BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name); @@ -694,8 +737,9 @@ void PresetUpdater::sync(PresetBundle *preset_bundle) // Unfortunatelly as of C++11, it needs to be copied again // into the closure (but perhaps the compiler can elide this). VendorMap vendors = preset_bundle->vendors; + std::string profile_archive_url =GUI::wxGetApp().app_config->profile_archive_url(); - p->thread = std::thread([this, vendors]() { + p->thread = std::thread([this, vendors, profile_archive_url]() { this->p->prune_tmps(); this->p->sync_config(std::move(vendors)); }); @@ -873,8 +917,26 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool for (const auto &bundle : bundles) { auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini"); + auto path_in_cache_vendor = (p->cache_vendor_path / bundle).replace_extension(".ini"); auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); - updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + // find if in cache vendor is newer version than in resources + auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); + auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); + if (vp_cache.config_version > vp_rsrc.config_version) { + // in case we are installing from cache / vendor. we should also copy index to cache + // This needs to be done now bcs the current one would be missing this version on next start + auto path_idx_cache_vendor(path_in_cache_vendor); + path_idx_cache_vendor.replace_extension(".idx"); + auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); + // DK: do this during perform_updates() too? + if (fs::exists(path_idx_cache_vendor)) + copy_file_fix(path_idx_cache_vendor, path_idx_cache); + else // Should we dialog this? + BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); + updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); + + } else + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); } return p->perform_updates(std::move(updates), snapshot); From 7873c28584828e2c468717bd04dd9da0ecf8fd89 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 14 Apr 2022 12:52:49 +0200 Subject: [PATCH 76/86] after rebase changes --- src/slic3r/Utils/PresetUpdater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index bb3d91ea56..878b819f73 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -160,7 +160,7 @@ struct PresetUpdater::priv void set_download_prefs(AppConfig *app_config); bool get_file(const std::string &url, const fs::path &target_path) const; void prune_tmps() const; - void sync_config(const VendorMap vendors); + void sync_config(const VendorMap vendors, const std::string& profile_archive_url); void check_install_indices() const; Updates get_config_updates(const Semver& old_slic3r_version) const; @@ -741,7 +741,7 @@ void PresetUpdater::sync(PresetBundle *preset_bundle) p->thread = std::thread([this, vendors, profile_archive_url]() { this->p->prune_tmps(); - this->p->sync_config(std::move(vendors)); + this->p->sync_config(std::move(vendors), profile_archive_url); }); } From 41d5c16b7672600f8b625cbac4ee57e9d2e361a3 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 22 Apr 2022 15:04:58 +0200 Subject: [PATCH 77/86] fix of crash on empty config -> add template filament fixed checking if template profile needs to be installed fixed checking path before loading profile header from cache / vendor --- src/slic3r/GUI/ConfigWizard.cpp | 39 +++++++++++++++++------------- src/slic3r/GUI/GUI_App.cpp | 1 + src/slic3r/Utils/PresetUpdater.cpp | 33 +++++++++++++------------ 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 4d0269a8ec..c22917b40e 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2808,21 +2808,25 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo } } } - // template_profile_selected check - template_profile_selected = false; - for (const auto& bp : bundles) { - if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { - for (const auto& preset : appconfig_presets) { - const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); - const Preset* template_material = template_materials.find_preset(preset.first, false); - if (template_material){ - template_profile_selected = true; + // todo: just workaround so template_profile_selected wont get to false after this function is called for SLA + // this will work unltil there are no SLA template filaments + if (technology == ptFFF) { + // template_profile_selected check + template_profile_selected = false; + for (const auto& bp : bundles) { + if (!bp.second.preset_bundle->vendors.empty() && bp.second.preset_bundle->vendors.begin()->second.templates_profile) { + for (const auto& preset : appconfig_presets) { + const PresetCollection& template_materials = bp.second.preset_bundle->materials(technology); + const Preset* template_material = template_materials.find_preset(preset.first, false); + if (template_material){ + template_profile_selected = true; + break; + } + } + if (template_profile_selected) { break; } } - if (template_profile_selected) { - break; - } } } assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); @@ -2988,11 +2992,12 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } const auto vendor = enabled_vendors.find(pair.first); - if (vendor == enabled_vendors.end() && ((pair.second.vendor_profile && !pair.second.vendor_profile->templates_profile) || !pair.second.vendor_profile) ) { continue; } - - if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile && vendor == enabled_vendors.end()) { - // Templates vendor needs to be installed - install_bundles.emplace_back(pair.first); + if (vendor == enabled_vendors.end()) { + // vendor not found + // if templates vendor and needs to be installed, add it + // then continue + if (template_profile_selected && pair.second.vendor_profile && pair.second.vendor_profile->templates_profile) + install_bundles.emplace_back(pair.first); continue; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 5a2562aab3..3c561b91dd 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -808,6 +808,7 @@ void GUI_App::post_init() // Configuration is not compatible and reconfigure was refused by the user. Application is closing. return; CallAfter([this] { + // preset_updater->sync downloads profile updates on background so it must begin after config wizard finished. bool cw_showed = this->config_wizard_startup(); this->preset_updater->sync(preset_bundle); this->app_version_check(false); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 878b819f73..1a720181c6 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -920,22 +920,25 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool auto path_in_cache_vendor = (p->cache_vendor_path / bundle).replace_extension(".ini"); auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); // find if in cache vendor is newer version than in resources - auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); - auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); - if (vp_cache.config_version > vp_rsrc.config_version) { - // in case we are installing from cache / vendor. we should also copy index to cache - // This needs to be done now bcs the current one would be missing this version on next start - auto path_idx_cache_vendor(path_in_cache_vendor); - path_idx_cache_vendor.replace_extension(".idx"); - auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); - // DK: do this during perform_updates() too? - if (fs::exists(path_idx_cache_vendor)) - copy_file_fix(path_idx_cache_vendor, path_idx_cache); - else // Should we dialog this? - BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); - updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); + if (boost::filesystem::exists(path_in_cache_vendor)) { + auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); + auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); + if (vp_cache.config_version > vp_rsrc.config_version) { + // in case we are installing from cache / vendor. we should also copy index to cache + // This needs to be done now bcs the current one would be missing this version on next start + auto path_idx_cache_vendor(path_in_cache_vendor); + path_idx_cache_vendor.replace_extension(".idx"); + auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); + // DK: do this during perform_updates() too? + if (fs::exists(path_idx_cache_vendor)) + copy_file_fix(path_idx_cache_vendor, path_idx_cache); + else // Should we dialog this? + BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); + updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); - } else + } else + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + } else updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); } From e4313399eaa9dedec8b481c1360a3eea3a5ecf2c Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 26 Sep 2022 14:54:26 +0200 Subject: [PATCH 78/86] after rebase changes --- src/slic3r/GUI/PresetComboBoxes.cpp | 6 +++--- src/slic3r/GUI/SavePresetDialog.hpp | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index be34f6c5da..b76e903cdb 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -912,7 +912,7 @@ void PlaterPresetComboBox::update() const AppConfig* app_config = wxGetApp().app_config; if (!template_presets.empty() && app_config->get("no_templates") == "0") { set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { Append(it->first, *it->second); validate_selection(it->first == selected_user_preset); } @@ -1100,7 +1100,7 @@ void TabPresetComboBox::update() if (preset.is_default || preset.is_system) { if (preset.vendor && preset.vendor->templates_profile) { - template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); if (i == idx_selected) selected = get_preset_name(preset); } else { @@ -1125,7 +1125,7 @@ void TabPresetComboBox::update() const AppConfig* app_config = wxGetApp().app_config; if (!template_presets.empty() && app_config->get("no_templates") == "0") { set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { int item_id = Append(it->first, *it->second.first); bool is_enabled = it->second.second; if (!is_enabled) diff --git a/src/slic3r/GUI/SavePresetDialog.hpp b/src/slic3r/GUI/SavePresetDialog.hpp index 0199e27dec..3088d457f6 100644 --- a/src/slic3r/GUI/SavePresetDialog.hpp +++ b/src/slic3r/GUI/SavePresetDialog.hpp @@ -87,7 +87,6 @@ private: public: -<<<<<<< master const wxString& get_info_line_extention() { return m_info_line_extention; } SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = "", bool template_filament = false); From 548205ffd83f99f38670095901fdeda1de43c67c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 14 Apr 2022 14:35:11 +0200 Subject: [PATCH 79/86] Fixes for downloading bundles and resources: - bed_texture/model may be empty. In that case, do not check for the existence of the file. - In case a vendor is new (=not in resources), it would have crashed when installing any printer from such vendor. The problem was that `install_bundles_rsrc` assumed that the INI is in resources. - several const keywords added - small refactoring - removed commented-out code in AppConfig::profile_archive_url(): the url shall not be customizable --- src/libslic3r/AppConfig.cpp | 5 +-- src/libslic3r/AppConfig.hpp | 2 +- src/libslic3r/Preset.cpp | 26 +++++------- src/libslic3r/Preset.hpp | 2 +- src/slic3r/GUI/ConfigWizard.cpp | 2 +- src/slic3r/Utils/PresetUpdater.cpp | 66 +++++++++++++++++++----------- src/slic3r/Utils/PresetUpdater.hpp | 6 +-- 7 files changed, 61 insertions(+), 48 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 44658e852e..7793a2bd48 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -671,11 +671,8 @@ std::string AppConfig::version_check_url() const return from_settings.empty() ? VERSION_CHECK_URL : from_settings; } -std::string AppConfig::profile_archive_url() const +const std::string& AppConfig::profile_archive_url() const { - // Do we want to have settable url? - //auto from_settings = get("profile_archive_url"); - //return from_settings.empty() ? PROFILE_ARCHIVE_URL : from_settings; return PROFILE_ARCHIVE_URL; } diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 219a5ff287..1ecd8cd643 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -140,7 +140,7 @@ public: // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. std::string version_check_url() const; // Get the Slic3r url to vendor profile archive zip. - std::string profile_archive_url() const; + const std::string& profile_archive_url() const; // Returns the original Slic3r version found in the ini file before it was overwritten // by the current version diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 486453bc60..355b44a5dd 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2131,25 +2131,21 @@ namespace PresetUtils { return out; } - bool vendor_profile_has_all_resources(const VendorProfile& vp, bool in_cache) + bool vendor_profile_has_all_resources(const VendorProfile& vp) { + namespace fs = boost::filesystem; + std::string vendor_folder = Slic3r::data_dir() + "/vendor/" + vp.id + "/"; std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/"; std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/"; - for (const VendorProfile::PrinterModel& model : vp.models) - { - if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.bed_texture)) - && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.bed_texture)) - && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.bed_texture)))) - return false; - if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.bed_model)) - && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.bed_model)) - && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.bed_model)))) - return false; - if ( !boost::filesystem::exists(boost::filesystem::path(vendor_folder + model.id + "_thumbnail.png")) - && !boost::filesystem::exists(boost::filesystem::path(rsrc_folder + model.id + "_thumbnail.png")) - && (!in_cache || !boost::filesystem::exists(boost::filesystem::path(cache_folder + model.id + "_thumbnail.png")))) - return false; + for (const VendorProfile::PrinterModel& model : vp.models) { + for (const std::string& res : { model.bed_texture, model.bed_model, model.id + "_thumbnail.png" } ) { + if (! res.empty() + && !fs::exists(fs::path(vendor_folder + res)) + && !fs::exists(fs::path(rsrc_folder + res)) + && (fs::exists(fs::path(cache_folder + res)))) + return false; + } } return true; } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 35d4b5e843..234e9a0dc0 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -620,7 +620,7 @@ namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); std::string system_printer_bed_model(const Preset& preset); std::string system_printer_bed_texture(const Preset& preset); - bool vendor_profile_has_all_resources(const VendorProfile& vp, bool in_cache); + bool vendor_profile_has_all_resources(const VendorProfile& vp); } // namespace PresetUtils diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index c22917b40e..49f5426f7e 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -3051,7 +3051,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (install_bundles.size() > 0) { // Install bundles from resources or cache / vendor. // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) + if (! updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false)) return false; } else { BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources or cache / vendor"; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 1a720181c6..940d92c7f2 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -157,7 +157,7 @@ struct PresetUpdater::priv priv(); - void set_download_prefs(AppConfig *app_config); + void set_download_prefs(const AppConfig *app_config); bool get_file(const std::string &url, const fs::path &target_path) const; void prune_tmps() const; void sync_config(const VendorMap vendors, const std::string& profile_archive_url); @@ -183,7 +183,7 @@ PresetUpdater::priv::priv() } // Pull relevant preferences from AppConfig -void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) +void PresetUpdater::priv::set_download_prefs(const AppConfig *app_config) { enabled_version_check = app_config->get("notify_release") != "none"; version_check_url = app_config->version_check_url(); @@ -370,9 +370,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string // check the fresh bundle for missing resources // for that, the ini file must be parsed (done above) - auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const std::string& url, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path){ - if (!boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) - && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { + auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, + const std::string& url, const fs::path& vendor_path, + const fs::path& rsrc_path, const fs::path& cache_path) + { + if (!fs::exists((vendor_path / (vendor + "/" + filename))) + && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); const auto resource_path = (cache_path / (vendor + "/" + filename)); @@ -382,12 +385,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string } }; for (const auto& model : vp.models) { - check_and_get_resource(vp.id, model.bed_texture, vendor.config_update_url, vendor_path, rsrc_path, cache_path); - if (cancel) { return; } - check_and_get_resource(vp.id, model.bed_model, vendor.config_update_url, vendor_path, rsrc_path, cache_path); - if (cancel) { return; } - check_and_get_resource(vp.id, model.id +"_thumbnail.png", vendor.config_update_url, vendor_path, rsrc_path, cache_path); - if (cancel) { return; } + for (const std::string& res : { model.bed_texture, model.bed_model, model.id +"_thumbnail.png"} ) { + if (! res.empty()) + check_and_get_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + if (cancel) + return; + } } } } @@ -515,7 +518,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version if (new_vp.config_version == recommended->config_version) { // The config bundle from the cache directory matches the recommended version of the index from the cache directory. // This is the newest known recommended config. Use it. - if (PresetUtils::vendor_profile_has_all_resources(new_vp, true)) { + if (PresetUtils::vendor_profile_has_all_resources(new_vp)) { // All resources for the profile in cache dir are existing (either in resources or data_dir/vendor or waiting to be copied to vendor from cache) // This final check is not performed for updates from resources dir below. new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); @@ -523,6 +526,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version bundle_path_idx_to_install = idx.path(); found = true; } else { + // Resource missing - treat as if the INI file was corrupted. throw Slic3r::CriticalException("Some resources are missing."); } @@ -677,11 +681,14 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } - auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path) { - if ( !boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) - && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { + auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, + const fs::path& vendor_path, const fs::path& rsrc_path, + const fs::path& cache_path) + { + if ( !fs::exists((vendor_path / (vendor + "/" + filename))) + && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { - if (!boost::filesystem::exists((cache_path / (vendor + "/" + filename)))) { + if (!fs::exists((cache_path / (vendor + "/" + filename)))) { BOOST_LOG_TRIVIAL(error) << "Resources missing in cache directory: " << vendor << " / " << filename; return; } @@ -728,7 +735,7 @@ PresetUpdater::~PresetUpdater() } } -void PresetUpdater::sync(PresetBundle *preset_bundle) +void PresetUpdater::sync(const PresetBundle *preset_bundle) { p->set_download_prefs(GUI::wxGetApp().app_config); if (!p->enabled_version_check && !p->enabled_config_update) { return; } @@ -909,7 +916,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 return R_NOOP; } -bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool snapshot) const +bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector bundles, bool snapshot) const { Updates updates; @@ -919,11 +926,16 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini"); auto path_in_cache_vendor = (p->cache_vendor_path / bundle).replace_extension(".ini"); auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); + + bool is_in_rsrc = fs::exists(path_in_rsrc); + bool is_in_cache_vendor = fs::exists(path_in_cache_vendor); + // find if in cache vendor is newer version than in resources - if (boost::filesystem::exists(path_in_cache_vendor)) { + if (is_in_cache_vendor) { auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); - if (vp_cache.config_version > vp_rsrc.config_version) { + + if (! is_in_rsrc || vp_cache.config_version > vp_rsrc.config_version) { // in case we are installing from cache / vendor. we should also copy index to cache // This needs to be done now bcs the current one would be missing this version on next start auto path_idx_cache_vendor(path_in_cache_vendor); @@ -936,10 +948,18 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); - } else - updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); - } else + } else { + if (is_in_rsrc) + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + } + } else { + if (! is_in_rsrc) { + // This should not happen. Instead of an assert, make it crash in Release mode too. + BOOST_LOG_TRIVIAL(error) << "Internal error in PresetUpdater! Terminating the application."; + std::terminate(); + } updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + } } return p->perform_updates(std::move(updates), snapshot); diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index 974a7dcfae..cf17bb685e 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -26,7 +26,7 @@ public: ~PresetUpdater(); // If either version check or config updating is enabled, get the appropriate data in the background and cache it. - void sync(PresetBundle *preset_bundle); + void sync(const PresetBundle *preset_bundle); // If version check is enabled, check if chaced online slic3r version is newer, notify if so. void slic3r_update_notify(); @@ -53,8 +53,8 @@ public: // that the config index installed from the Internet is equal to the index contained in the installation package. UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const; - // "Update" a list of bundles from resources (behaves like an online update). - bool install_bundles_rsrc(std::vector bundles, bool snapshot = true) const; + // "Update" a list of bundles from resources or cache/vendor (behaves like an online update). + bool install_bundles_rsrc_or_cache_vendor(std::vector bundles, bool snapshot = true) const; void on_update_notification_confirm(); From 1589d89ca20d4ddf523a75b0d4b821b7e620da9e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 6 Oct 2022 16:28:38 +0200 Subject: [PATCH 80/86] Fixes for template filament profiles: - do not show "Template Filaments" in the list of vendors in wizard - slight refactoring - typos --- src/libslic3r/Preset.cpp | 3 +-- src/slic3r/GUI/ConfigWizard.cpp | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 355b44a5dd..951cc7018d 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1218,8 +1218,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++idx_preset) { if (m_presets[idx_preset].vendor && !m_presets[idx_preset].vendor->templates_profile && m_presets[idx_preset].is_compatible) { std::string preset_alias = m_presets[idx_preset].alias; - for (size_t idx_in_templates = 0; idx_in_templates < indices_of_template_presets.size(); ++idx_in_templates) { - size_t idx_of_template_in_presets = indices_of_template_presets[idx_in_templates]; + for (size_t idx_of_template_in_presets : indices_of_template_presets) { if (m_presets[idx_of_template_in_presets].alias == preset_alias) { // unselect selected template filament if there is non-template alias compatible if (idx_of_template_in_presets == m_idx_selected && (unselect_if_incompatible == PresetSelectCompatibleType::Always || unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible)) { diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 49f5426f7e..2ecdca1ea0 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1197,7 +1197,7 @@ void PageMaterials::select_material(int i) const std::string& alias_key = list_profile->get_data(i); if (checked && template_shown && !notification_shown) { notification_shown = true; - wxString message = _L("You have selelected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); + wxString message = _L("You have selected template filament. Please note that these filaments are available for all printers but are NOT certain to be compatible with your printer. Do you still wish to have this filament selected?\n(This message won't be displayed again.)"); MessageDialog msg(this, message, _L("Notice"), wxYES_NO); if (msg.ShowModal() == wxID_NO) { list_profile->Check(i, false); @@ -1608,6 +1608,8 @@ PageVendors::PageVendors(ConfigWizard *parent) for (const auto &pair : wizard_p()->bundles) { const VendorProfile *vendor = pair.second.vendor_profile; if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + if (vendor->templates_profile) + continue; auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { From fc65d73c2d8c2c76f9e19277895faa66549d4ec7 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 21 Oct 2022 14:28:05 +0200 Subject: [PATCH 81/86] Wizard and PresetUpdater changes updater: - Sync downloads also missing thumbnails. - Copying of downloaded resources (perform_updates) also downloads missing ones (new vendor or installing vendor with added printers ). - This copy&download shows progress dialog now. - Fix of crash when installing new vendor (not in rsrc dir) Wizard: - Cancel updater sync when starting wizard to avoid multiple downloads. - Load thumbnails from cache dir (downloaded by updater sync). Preset: - Profiles now has settable name of thumbnail. If not specified, name + _thubnail.png is used (as it was before). --- src/libslic3r/Preset.cpp | 6 +- src/libslic3r/Preset.hpp | 1 + src/slic3r/GUI/ConfigWizard.cpp | 26 ++-- src/slic3r/GUI/GUI_App.cpp | 2 + src/slic3r/Utils/PresetUpdater.cpp | 198 +++++++++++++++++++++++------ src/slic3r/Utils/PresetUpdater.hpp | 1 + 6 files changed, 188 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 951cc7018d..e9d3b09ffd 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -205,6 +205,10 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem } model.bed_model = section.second.get("bed_model", ""); model.bed_texture = section.second.get("bed_texture", ""); + model.thumbnail = section.second.get("thumbnail", ""); + if (model.thumbnail.empty()) + model.thumbnail = model.id + "_thumbnail.png"; + if (! model.id.empty() && ! model.variants.empty()) res.models.push_back(std::move(model)); } @@ -2138,7 +2142,7 @@ namespace PresetUtils { std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/"; std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/"; for (const VendorProfile::PrinterModel& model : vp.models) { - for (const std::string& res : { model.bed_texture, model.bed_model, model.id + "_thumbnail.png" } ) { + for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail } ) { if (! res.empty() && !fs::exists(fs::path(vendor_folder + res)) && !fs::exists(fs::path(rsrc_folder + res)) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 234e9a0dc0..9a16a16a99 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -53,6 +53,7 @@ public: // Vendor & Printer Model specific print bed model & texture. std::string bed_model; std::string bed_texture; + std::string thumbnail; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 2ecdca1ea0..2de72ad024 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -239,15 +239,23 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt } 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); + + bool found = false; + for (const std::string& res : { Slic3r::data_dir() + "/vendor/" + vendor.id + "/", Slic3r::resources_dir() + "/profiles/" + vendor.id + "/", Slic3r::data_dir() + "/cache/" + vendor.id + "/" } ) { + if (load_bitmap(GUI::from_u8(res + "/" + model.thumbnail), bitmap, bitmap_width)) { + found = true; + break; } } + + if (!found) { + BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") + % model.thumbnail + % 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); @@ -3053,7 +3061,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (install_bundles.size() > 0) { // Install bundles from resources or cache / vendor. // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false)) + + bool install_result = updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false); + if (!install_result) return false; } else { BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources or cache / vendor"; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3c561b91dd..6975006a62 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2999,6 +2999,8 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); if (reason == ConfigWizard::RR_USER) { + // Cancel sync before starting wizard to prevent two downloads at same time + preset_updater->cancel_sync(); if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) return false; } diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 940d92c7f2..756fcb43ed 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -264,9 +264,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed."; return; } - if (cancel) { return; } + if (cancel) { + return; + } // Unzip archive to cache / vendor + std::vector vendors_only_in_archive; mz_zip_archive archive; mz_zip_zero_struct(&archive); if (!open_zip_reader(&archive, archive_path.string())) { @@ -293,22 +296,64 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string file.write(buffer.c_str(), buffer.size()); file.close(); fs::rename(tmp_path, target_path); + + if (name.substr(name.size() - 3) == "ini") + vendors_only_in_archive.push_back(name); } } } close_zip_reader(&archive); } + auto get_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, + const std::string& url, const fs::path& vendor_path, + const fs::path& rsrc_path, const fs::path& cache_path) + { + if (filename.empty() || vendor.empty()) + return; + + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); + + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + return; + } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + return; + } + + BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url + + if (!fs::exists(file_in_cache.parent_path())) + fs::create_directory(file_in_cache.parent_path()); + + self.get_file(resource_url, file_in_cache); + return; + }; + // Update vendor preset bundles if in Vendor // Over all indices from the cache directory: for (auto &index : index_db) { - if (cancel) { return; } + if (cancel) { + return; + } const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { - BOOST_LOG_TRIVIAL(info) << "No such vendor: " << index.vendor(); + // Not installed vendor yet we need to check missing thumbnails (of new printers) + BOOST_LOG_TRIVIAL(debug) << "No such vendor: " << index.vendor(); continue; } + const VendorProfile &vendor = vendor_it->second; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string(); @@ -367,32 +412,59 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string if (vp.config_version != recommended) continue; copy_file_fix(path_in_archive, path_in_cache); + // vendors that are checked here, doesnt need to be checked again later + const auto archive_it = std::find(vendors_only_in_archive.begin(), vendors_only_in_archive.end(), index.vendor() + ".ini"); + if (archive_it != vendors_only_in_archive.end()) { + vendors_only_in_archive.erase(archive_it); + } // check the fresh bundle for missing resources // for that, the ini file must be parsed (done above) - auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, - const std::string& url, const fs::path& vendor_path, - const fs::path& rsrc_path, const fs::path& cache_path) - { - if (!fs::exists((vendor_path / (vendor + "/" + filename))) - && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { - BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; - const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); - const auto resource_path = (cache_path / (vendor + "/" + filename)); - if (!fs::exists(resource_path.parent_path())) - fs::create_directory(resource_path.parent_path()); - self.get_file(resource_url, resource_path); - } - }; for (const auto& model : vp.models) { - for (const std::string& res : { model.bed_texture, model.bed_model, model.id +"_thumbnail.png"} ) { - if (! res.empty()) - check_and_get_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail/*id +"_thumbnail.png"*/} ) { + if (! res.empty()) { + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_missing_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << res << " for " << vp.id << " " << model.id << ": " << e.what(); + } + + } if (cancel) return; } } } + // Download missing thumbnails for not-installed vendors. + for (const std::string& vendor : vendors_only_in_archive) + { + BOOST_LOG_TRIVIAL(error) << vendor; + const auto path_in_archive = cache_vendor_path / vendor; + assert(boost::filesystem::exists(path_in_archive)); + auto vp = VendorProfile::from_ini(path_in_archive, true); + for (const auto& model : vp.models) { + if (!model.thumbnail.empty()) { + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_missing_resource(vp.id, model.thumbnail, vp.config_update_url, vendor_path, rsrc_path, cache_path); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + + } + } } // Install indicies from resources. Only installs those that are either missing or older than in resources. @@ -643,6 +715,9 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size()); + wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles")); + progress_dialog.Pulse(); + for (const auto &update : updates.updates) { BOOST_LOG_TRIVIAL(info) << '\t' << update; @@ -681,32 +756,71 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } - auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, + auto get_and_copy_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const fs::path& vendor_path, const fs::path& rsrc_path, - const fs::path& cache_path) + const fs::path& cache_path, const std::string& url) { - if ( !fs::exists((vendor_path / (vendor + "/" + filename))) - && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { + if (filename.empty() || vendor.empty()) + return; - if (!fs::exists((cache_path / (vendor + "/" + filename)))) { - BOOST_LOG_TRIVIAL(error) << "Resources missing in cache directory: " << vendor << " / " << filename; - return; - } - if (!fs::exists((vendor_path / vendor))) - fs::create_directory((vendor_path / vendor)); + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); - copy_file_fix((cache_path / (vendor + "/" + filename)), (vendor_path / (vendor + "/" + filename))); + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + return; } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + return; + } + if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir. + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url + + if (!fs::exists(file_in_vendor.parent_path())) + fs::create_directory(file_in_vendor.parent_path()); + + self.get_file(resource_url, file_in_vendor); + return; + } + + if (!fs::exists(file_in_vendor.parent_path())) // create vendor_name dir in vendor + fs::create_directory(file_in_vendor.parent_path()); + + BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor; + copy_file_fix(file_in_cache, file_in_vendor); }; + // check if any resorces of installed bundle are missing. If so, new ones should be already downloaded at cache/vendor_id/ auto vp = VendorProfile::from_ini(update.target, true); + progress_dialog.Update(1, GUI::format_wxstr(_L("Downloading resources for %1%."),vp.id)); + progress_dialog.Pulse(); for (const auto& model : vp.models) { - copy_missing_resource(vp.id, model.bed_texture, vendor_path, rsrc_path, cache_path); - copy_missing_resource(vp.id, model.bed_model, vendor_path, rsrc_path, cache_path); - copy_missing_resource(vp.id, model.id + "_thumbnail.png", vendor_path, rsrc_path, cache_path); + for (const std::string& resource : { model.bed_texture, model.bed_model, model.thumbnail }) { + if (resource.empty()) + continue; + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_and_copy_missing_resource(vp.id, resource, vendor_path, rsrc_path, cache_path, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to prepare " << resource << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } } } + + progress_dialog.Destroy(); } return true; @@ -752,6 +866,16 @@ void PresetUpdater::sync(const PresetBundle *preset_bundle) }); } +void PresetUpdater::cancel_sync() +{ + if (p && p->thread.joinable()) { + // This will stop transfers being done by the thread, if any. + // Cancelling takes some time, but should complete soon enough. + p->cancel = true; + p->thread.join(); + } +} + void PresetUpdater::slic3r_update_notify() { if (! p->enabled_version_check) @@ -932,10 +1056,10 @@ bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector vp_rsrc.config_version) { + if (!is_in_rsrc || version_cache > version_rsrc) { // in case we are installing from cache / vendor. we should also copy index to cache // This needs to be done now bcs the current one would be missing this version on next start auto path_idx_cache_vendor(path_in_cache_vendor); diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index cf17bb685e..5bcba615bc 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -27,6 +27,7 @@ public: // If either version check or config updating is enabled, get the appropriate data in the background and cache it. void sync(const PresetBundle *preset_bundle); + void cancel_sync(); // If version check is enabled, check if chaced online slic3r version is newer, notify if so. void slic3r_update_notify(); From af0e312542dd7ef98ea7957611f7fe9abe1a1fb4 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 18 Jan 2023 10:50:59 +0100 Subject: [PATCH 82/86] Profile updates and installation: - Refactoring and functionality change of PresetUpdater::sync_config. Zip archive now contains only index files. From index file it is decided wheter .ini file should be downloaded and where (cache for update of installed, cache/vendor for unistalled). New vendors are downloaded from set address. Fron .ini file it is decided wheter thumbnail should be downloaded. All resources for already installed vendors are checked and downloaded. - TemplateFilaments renamed to Templates (Warning: This might create duplicities if both files are present!). - Various checks added to prevent crashes when dealing with broken presets, wrong files etc. - Delayed error message when loading present finds duplicities - wait with dialog until Splash screen is gone. - Minor changes in Config wizard when searching & loading printer thumbnails. --- resources/profiles/TemplateFilaments.idx | 2 - resources/profiles/TemplateFilaments.ini | 1410 ----- resources/profiles/Templates.idx | 2 + resources/profiles/Templates.ini | 2144 +++++++ src/libslic3r/AppConfig.cpp | 27 +- src/libslic3r/AppConfig.hpp | 7 +- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PresetBundle.cpp | 15 +- src/slic3r/GUI/ConfigWizard.cpp | 40 +- src/slic3r/GUI/ConfigWizard_private.hpp | 4 +- src/slic3r/GUI/GUI_App.cpp | 6782 +++++++++++----------- src/slic3r/GUI/PresetComboBoxes.cpp | 46 +- src/slic3r/Utils/PresetUpdater.cpp | 613 +- src/slic3r/Utils/PresetUpdater.hpp | 2 + 14 files changed, 6085 insertions(+), 5011 deletions(-) delete mode 100644 resources/profiles/TemplateFilaments.idx delete mode 100644 resources/profiles/TemplateFilaments.ini create mode 100644 resources/profiles/Templates.idx create mode 100644 resources/profiles/Templates.ini diff --git a/resources/profiles/TemplateFilaments.idx b/resources/profiles/TemplateFilaments.idx deleted file mode 100644 index 94223722f8..0000000000 --- a/resources/profiles/TemplateFilaments.idx +++ /dev/null @@ -1,2 +0,0 @@ -min_slic3r_version = 2.4.0-rc -0.0.1 Initial diff --git a/resources/profiles/TemplateFilaments.ini b/resources/profiles/TemplateFilaments.ini deleted file mode 100644 index 000e25f34c..0000000000 --- a/resources/profiles/TemplateFilaments.ini +++ /dev/null @@ -1,1410 +0,0 @@ -# Print profiles for the Prusa Research printers. - -[vendor] -# Vendor name will be shown by the Config Wizard. -name = Templates Filaments -# Configuration version of this file. Config file will only be installed, if the config_version differs. -# This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.1 -# Where to get the updates from? -#config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ -# changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% -templates_profile = 1 - -## XXX--- Generic filament profiles ---XXX - -[filament:*common*] -cooling = 1 -compatible_printers = -compatible_printers_condition = -end_filament_gcode = "; Filament-specific end gcode" -extrusion_multiplier = 1 -filament_cost = 0 -filament_density = 0 -filament_diameter = 1.75 -filament_notes = "" -filament_settings_id = "" -filament_soluble = 0 -min_print_speed = 15 -slowdown_below_layer_time = 15 -start_filament_gcode = "; Filament gcode" - -[filament:*PLA*] -inherits = *common* -bed_temperature = 60 -bridge_fan_speed = 100 -disable_fan_first_layers = 1 -fan_always_on = 1 -fan_below_layer_time = 100 -filament_colour = #FF8000 -filament_type = PLA -first_layer_bed_temperature = 60 -first_layer_temperature = 215 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 210 - -[filament:*PET*] -inherits = *common* -bed_temperature = 90 -bridge_fan_speed = 50 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -filament_colour = #FF8000 -filament_type = PETG -first_layer_bed_temperature = 85 -first_layer_temperature = 230 -max_fan_speed = 50 -min_fan_speed = 30 -temperature = 240 - -[filament:*ABS*] -inherits = *common* -bed_temperature = 100 -bridge_fan_speed = 25 -cooling = 0 -fan_always_on = 0 -fan_below_layer_time = 20 -filament_colour = #FFF2EC -filament_type = ABS -first_layer_bed_temperature = 100 -first_layer_temperature = 255 -max_fan_speed = 30 -min_fan_speed = 20 -temperature = 255 - -[filament:*ABSC*] -inherits = *common* -bed_temperature = 100 -bridge_fan_speed = 25 -cooling = 1 -disable_fan_first_layers = 4 -fan_always_on = 0 -fan_below_layer_time = 30 -slowdown_below_layer_time = 20 -filament_colour = #FFF2EC -filament_type = ABS -first_layer_bed_temperature = 100 -first_layer_temperature = 255 -max_fan_speed = 15 -min_fan_speed = 15 -min_print_speed = 15 -temperature = 255 - -[filament:*FLEX*] -inherits = *common* -bed_temperature = 50 -bridge_fan_speed = 80 -cooling = 0 -disable_fan_first_layers = 3 -extrusion_multiplier = 1.15 -fan_always_on = 0 -fan_below_layer_time = 100 -filament_colour = #008000 -filament_max_volumetric_speed = 1.5 -filament_type = FLEX -first_layer_bed_temperature = 50 -first_layer_temperature = 240 -max_fan_speed = 90 -min_fan_speed = 70 -temperature = 240 - -[filament:ColorFabb bronzeFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.2 -filament_density = 3.9 -filament_spool_weight = 236 -filament_colour = #804040 - -[filament:ColorFabb steelFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.2 -filament_density = 3.13 -filament_spool_weight = 236 -filament_colour = #808080 - -[filament:ColorFabb copperFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.2 -filament_density = 3.9 -filament_spool_weight = 236 -filament_colour = #82603E - -[filament:ColorFabb HT] -inherits = *PET* -filament_vendor = ColorFabb -bed_temperature = 100 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_density = 1.18 -filament_spool_weight = 236 -first_layer_bed_temperature = 100 -first_layer_temperature = 270 -max_fan_speed = 20 -min_fan_speed = 10 -temperature = 270 - -[filament:ColorFabb PLA-PHA] -inherits = *PLA* -filament_vendor = ColorFabb -filament_density = 1.24 -filament_spool_weight = 236 - -[filament:ColorFabb woodFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.1 -filament_density = 1.15 -filament_spool_weight = 236 -filament_colour = #dfc287 -filament_max_volumetric_speed = 9 -first_layer_temperature = 200 -temperature = 200 - -[filament:ColorFabb corkFill] -inherits = *PLA* -filament_vendor = ColorFabb -extrusion_multiplier = 1.1 -filament_density = 1.18 -filament_spool_weight = 236 -filament_colour = #634d33 -first_layer_temperature = 220 -temperature = 220 - -[filament:ColorFabb XT] -inherits = *PET* -filament_vendor = ColorFabb -filament_density = 1.27 -filament_spool_weight = 236 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 270 - -[filament:ColorFabb XT-CF20] -inherits = *PET* -filament_vendor = ColorFabb -extrusion_multiplier = 1.05 -filament_density = 1.35 -filament_spool_weight = 236 -filament_colour = #804040 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 260 - -[filament:ColorFabb nGen] -inherits = *PET* -filament_vendor = ColorFabb -filament_density = 1.2 -filament_spool_weight = 236 -bridge_fan_speed = 40 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_type = NGEN -first_layer_temperature = 240 -max_fan_speed = 35 -min_fan_speed = 20 - -[filament:ColorFabb nGen flex] -inherits = *FLEX* -filament_vendor = ColorFabb -filament_density = 1 -filament_spool_weight = 236 -bed_temperature = 85 -bridge_fan_speed = 40 -cooling = 1 -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -fan_below_layer_time = 10 -first_layer_bed_temperature = 85 -first_layer_temperature = 260 -max_fan_speed = 35 -min_fan_speed = 20 -temperature = 260 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Kimya PETG Carbon] -inherits = *PET* -filament_vendor = Kimya -extrusion_multiplier = 1.05 -filament_density = 1.317 -filament_colour = #804040 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 240 -filament_retract_length = nil - -[filament:Kimya ABS Carbon] -inherits = *ABSC* -filament_vendor = Kimya -filament_density = 1.032 -filament_colour = #804040 -first_layer_temperature = 260 -temperature = 260 - -[filament:Kimya ABS Kevlar] -inherits = Kimya ABS Carbon -filament_vendor = Kimya -filament_density = 1.037 - -[filament:E3D Edge] -inherits = *PET* -filament_vendor = E3D -filament_density = 1.26 -filament_type = EDGE - -[filament:E3D PC-ABS] -inherits = *ABS* -filament_vendor = E3D -filament_type = PC -filament_density = 1.05 -first_layer_temperature = 270 -temperature = 270 - -[filament:Fillamentum PLA] -inherits = *PLA* -filament_vendor = Fillamentum -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:Fillamentum ABS] -inherits = *ABSC* -filament_vendor = Fillamentum -filament_density = 1.04 -filament_spool_weight = 230 -first_layer_temperature = 240 -temperature = 240 - -[filament:Fillamentum ASA] -inherits = *ABS* -filament_vendor = Fillamentum -filament_density = 1.07 -filament_spool_weight = 230 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -min_print_speed = 15 -slowdown_below_layer_time = 15 -first_layer_temperature = 260 -temperature = 260 -filament_type = ASA - -[filament:Prusament ASA] -inherits = *ABS* -filament_vendor = Prusa Polymers -filament_density = 1.07 -filament_spool_weight = 201 -fan_always_on = 1 -first_layer_temperature = 260 -first_layer_bed_temperature = 105 -temperature = 260 -bed_temperature = 110 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 15 -disable_fan_first_layers = 4 -filament_type = ASA -filament_colour = #FFF2EC - -[filament:Prusament PC Blend] -inherits = *ABS* -filament_vendor = Prusa Polymers -filament_density = 1.22 -filament_spool_weight = 201 -fan_always_on = 0 -first_layer_temperature = 275 -first_layer_bed_temperature = 110 -temperature = 275 -bed_temperature = 115 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 20 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -filament_type = PC -filament_colour = #DEE0E6 - -[filament:Prusament PC Blend Carbon Fiber] -inherits = Prusament PC Blend -filament_density = 1.16 -extrusion_multiplier = 1.04 -first_layer_temperature = 285 -temperature = 285 -disable_fan_first_layers = 4 -fan_below_layer_time = 10 -filament_colour = #BBBBBB - -[filament:Fillamentum CPE] -inherits = *PET* -filament_vendor = Fillamentum -filament_density = 1.25 -filament_spool_weight = 230 -filament_type = CPE -first_layer_bed_temperature = 90 -first_layer_temperature = 275 -min_fan_speed = 30 -max_fan_speed = 50 -disable_fan_first_layers = 3 -full_fan_speed_layer = 5 -temperature = 275 - -[filament:Fillamentum Timberfill] -inherits = *PLA* -filament_vendor = Fillamentum -extrusion_multiplier = 1.05 -filament_density = 1.15 -filament_spool_weight = 230 -filament_colour = #804040 -filament_max_volumetric_speed = 10 -first_layer_temperature = 190 -temperature = 190 - -[filament:Smartfil Wood] -inherits = *PLA* -filament_vendor = Smart Materials 3D -extrusion_multiplier = 1.05 -filament_density = 1.58 -filament_colour = #804040 -first_layer_temperature = 220 -temperature = 220 - -[filament:Generic ABS] -inherits = *ABSC* -filament_vendor = Generic -filament_density = 1.04 - -[filament:Esun ABS] -inherits = *ABSC* -filament_vendor = Esun -filament_density = 1.01 -filament_spool_weight = 265 - -[filament:Hatchbox ABS] -inherits = *ABSC* -filament_vendor = Hatchbox -filament_density = 1.04 -filament_spool_weight = 245 - -[filament:Filament PM ABS] -inherits = *ABSC* -filament_vendor = Filament PM -filament_density = 1.08 -filament_spool_weight = 230 - -[filament:Verbatim ABS] -inherits = *ABSC* -filament_vendor = Verbatim -filament_density = 1.05 -filament_spool_weight = 235 - -[filament:Generic PETG] -inherits = *PET* -filament_vendor = Generic -filament_density = 1.27 - -[filament:Extrudr DuraPro ASA] -inherits = Fillamentum ASA -filament_vendor = Extrudr -bed_temperature = 90 -filament_density = 1.05 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=120" -first_layer_bed_temperature = 90 -first_layer_temperature = 220 -temperature = 220 -filament_spool_weight = 230 - -[filament:Extrudr PETG] -inherits = *PET* -filament_vendor = Extrudr -bed_temperature = 70 -filament_density = 1.29 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=94" -first_layer_bed_temperature = 70 -first_layer_temperature = 220 -temperature = 220 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 -full_fan_speed_layer = 0 - -[filament:Extrudr XPETG CF] -inherits = Extrudr PETG -filament_density = 1.41 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=198" -first_layer_temperature = 235 -temperature = 235 -compatible_printers_condition = nozzle_diameter[0]>=0.4 -filament_spool_weight = 230 - -[filament:Extrudr XPETG Matt] -inherits = Extrudr PETG -filament_density = 1.41 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=199" -first_layer_temperature = 230 -temperature = 230 - -[filament:Extrudr BioFusion] -inherits = *PLA* -filament_vendor = Extrudr -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_density = 1.25 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=121" -first_layer_temperature = 220 -temperature = 220 -max_fan_speed = 45 -min_fan_speed = 25 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr Flax] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.45 -filament_notes = "High Performance Filament for decorative parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=131" -first_layer_temperature = 190 -temperature = 190 -max_fan_speed = 80 -min_fan_speed = 30 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 - -[filament:Extrudr GreenTEC] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.3 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=106" -first_layer_temperature = 208 -temperature = 208 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr GreenTEC Pro] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.35 -filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=134" -temperature = 215 -max_fan_speed = 80 -min_fan_speed = 30 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 - -[filament:Extrudr GreenTEC Pro Carbon] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.2 -filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher stregnth and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=138" -first_layer_temperature = 225 -max_fan_speed = 80 -min_fan_speed = 30 -temperature = 225 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 20 -filament_spool_weight = 230 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Extrudr PLA NX1] -inherits = *PLA* -filament_vendor = Extrudr -filament_density = 1.24 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=97" -temperature = 205 -bed_temperature = 60 -first_layer_temperature = 205 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 0 -max_fan_speed = 90 -min_fan_speed = 30 -slowdown_below_layer_time = 20 -filament_spool_weight = 262 - -[filament:Extrudr PLA NX2] -inherits = Extrudr PLA NX1 -filament_density = 1.3 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=128" - -[filament:Extrudr Flex Hard] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.2 -filament_density = 1.2 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:Extrudr Flex Medium] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.2 -filament_density = 1.19 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:Extrudr Flex SemiSoft] -inherits = *FLEX* -filament_vendor = Extrudr -disable_fan_first_layers = 1 -extrusion_multiplier = 1.2 -filament_density = 1.18 -filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" -filament_spool_weight = 230 -slowdown_below_layer_time = 20 - -[filament:addnorth Adamant S1] -inherits = *FLEX* -filament_vendor = addnorth -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -filament_density = 1.22 -temperature = 250 -bed_temperature = 50 -first_layer_temperature = 245 -first_layer_bed_temperature = 50 -slowdown_below_layer_time = 20 -min_print_speed = 20 -fan_below_layer_time = 15 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 40 -max_fan_speed = 70 -bridge_fan_speed = 60 -filament_spool_weight = 0 - -[filament:addnorth Adura X] -inherits = *PET* -filament_vendor = addnorth -filament_density = 1.27 -filament_type = NYLON -extrusion_multiplier = 1 -bed_temperature = 60 -first_layer_bed_temperature = 60 -first_layer_temperature = 265 -temperature = 270 -fan_always_on = 0 -min_fan_speed = 20 -max_fan_speed = 40 -bridge_fan_speed = 70 -slowdown_below_layer_time = 10 -min_print_speed = 20 -fan_below_layer_time = 10 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:addnorth E-PLA] -inherits = *PLA* -filament_vendor = addnorth -filament_density = 1.24 -extrusion_multiplier = 1 -temperature = 215 -bed_temperature = 60 -first_layer_temperature = 215 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 3 -slowdown_below_layer_time = 15 -filament_spool_weight = 0 - -[filament:addnorth ESD-PETG] -inherits = *PET* -filament_vendor = addnorth -filament_density = 1.27 -extrusion_multiplier = 1 -bed_temperature = 80 -first_layer_bed_temperature = 85 -first_layer_temperature = 245 -temperature = 265 -fan_always_on = 1 -min_fan_speed = 15 -max_fan_speed = 30 -bridge_fan_speed = 35 -slowdown_below_layer_time = 10 -min_print_speed = 15 -fan_below_layer_time = 8 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 - -[filament:addnorth OBC Polyethylene] -inherits = *FLEX* -filament_vendor = addnorth -disable_fan_first_layers = 3 -extrusion_multiplier = 1 -filament_density = 1.22 -temperature = 200 -bed_temperature = 100 -first_layer_temperature = 195 -first_layer_bed_temperature = 100 -slowdown_below_layer_time = 5 -fan_below_layer_time = 15 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 30 -bridge_fan_speed = 40 -min_print_speed = 20 -filament_spool_weight = 0 -filament_notes = "Use Magigoo PP bed adhesive or PP packing tape (on a cold printbed)." - -[filament:addnorth PETG] -inherits = *PET* -filament_vendor = addnorth -filament_density = 1.27 -bed_temperature = 80 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 250 -fan_always_on = 1 -min_fan_speed = 15 -max_fan_speed = 40 -bridge_fan_speed = 50 -slowdown_below_layer_time = 10 -min_print_speed = 15 -fan_below_layer_time = 15 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 - -[filament:addnorth Rigid X] -inherits = *PET* -filament_vendor = addnorth -filament_density = 1.27 -extrusion_multiplier = 1 -bed_temperature = 85 -first_layer_bed_temperature = 90 -first_layer_temperature = 250 -temperature = 260 -fan_always_on = 1 -min_fan_speed = 20 -max_fan_speed = 60 -bridge_fan_speed = 70 -slowdown_below_layer_time = 10 -fan_below_layer_time = 20 -min_print_speed = 20 -disable_fan_first_layers = 3 -full_fan_speed_layer = 0 -filament_spool_weight = 0 -filament_notes = "Please use a nozzle that is resistant to abrasive filaments, like hardened steel." -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:addnorth Textura] -inherits = *PLA* -filament_vendor = addnorth -filament_density = 1.24 -extrusion_multiplier = 0.95 -temperature = 215 -bed_temperature = 65 -first_layer_temperature = 215 -first_layer_bed_temperature = 65 -min_fan_speed = 20 -max_fan_speed = 40 -bridge_fan_speed = 60 -full_fan_speed_layer = 0 -slowdown_below_layer_time = 15 -min_print_speed = 20 -filament_spool_weight = 0 -filament_retract_length = 1 - -[filament:Filamentworld ABS] -inherits = *ABSC* -filament_vendor = Filamentworld -filament_density = 1.04 -temperature = 230 -bed_temperature = 95 -first_layer_temperature = 240 -first_layer_bed_temperature = 100 -max_fan_speed = 20 -min_fan_speed = 10 -min_print_speed = 20 -disable_fan_first_layers = 3 -fan_below_layer_time = 60 -slowdown_below_layer_time = 15 -bridge_fan_speed = 20 - -[filament:Filamentworld PETG] -inherits = *PET* -filament_vendor = Filamentworld -filament_density = 1.27 -bed_temperature = 70 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 235 -fan_always_on = 1 -min_fan_speed = 25 -max_fan_speed = 55 -bridge_fan_speed = 55 -slowdown_below_layer_time = 20 -min_print_speed = 20 -fan_below_layer_time = 35 -disable_fan_first_layers = 2 -full_fan_speed_layer = 0 -filament_spool_weight = 0 - -[filament:Filamentworld PLA] -inherits = *PLA* -filament_vendor = Filamentworld -filament_density = 1.24 -temperature = 205 -bed_temperature = 55 -first_layer_temperature = 215 -first_layer_bed_temperature = 60 -full_fan_speed_layer = 3 -slowdown_below_layer_time = 10 -filament_spool_weight = 0 -min_print_speed = 20 - -[filament:Filament PM PETG] -inherits = *PET* -filament_vendor = Filament PM -filament_density = 1.27 -filament_spool_weight = 230 - -[filament:Generic PLA] -inherits = *PLA* -filament_vendor = Generic -filament_density = 1.24 - -[filament:Devil Design PLA] -inherits = *PLA* -filament_vendor = Devil Design -filament_density = 1.24 -filament_spool_weight = 250 - -[filament:Devil Design PETG] -inherits = *PET* -filament_vendor = Devil Design -filament_density = 1.23 -filament_spool_weight = 250 -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 - -[filament:Spectrum PLA] -inherits = *PLA* -filament_vendor = Spectrum -filament_density = 1.24 - -[filament:Generic FLEX] -inherits = *FLEX* -filament_vendor = Generic -filament_density = 1.22 -filament_retract_length = 0 -filament_retract_speed = nil -filament_retract_lift = nil - -[filament:Fillamentum Flexfill 92A] -inherits = *FLEX* -filament_vendor = Fillamentum -filament_density = 1.20 -filament_spool_weight = 230 -filament_deretract_speed = 20 -fan_always_on = 1 -cooling = 0 -max_fan_speed = 60 -min_fan_speed = 60 -disable_fan_first_layers = 4 -full_fan_speed_layer = 6 - -[filament:AmazonBasics TPU] -inherits = *FLEX* -filament_vendor = AmazonBasics -fan_always_on = 1 -extrusion_multiplier = 1.1 -first_layer_temperature = 235 -first_layer_bed_temperature = 50 -temperature = 235 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 80 -min_fan_speed = 80 -filament_density = 1.21 -disable_fan_first_layers = 4 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:SainSmart TPU] -inherits = *FLEX* -filament_vendor = SainSmart -fan_always_on = 1 -filament_max_volumetric_speed = 2.5 -extrusion_multiplier = 1.1 -first_layer_temperature = 230 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 80 -min_fan_speed = 80 -filament_density = 1.21 -disable_fan_first_layers = 4 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:Filatech FilaFlex40] -inherits = *FLEX* -filament_vendor = Filatech -fan_always_on = 1 -extrusion_multiplier = 1.1 -first_layer_temperature = 230 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 50 -bridge_fan_speed = 100 -max_fan_speed = 50 -min_fan_speed = 50 -filament_density = 1.22 -disable_fan_first_layers = 4 -min_print_speed = 15 -slowdown_below_layer_time = 10 -cooling = 1 - -[filament:Filatech FilaFlex30] -inherits = Filatech FilaFlex40 -temperature = 225 -filament_density = 1.15 -extrusion_multiplier = 1.1 - -[filament:Filatech FilaFlex55] -inherits = Filatech FilaFlex40 -temperature = 230 -filament_density = 1.18 -bed_temperature = 60 -fan_always_on = 0 -fan_below_layer_time = 60 -first_layer_temperature = 235 -extrusion_multiplier = 1 - -[filament:Filatech TPE] -inherits = Filatech FilaFlex40 -first_layer_temperature = 230 -temperature = 225 -filament_density = 1.2 -fan_below_layer_time = 60 -max_fan_speed = 80 -min_fan_speed = 80 -fan_always_on = 1 - -[filament:Filatech TPU] -inherits = Filatech FilaFlex40 -first_layer_temperature = 230 -filament_density = 1.2 -fan_below_layer_time = 60 -max_fan_speed = 80 -min_fan_speed = 80 -fan_always_on = 1 -temperature = 235 - -[filament:Filatech ABS] -inherits = *ABSC* -filament_vendor = Filatech -extrusion_multiplier = 0.95 -filament_density = 1.05 - -[filament:Filatech FilaCarbon] -inherits = *ABSC* -filament_vendor = Filatech -extrusion_multiplier = 0.95 -filament_density = 1.1 -first_layer_bed_temperature = 105 -bed_temperature = 100 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Filatech FilaPLA] -inherits = *PLA* -filament_vendor = Filatech -filament_density = 1.3 -first_layer_temperature = 235 -first_layer_bed_temperature = 50 -temperature = 230 -bed_temperature = 55 - -[filament:Filatech PLA] -inherits = *PLA* -filament_vendor = Filatech -filament_density = 1.25 -first_layer_temperature = 215 -temperature = 210 - -[filament:Filatech PLA+] -inherits = Filatech PLA -filament_density = 1.24 - -[filament:Filatech FilaTough] -inherits = Filatech ABS -extrusion_multiplier = 0.95 -filament_density = 1.29 -first_layer_temperature = 245 -first_layer_bed_temperature = 80 -temperature = 240 -bed_temperature = 90 -cooling = 0 - -[filament:Filatech HIPS] -inherits = Prusa HIPS -filament_vendor = Filatech -filament_density = 1.07 -filament_spool_weight = -first_layer_temperature = 230 -first_layer_bed_temperature = 100 -temperature = 225 -bed_temperature = 110 - -[filament:Filatech PA] -inherits = *ABSC* -filament_vendor = Filatech -filament_density = 1.1 -first_layer_temperature = 275 -first_layer_bed_temperature = 110 -temperature = 275 -bed_temperature = 115 -fan_always_on = 0 -cooling = 0 -bridge_fan_speed = 25 -filament_type = NYLON - -[filament:Filatech PC] -inherits = Filatech PA -first_layer_bed_temperature = 110 -bed_temperature = 115 -filament_density = 1.2 -filament_type = PC - -[filament:Filatech PC-ABS] -inherits = Filatech PC -first_layer_temperature = 270 -temperature = 270 -first_layer_bed_temperature = 100 -bed_temperature = 100 -filament_density = 1.08 -filament_type = PC -fan_always_on = 0 -cooling = 1 -extrusion_multiplier = 0.95 -disable_fan_first_layers = 6 - -[filament:Filatech PETG] -inherits = *PET* -filament_vendor = Filatech -filament_density = 1.27 -first_layer_temperature = 240 -first_layer_bed_temperature = 75 -temperature = 245 -bed_temperature = 80 -extrusion_multiplier = 0.95 -fan_always_on = 0 - -[filament:Filatech Wood-PLA] -inherits = Filatech PLA -filament_density = 1.05 -first_layer_temperature = 210 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Ultrafuse PET] -inherits = *PET* -filament_vendor = BASF -filament_density = 1.33 -first_layer_temperature = 220 -first_layer_bed_temperature = 70 -temperature = 215 -bed_temperature = 70 -fan_below_layer_time = 10 -min_fan_speed = 75 -max_fan_speed = 100 -bridge_fan_speed = 100 -filament_type = PET -disable_fan_first_layers = 1 -full_fan_speed_layer = 3 -filament_notes = "BASF Forward AM Ultrafuse PET\nMaterial profile version 1.0\n\nMaterial Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion.\n" - -[filament:Ultrafuse PRO1] -inherits = Prusament PLA -filament_vendor = BASF -filament_density = 1.25 -filament_spool_weight = 0 -filament_colour = #FFFFFF -filament_notes = "BASF Forward AM Ultrafuse PLA PRO1\nMaterial profile version 1.0\n\nMaterial Description\nPLA PRO1 is an extremely versatile tough PLA filament made for professionals. It reduces your printing time by 30% – 80%, (subject to printer and object limitations) and the strength exceeds overall mechanical properties of printed ABS parts. Printer settings can be tuned to achieve blazing fast speeds or an unrivaled surface finish. The excellent quality control ensures the highest levels of consistency between colors and batches, it will perform as expected, every time.\n\nPrinting Recommendations:\nUltrafuse PLA PRO1 can be printed directly onto a clean build plate.\n" - -[filament:Ultrafuse ABS] -inherits = *ABSC* -filament_vendor = BASF -filament_density = 1.04 -min_fan_speed = 10 -max_fan_speed = 20 -bed_temperature = 100 -disable_fan_first_layers = 3 -filament_colour = #FFFFFF -filament_notes = "BASF Forward AM Ultrafuse ABS\nMaterial profile version 1.0\n\nMaterial Description\nABS is the second most used 3D printing material. It is strong, flexible and has a high heat resistance. ABS is a preferred plastic for engineers and professional applications. ABS can be smoothened with acetone. To make a proper 3D print with ABS you will need a heated print bed. The filament is available in 9 colors.\n\nPrinting Recommendations:\n\nApply Tape, adhesion spray or glue to a clean build plate to improve adhesion.\n" - -[filament:Ultrafuse Metal] -inherits = *ABSC* -filament_vendor = BASF -filament_density = 4.5 -extrusion_multiplier = 1.08 -first_layer_temperature = 250 -first_layer_bed_temperature = 100 -temperature = 250 -bed_temperature = 100 -min_fan_speed = 0 -max_fan_speed = 0 -bridge_fan_speed = 0 -cooling = 0 -fan_always_on = 0 -filament_max_volumetric_speed = 4 -filament_type = METAL -compatible_printers_condition = nozzle_diameter[0]>=0.4 -filament_colour = #FFFFFF - -[filament:Polymaker PC-Max] -inherits = *ABS* -filament_vendor = Polymaker -filament_density = 1.20 -filament_type = PC -bed_temperature = 115 -filament_colour = #FFF2EC -first_layer_bed_temperature = 100 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 0 - -[filament:PrimaSelect PVA+] -inherits = *PLA* -filament_vendor = PrimaSelect -filament_density = 1.23 -cooling = 0 -fan_always_on = 0 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = PVA -first_layer_temperature = 195 -temperature = 195 - -[filament:Prusa ABS] -inherits = *ABSC* -filament_vendor = Made for Prusa -filament_density = 1.08 -filament_spool_weight = 230 - -[filament:Prusa HIPS] -inherits = *ABS* -filament_vendor = Made for Prusa -filament_density = 1.04 -filament_spool_weight = 230 -bridge_fan_speed = 50 -cooling = 1 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 10 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = HIPS -first_layer_temperature = 220 -max_fan_speed = 20 -min_fan_speed = 20 -temperature = 220 - -[filament:Generic HIPS] -inherits = *ABS* -filament_vendor = Generic -filament_density = 1.04 -bridge_fan_speed = 50 -cooling = 1 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 10 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = HIPS -first_layer_temperature = 230 -max_fan_speed = 20 -min_fan_speed = 20 -temperature = 230 - -[filament:Prusa PETG] -inherits = *PET* -filament_vendor = Made for Prusa -filament_density = 1.27 -filament_spool_weight = 230 - -[filament:Verbatim PETG] -inherits = *PET* -filament_vendor = Verbatim -filament_density = 1.27 -filament_spool_weight = 235 - -[filament:Fiberlogy PETG] -inherits = *PET* -filament_vendor = Fiberlogy -filament_density = 1.27 - -[filament:Prusament PETG] -inherits = *PET* -filament_vendor = Prusa Polymers -first_layer_temperature = 240 -temperature = 250 -filament_density = 1.27 -filament_spool_weight = 201 -filament_type = PETG - -[filament:Prusa PLA] -inherits = *PLA* -filament_vendor = Made for Prusa -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:Fiberlogy PLA] -inherits = *PLA* -filament_vendor = Fiberlogy -filament_density = 1.24 - -[filament:Filament PM PLA] -inherits = *PLA* -filament_vendor = Filament PM -filament_density = 1.24 -filament_spool_weight = 230 - -[filament:AmazonBasics PLA] -inherits = *PLA* -filament_vendor = AmazonBasics -filament_density = 1.24 - -[filament:Overture PLA] -inherits = *PLA* -filament_vendor = Overture -filament_density = 1.24 -filament_spool_weight = 235 - -[filament:Hatchbox PLA] -inherits = *PLA* -filament_vendor = Hatchbox -filament_density = 1.27 -filament_spool_weight = 245 - -[filament:Esun PLA] -inherits = *PLA* -filament_vendor = Esun -filament_density = 1.24 -filament_spool_weight = 265 - -[filament:Das Filament PLA] -inherits = *PLA* -filament_vendor = Das Filament -filament_density = 1.24 - -[filament:EUMAKERS PLA] -inherits = *PLA* -filament_vendor = EUMAKERS -filament_density = 1.24 - -[filament:Floreon3D PLA] -inherits = *PLA* -filament_vendor = Floreon3D -filament_density = 1.24 - -[filament:Prusament PLA] -inherits = *PLA* -filament_vendor = Prusa Polymers -temperature = 215 -filament_density = 1.24 -filament_spool_weight = 201 -filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" -compatible_printers_condition = nozzle_diameter[0]!=0.8 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) - -[filament:Prusament PVB] -inherits = *PLA* -filament_vendor = Prusa Polymers -temperature = 215 -bed_temperature = 75 -first_layer_bed_temperature = 75 -filament_density = 1.09 -filament_spool_weight = 201 -filament_type = PVB -filament_soluble = 1 -filament_colour = #FFFF6F -slowdown_below_layer_time = 20 - -[filament:Fillamentum Flexfill 98A] -inherits = *FLEX* -filament_vendor = Fillamentum -filament_density = 1.23 -filament_spool_weight = 230 -extrusion_multiplier = 1.1 -filament_max_volumetric_speed = 1.35 -fan_always_on = 1 -cooling = 0 -max_fan_speed = 60 -min_fan_speed = 60 -disable_fan_first_layers = 4 - -[filament:Taulman Bridge] -inherits = *common* -filament_vendor = Taulman -filament_density = 1.13 -bed_temperature = 90 -bridge_fan_speed = 40 -cooling = 0 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -filament_colour = #DEE0E6 -filament_soluble = 0 -filament_type = NYLON -first_layer_bed_temperature = 60 -first_layer_temperature = 240 -max_fan_speed = 0 -min_fan_speed = 0 -temperature = 250 - -[filament:Fillamentum Nylon FX256] -inherits = *common* -filament_vendor = Fillamentum -filament_density = 1.01 -filament_spool_weight = 230 -bed_temperature = 90 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 6 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 20 -filament_colour = #DEE0E6 -filament_max_volumetric_speed = 6 -filament_soluble = 0 -filament_type = NYLON -first_layer_bed_temperature = 90 -first_layer_temperature = 250 -max_fan_speed = 0 -min_fan_speed = 0 -temperature = 250 - -[filament:Fiberthree F3 PA Pure Pro] -inherits = *common* -filament_vendor = Fiberthree -filament_density = 1.2 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 20 -min_fan_speed = 20 - -[filament:Fiberthree F3 PA-CF Pro] -inherits = *common* -filament_vendor = Fiberthree -filament_density = 1.25 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 275 -temperature = 275 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 0 -min_fan_speed = 0 -compatible_printers_condition = nozzle_diameter[0]>=0.4 - -[filament:Fiberthree F3 PA-GF Pro] -inherits = Fiberthree F3 PA-CF Pro -filament_vendor = Fiberthree -filament_density = 1.27 -fan_always_on = 1 -max_fan_speed = 15 -min_fan_speed = 15 - -[filament:Taulman T-Glase] -inherits = *PET* -filament_vendor = Taulman -filament_density = 1.27 -bridge_fan_speed = 40 -cooling = 0 -fan_always_on = 0 -first_layer_bed_temperature = 90 -first_layer_temperature = 240 -max_fan_speed = 5 -min_fan_speed = 0 - -[filament:Verbatim PLA] -inherits = *PLA* -filament_vendor = Verbatim -filament_density = 1.24 -filament_spool_weight = 235 - -[filament:Verbatim BVOH] -inherits = *common* -filament_vendor = Verbatim -filament_density = 1.14 -filament_spool_weight = 235 -bed_temperature = 60 -bridge_fan_speed = 100 -cooling = 0 -disable_fan_first_layers = 1 -extrusion_multiplier = 1 -fan_always_on = 0 -fan_below_layer_time = 100 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = PVA -first_layer_bed_temperature = 60 -first_layer_temperature = 215 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 210 - -[filament:Verbatim PP] -inherits = *common* -filament_vendor = Verbatim -filament_density = 0.89 -filament_spool_weight = 235 -bed_temperature = 100 -bridge_fan_speed = 100 -cooling = 1 -disable_fan_first_layers = 2 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 100 -filament_colour = #DEE0E6 -filament_type = PP -first_layer_bed_temperature = 100 -first_layer_temperature = 220 -max_fan_speed = 100 -min_fan_speed = 100 -temperature = 220 - diff --git a/resources/profiles/Templates.idx b/resources/profiles/Templates.idx new file mode 100644 index 0000000000..3edb4400fb --- /dev/null +++ b/resources/profiles/Templates.idx @@ -0,0 +1,2 @@ +min_slic3r_version = 2.6.0-alpha0 +1.0.0 Initial diff --git a/resources/profiles/Templates.ini b/resources/profiles/Templates.ini new file mode 100644 index 0000000000..6c6ff646c6 --- /dev/null +++ b/resources/profiles/Templates.ini @@ -0,0 +1,2144 @@ +# Generic filament profile templates + +[vendor] +name = Filaments Templates +config_version = 1.0.0 +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Templates/ +templates_profile = 1 + +## Generic filament profiles + +# Print profiles for the Prusa Research printers. + +[filament:*common*] +cooling = 1 +compatible_printers = +compatible_printers_condition = +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +filament_loading_speed = 14 +filament_loading_speed_start = 19 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_toolchange_delay = 0 +filament_cooling_moves = 1 +filament_cooling_initial_speed = 3 +filament_cooling_final_speed = 2 +filament_load_time = 0 +filament_unload_time = 0 +filament_ramming_parameters = "130 120 2.70968 2.93548 3.32258 3.83871 4.58065 5.54839 6.51613 7.35484 7.93548 8.16129| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 11.342 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" +filament_minimal_purge_on_wipe_tower = 0 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = "" +filament_soluble = 0 +min_print_speed = 10 +slowdown_below_layer_time = 10 +start_filament_gcode = + +[filament:*PLA*] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF8000 +filament_max_volumetric_speed = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:*PET*] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 50 +disable_fan_first_layers = 3 +full_fan_speed_layer = 5 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_max_volumetric_speed = 0 +filament_type = PETG +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #FFF2EC +filament_max_volumetric_speed = 0 +filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*ABSC*] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 25 +cooling = 1 +disable_fan_first_layers = 4 +fan_always_on = 0 +fan_below_layer_time = 30 +slowdown_below_layer_time = 20 +filament_colour = #FFF2EC +filament_max_volumetric_speed = 0 +filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 15 +min_fan_speed = 15 +min_print_speed = 15 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bed_temperature = 50 +bridge_fan_speed = 80 +cooling = 0 +disable_fan_first_layers = 3 +extrusion_multiplier = 1.15 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #008000 +filament_max_volumetric_speed = 1.8 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 240 +max_fan_speed = 90 +min_fan_speed = 70 +temperature = 240 + +[filament:ColorFabb bronzeFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.12 +filament_cost = 80.65 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #804040 + +[filament:ColorFabb steelFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 80.65 +filament_density = 3.13 +filament_spool_weight = 236 +filament_colour = #808080 + +[filament:ColorFabb copperFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 80.65 +filament_density = 3.9 +filament_spool_weight = 236 +filament_colour = #82603E + +[filament:ColorFabb HT] +inherits = *PET* +filament_vendor = ColorFabb +bed_temperature = 100 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_cost = 65.66 +filament_density = 1.18 +filament_spool_weight = 236 +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* +filament_vendor = ColorFabb +filament_cost = 54.84 +filament_density = 1.24 +filament_spool_weight = 236 + +[filament:ColorFabb woodFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 78.63 +filament_density = 1.15 +filament_spool_weight = 236 +filament_colour = #dfc287 +first_layer_temperature = 200 +temperature = 200 + +[filament:ColorFabb corkFill] +inherits = *PLA* +filament_vendor = ColorFabb +extrusion_multiplier = 1.1 +filament_cost = 78.63 +filament_density = 1.18 +filament_spool_weight = 236 +filament_colour = #634d33 +filament_max_volumetric_speed = 6 +first_layer_temperature = 220 +temperature = 220 + +[filament:ColorFabb XT] +inherits = *PET* +filament_vendor = ColorFabb +filament_cost = 62.90 +filament_density = 1.27 +filament_spool_weight = 236 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +filament_vendor = ColorFabb +extrusion_multiplier = 1.05 +filament_cost = 80.65 +filament_density = 1.35 +filament_spool_weight = 236 +filament_colour = #804040 +filament_max_volumetric_speed = 2 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +filament_vendor = ColorFabb +filament_cost = 52.46 +filament_density = 1.2 +filament_spool_weight = 236 +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:ColorFabb nGen flex] +inherits = *FLEX* +filament_vendor = ColorFabb +filament_cost = 58.30 +filament_density = 1 +filament_spool_weight = 236 +bed_temperature = 85 +bridge_fan_speed = 40 +cooling = 1 +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +fan_below_layer_time = 10 +filament_max_volumetric_speed = 5 +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +max_fan_speed = 35 +min_fan_speed = 20 +temperature = 260 + +[filament:Kimya PETG Carbon] +inherits = *PET* +filament_vendor = Kimya +extrusion_multiplier = 1.05 +filament_cost = 150.02 +filament_density = 1.317 +filament_colour = #804040 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 240 + +[filament:Kimya ABS Carbon] +inherits = *ABSC* +filament_vendor = Kimya +filament_cost = 140.34 +filament_density = 1.032 +filament_colour = #804040 +first_layer_temperature = 260 +temperature = 260 + +[filament:Kimya ABS Kevlar] +inherits = Kimya ABS Carbon +filament_vendor = Kimya +filament_density = 1.037 + +[filament:E3D Edge] +inherits = *PET* +filament_vendor = E3D +filament_cost = 56.9 +filament_density = 1.26 +filament_type = EDGE + +[filament:E3D PC-ABS] +inherits = *ABS* +filament_vendor = E3D +filament_cost = 0 +filament_type = PC +filament_density = 1.05 +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum PLA] +inherits = *PLA* +filament_vendor = Fillamentum +filament_cost = 35.48 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Fillamentum ABS] +inherits = *ABSC* +filament_vendor = Fillamentum +filament_cost = 32.4 +filament_density = 1.04 +filament_spool_weight = 230 +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +filament_vendor = Fillamentum +filament_cost = 38.7 +filament_density = 1.07 +filament_spool_weight = 230 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 260 +temperature = 260 +filament_type = ASA + +[filament:Prusament ASA] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_cost = 42.69 +filament_density = 1.07 +filament_spool_weight = 201 +fan_always_on = 1 +first_layer_temperature = 260 +first_layer_bed_temperature = 100 +temperature = 260 +bed_temperature = 100 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 15 +disable_fan_first_layers = 4 +filament_type = ASA +filament_colour = #FFF2EC + +[filament:Prusament PC Blend] +inherits = *ABS* +filament_vendor = Prusa Polymers +filament_cost = 62.36 +filament_density = 1.22 +filament_spool_weight = 201 +fan_always_on = 0 +first_layer_temperature = 275 +first_layer_bed_temperature = 105 +temperature = 275 +bed_temperature = 105 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 20 +disable_fan_first_layers = 4 +fan_below_layer_time = 30 +filament_type = PC +filament_colour = #DEE0E6 + +[filament:Prusament PC Blend Carbon Fiber] +inherits = Prusament PC Blend +filament_cost = 90.73 +filament_density = 1.16 +extrusion_multiplier = 1.04 +first_layer_temperature = 285 +temperature = 285 +disable_fan_first_layers = 4 +fan_below_layer_time = 10 +filament_colour = #BBBBBB + +[filament:Prusament PA11 Carbon Fiber] +inherits = Prusament PC Blend Carbon Fiber +filament_cost = 151.24 +filament_density = 1.11 +filament_type = PA +extrusion_multiplier = 1.05 +first_layer_temperature = 275 +temperature = 285 +first_layer_bed_temperature = 90 +bed_temperature = 105 +fan_below_layer_time = 10 + +[filament:Fillamentum CPE] +inherits = *PET* +filament_vendor = Fillamentum +filament_cost = 56.45 +filament_density = 1.25 +filament_spool_weight = 230 +filament_type = CPE +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +min_fan_speed = 30 +max_fan_speed = 50 +disable_fan_first_layers = 3 +full_fan_speed_layer = 5 +temperature = 275 + +[filament:Fillamentum Timberfill] +inherits = *PLA* +filament_vendor = Fillamentum +extrusion_multiplier = 1.05 +filament_cost = 68 +filament_density = 1.15 +filament_spool_weight = 230 +filament_colour = #804040 +first_layer_temperature = 190 +temperature = 190 +filament_retract_lift = 0.2 + +[filament:Smartfil Wood] +inherits = *PLA* +filament_vendor = Smart Materials 3D +extrusion_multiplier = 1.05 +filament_cost = 68 +filament_density = 1.58 +filament_colour = #804040 +first_layer_temperature = 220 +temperature = 220 + +[filament:Generic ABS] +inherits = *ABSC* +filament_vendor = Generic +filament_cost = 27.82 +filament_density = 1.04 + +[filament:Esun ABS] +inherits = *ABSC* +filament_vendor = Esun +filament_cost = 27.82 +filament_density = 1.01 +filament_spool_weight = 265 + +[filament:Hatchbox ABS] +inherits = *ABSC* +filament_vendor = Hatchbox +filament_cost = 27.82 +filament_density = 1.04 +filament_spool_weight = 245 + +[filament:Filament PM ABS] +inherits = *ABSC* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Verbatim ABS] +inherits = *ABSC* +filament_vendor = Verbatim +filament_cost = 25.87 +filament_density = 1.05 +filament_spool_weight = 235 + +[filament:Generic PETG] +inherits = *PET* +filament_vendor = Generic +filament_cost = 27.82 +filament_density = 1.27 + +[filament:Extrudr DuraPro ASA] +inherits = Fillamentum ASA +filament_vendor = Extrudr +bed_temperature = 90 +filament_cost = 34.64 +filament_density = 1.05 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=120" +first_layer_bed_temperature = 90 +first_layer_temperature = 220 +temperature = 220 +filament_spool_weight = 230 + +[filament:Extrudr PETG] +inherits = *PET* +filament_vendor = Extrudr +bed_temperature = 70 +filament_cost = 35.45 +filament_density = 1.29 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=94" +first_layer_bed_temperature = 70 +first_layer_temperature = 220 +temperature = 220 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 +full_fan_speed_layer = 0 + +[filament:Extrudr XPETG CF] +inherits = Extrudr PETG +filament_cost = 62.49 +filament_density = 1.29 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=198" +first_layer_temperature = 235 +temperature = 235 +filament_spool_weight = 230 + +[filament:Extrudr XPETG Matt] +inherits = Extrudr PETG +filament_cost = 29.99 +filament_density = 1.41 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=199" +first_layer_temperature = 230 +temperature = 230 + +[filament:Extrudr BioFusion] +inherits = *PLA* +filament_vendor = Extrudr +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_cost = 31.23 +filament_density = 1.25 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=121" +first_layer_temperature = 220 +temperature = 220 +max_fan_speed = 45 +min_fan_speed = 25 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr Flax] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 50.91 +filament_density = 1.45 +filament_notes = "High Performance Filament for decorative parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=131" +first_layer_temperature = 190 +temperature = 190 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr GreenTEC] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 50.91 +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?ignorechildren=1&material=106" +first_layer_temperature = 208 +temperature = 208 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr GreenTEC Pro] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 56.23 +filament_density = 1.35 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher strength and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=134" +temperature = 215 +max_fan_speed = 80 +min_fan_speed = 30 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr GreenTEC Pro Carbon] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 62.49 +filament_density = 1.2 +filament_notes = "High Performance Filament for technical parts.\nPrints as easily as PLA with much higher stregnth and temperature resistance.\nFully biodegradable with a nice matt finish.\n\nhttps://www.extrudr.com/en/products/catalogue/?material=138" +first_layer_temperature = 225 +max_fan_speed = 80 +min_fan_speed = 30 +temperature = 225 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 20 +filament_spool_weight = 230 + +[filament:Extrudr PLA NX1] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 22.76 +filament_density = 1.24 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=97" +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 0 +max_fan_speed = 90 +min_fan_speed = 30 +slowdown_below_layer_time = 20 +filament_spool_weight = 262 + +[filament:Extrudr PLA NX2] +inherits = Extrudr PLA NX1 +filament_cost = 23.63 +filament_density = 1.3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=128" + +[filament:Extrudr Flex Hard] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.2 +filament_max_volumetric_speed = 3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=115" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex Medium] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.19 +filament_max_volumetric_speed = 3 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=117" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:Extrudr Flex SemiSoft] +inherits = *FLEX* +filament_vendor = Extrudr +disable_fan_first_layers = 1 +extrusion_multiplier = 1.15 +filament_cost = 39.98 +filament_density = 1.18 +filament_max_volumetric_speed = 1.8 +filament_notes = "https://www.extrudr.com/en/products/catalogue/?material=116" +filament_spool_weight = 230 +slowdown_below_layer_time = 20 + +[filament:addnorth Adamant S1] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_cost = +filament_density = 1.22 +temperature = 250 +bed_temperature = 50 +first_layer_temperature = 245 +first_layer_bed_temperature = 50 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 40 +max_fan_speed = 70 +bridge_fan_speed = 60 +filament_max_volumetric_speed = 1.7 + +[filament:addnorth Adura X] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +filament_type = PA +extrusion_multiplier = 0.98 +bed_temperature = 105 +first_layer_bed_temperature = 105 +first_layer_temperature = 265 +temperature = 270 +fan_always_on = 0 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +min_print_speed = 20 +fan_below_layer_time = 10 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 + +[filament:addnorth E-PLA] +inherits = *PLA* +filament_vendor = addnorth +filament_cost = 24.99 +filament_density = 1.24 +extrusion_multiplier = 0.98 +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +filament_spool_weight = 0 + +[filament:addnorth ESD-PETG] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 245 +temperature = 265 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 30 +bridge_fan_speed = 35 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 8 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 + +[filament:addnorth OBC Polyethylene] +inherits = *FLEX* +filament_vendor = addnorth +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +filament_cost = 82 +filament_density = 1.22 +temperature = 200 +bed_temperature = 100 +first_layer_temperature = 195 +first_layer_bed_temperature = 100 +slowdown_below_layer_time = 5 +fan_below_layer_time = 15 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 20 +max_fan_speed = 30 +bridge_fan_speed = 40 +min_print_speed = 20 +filament_spool_weight = 0 +filament_notes = "Use Magigoo PP bed adhesive or PP packing tape (on a cold printbed)." + +[filament:addnorth PETG] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +bed_temperature = 80 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 250 +fan_always_on = 1 +min_fan_speed = 15 +max_fan_speed = 40 +bridge_fan_speed = 50 +slowdown_below_layer_time = 10 +min_print_speed = 15 +fan_below_layer_time = 15 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:addnorth Rigid X] +inherits = *PET* +filament_vendor = addnorth +filament_cost = 29.99 +filament_density = 1.27 +extrusion_multiplier = 1 +bed_temperature = 85 +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +temperature = 260 +fan_always_on = 1 +min_fan_speed = 20 +max_fan_speed = 60 +bridge_fan_speed = 70 +slowdown_below_layer_time = 10 +fan_below_layer_time = 20 +min_print_speed = 20 +disable_fan_first_layers = 3 +full_fan_speed_layer = 0 +filament_spool_weight = 0 +filament_notes = "Please use a nozzle that is resistant to abrasive filaments, like hardened steel." + +[filament:addnorth Textura] +inherits = *PLA* +filament_vendor = addnorth +filament_cost = 24.99 +filament_density = 1.24 +extrusion_multiplier = 0.95 +temperature = 215 +bed_temperature = 65 +first_layer_temperature = 215 +first_layer_bed_temperature = 65 +min_fan_speed = 20 +max_fan_speed = 40 +bridge_fan_speed = 60 +full_fan_speed_layer = 0 +slowdown_below_layer_time = 15 +min_print_speed = 20 +filament_spool_weight = 0 + +[filament:Filamentworld ABS] +inherits = *ABSC* +filament_vendor = Filamentworld +filament_cost = 24.9 +filament_density = 1.04 +temperature = 230 +bed_temperature = 95 +first_layer_temperature = 240 +first_layer_bed_temperature = 100 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 20 +disable_fan_first_layers = 3 +fan_below_layer_time = 60 +slowdown_below_layer_time = 15 +bridge_fan_speed = 20 + +[filament:Filamentworld PETG] +inherits = *PET* +filament_vendor = Filamentworld +filament_cost = 34.9 +filament_density = 1.27 +bed_temperature = 70 +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +temperature = 235 +fan_always_on = 1 +min_fan_speed = 25 +max_fan_speed = 55 +bridge_fan_speed = 55 +slowdown_below_layer_time = 20 +min_print_speed = 20 +fan_below_layer_time = 35 +disable_fan_first_layers = 2 +full_fan_speed_layer = 0 +filament_spool_weight = 0 + +[filament:Filamentworld PLA] +inherits = *PLA* +filament_vendor = Filamentworld +filament_cost = 24.9 +filament_density = 1.24 +temperature = 205 +bed_temperature = 55 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 10 +filament_spool_weight = 0 +min_print_speed = 20 + +[filament:Filament PM PETG] +inherits = *PET* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Generic PLA] +inherits = *PLA* +filament_vendor = Generic +filament_cost = 25.4 +filament_density = 1.24 + +[filament:3D-Fuel Standard PLA] +inherits = *PLA* +filament_vendor = 3D-Fuel +filament_cost = 22.14 +filament_density = 1.24 +first_layer_temperature = 210 +temperature = 200 + +[filament:3D-Fuel EasiPrint PLA] +inherits = 3D-Fuel Standard PLA +filament_cost = 30.44 + +[filament:3D-Fuel Pro PLA] +inherits = *PLA* +filament_vendor = 3D-Fuel +filament_cost = 26.57 +filament_density = 1.22 +first_layer_temperature = 220 +temperature = 215 + +[filament:3D-Fuel Buzzed] +inherits = 3D-Fuel Standard PLA +filament_cost = 44.27 +filament_retract_lift = 0 +first_layer_temperature = 210 +temperature = 195 + +[filament:3D-Fuel Wound up] +inherits = 3D-Fuel Buzzed +filament_cost = 44.27 +filament_retract_lift = nil +first_layer_temperature = 215 +temperature = 210 + +[filament:3D-Fuel Workday ABS] +inherits = *ABSC* +filament_vendor = 3D-Fuel +filament_cost = 23.25 +filament_density = 1.04 + +[filament:Jessie PLA] +inherits = *PLA* +filament_vendor = Printed Solid +filament_cost = 21 +filament_density = 1.24 + +[filament:Jessie PETG] +inherits = *PET* +filament_vendor = Printed Solid +filament_cost = 22 +filament_density = 1.27 +first_layer_temperature = 240 +first_layer_bed_temperature = 85 +temperature = 245 +bed_temperature = 90 + +[filament:Devil Design PLA] +inherits = *PLA* +filament_vendor = Devil Design +filament_cost = 20.99 +filament_density = 1.24 +filament_spool_weight = 250 + +[filament:Devil Design PETG] +inherits = *PET* +filament_vendor = Devil Design +filament_cost = 20.99 +filament_density = 1.23 +filament_spool_weight = 250 +first_layer_temperature = 230 +first_layer_bed_temperature = 85 +temperature = 230 +bed_temperature = 90 + +[filament:Spectrum PLA] +inherits = *PLA* +filament_vendor = Spectrum +filament_cost = 21.50 +filament_density = 1.24 + +[filament:Generic FLEX] +inherits = *FLEX* +filament_vendor = Generic +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.2 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil + +[filament:Fillamentum Flexfill 92A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_cost = 33.99 +filament_density = 1.20 +filament_spool_weight = 230 +filament_max_volumetric_speed = 1.2 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 + +[filament:AmazonBasics TPU] +inherits = *FLEX* +filament_vendor = AmazonBasics +fan_always_on = 1 +filament_max_volumetric_speed = 1.8 +extrusion_multiplier = 1.14 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 235 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_retract_before_travel = 3 +filament_cost = 19.99 +filament_density = 1.21 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:SainSmart TPU] +inherits = *FLEX* +filament_vendor = SainSmart +fan_always_on = 1 +filament_max_volumetric_speed = 2.5 +extrusion_multiplier = 1.05 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 80 +min_fan_speed = 80 +filament_retract_before_travel = 3 +filament_cost = 32.99 +filament_density = 1.21 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:NinjaTek NinjaFlex TPU] +inherits = *FLEX* +filament_vendor = NinjaTek +fan_always_on = 1 +filament_max_volumetric_speed = 1.2 +extrusion_multiplier = 1.15 +first_layer_temperature = 238 +first_layer_bed_temperature = 50 +temperature = 238 +bed_temperature = 50 +bridge_fan_speed = 75 +max_fan_speed = 60 +min_fan_speed = 60 +filament_cost = 85 +filament_density = 1.19 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +min_print_speed = 10 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:NinjaTek Cheetah TPU] +inherits = NinjaTek NinjaFlex TPU +filament_retract_length = 1.5 +filament_density = 1.22 +filament_max_volumetric_speed = 4 +extrusion_multiplier = 1.05 +filament_retract_speed = 45 +filament_deretract_speed = 25 +first_layer_temperature = 240 +temperature = 240 + +[filament:Filatech FilaFlex40] +inherits = *FLEX* +filament_vendor = Filatech +fan_always_on = 1 +filament_max_volumetric_speed = 2.5 +extrusion_multiplier = 1.1 +first_layer_temperature = 230 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 50 +bridge_fan_speed = 100 +max_fan_speed = 50 +min_fan_speed = 50 +filament_cost = 84.68 +filament_density = 1.22 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 +min_print_speed = 15 +slowdown_below_layer_time = 10 +cooling = 1 + +[filament:Filatech FilaFlex30] +inherits = Filatech FilaFlex40 +temperature = 225 +filament_density = 1.15 +extrusion_multiplier = 1.1 +filament_cost = + +[filament:Filatech FilaFlex55] +inherits = Filatech FilaFlex40 +temperature = 230 +filament_density = 1.18 +bed_temperature = 60 +fan_always_on = 0 +fan_below_layer_time = 60 +filament_cost = +first_layer_temperature = 235 +extrusion_multiplier = 1 + +[filament:Filatech TPU] +inherits = Filatech FilaFlex40 +first_layer_temperature = 230 +filament_density = 1.2 +fan_below_layer_time = 60 +max_fan_speed = 80 +min_fan_speed = 80 +fan_always_on = 1 +temperature = 235 + +[filament:Filatech ABS] +inherits = *ABSC* +filament_vendor = Filatech +filament_cost = +extrusion_multiplier = 1 +filament_density = 1.05 + +[filament:Filatech FilaCarbon] +inherits = *ABSC* +filament_vendor = Filatech +filament_cost = +extrusion_multiplier = 0.95 +filament_density = 1.1 +first_layer_bed_temperature = 100 +bed_temperature = 100 + +[filament:Filatech FilaPLA] +inherits = *PLA* +filament_vendor = Filatech +filament_cost = +filament_density = 1.3 +first_layer_temperature = 235 +first_layer_bed_temperature = 50 +temperature = 230 +bed_temperature = 55 + +[filament:Filatech PLA] +inherits = *PLA* +filament_vendor = Filatech +filament_cost = +filament_density = 1.25 +first_layer_temperature = 215 +temperature = 210 + +[filament:Filatech PLA+] +inherits = Filatech PLA +filament_density = 1.24 + +[filament:Filatech FilaTough] +inherits = Filatech ABS +filament_cost = +extrusion_multiplier = 0.95 +filament_density = 1.29 +first_layer_temperature = 245 +first_layer_bed_temperature = 80 +temperature = 240 +bed_temperature = 90 +cooling = 0 + +[filament:Filatech HIPS] +inherits = Prusa HIPS +filament_vendor = Filatech +filament_density = 1.07 +filament_spool_weight = +first_layer_temperature = 230 +first_layer_bed_temperature = 100 +temperature = 225 +bed_temperature = 100 + +[filament:Filatech PA] +inherits = *ABSC* +filament_vendor = Filatech +filament_density = 1.1 +first_layer_temperature = 275 +first_layer_bed_temperature = 105 +temperature = 275 +bed_temperature = 105 +fan_always_on = 0 +cooling = 0 +bridge_fan_speed = 25 +filament_type = PA + +[filament:Filatech PC] +inherits = Filatech PA +filament_density = 1.2 +filament_type = PC + +[filament:Filatech PC-ABS] +inherits = Filatech PC +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_density = 1.08 +filament_type = PC +fan_always_on = 0 +cooling = 1 +extrusion_multiplier = 0.95 +disable_fan_first_layers = 6 + +[filament:Filatech PETG] +inherits = *PET* +filament_vendor = Filatech +filament_cost = +filament_density = 1.27 +first_layer_temperature = 240 +first_layer_bed_temperature = 75 +temperature = 245 +bed_temperature = 80 +extrusion_multiplier = 0.95 +fan_always_on = 0 + +[filament:Filatech Wood-PLA] +inherits = Filatech PLA +filament_density = 1.05 +first_layer_temperature = 210 + +[filament:Ultrafuse PET] +inherits = *PET* +filament_vendor = BASF +filament_density = 1.33 +filament_colour = #F7F7F7 +first_layer_temperature = 220 +first_layer_bed_temperature = 70 +temperature = 215 +bed_temperature = 70 +fan_below_layer_time = 10 +min_fan_speed = 75 +max_fan_speed = 100 +bridge_fan_speed = 100 +filament_type = PET +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +filament_notes = "Material Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PRO1] +inherits = Prusament PLA +filament_vendor = BASF +filament_cost = +filament_density = 1.25 +filament_spool_weight = 0 +filament_colour = #FFFFFF +filament_notes = "Material Description\nPLA PRO1 is an extremely versatile tough PLA filament made for professionals. It reduces your printing time by 30% – 80%, (subject to printer and object limitations) and the strength exceeds overall mechanical properties of printed ABS parts. Printer settings can be tuned to achieve blazing fast speeds or an unrivaled surface finish. The excellent quality control ensures the highest levels of consistency between colors and batches, it will perform as expected, every time.\n\nPrinting Recommendations:\nUltrafuse PLA PRO1 can be printed directly onto a clean build plate." + +[filament:Ultrafuse ABS] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 1.04 +min_fan_speed = 10 +max_fan_speed = 20 +bed_temperature = 100 +disable_fan_first_layers = 3 +filament_colour = #FFFFFF +filament_notes = "Material Description\nABS is the second most used 3D printing material. It is strong, flexible and has a high heat resistance. ABS is a preferred plastic for engineers and professional applications. ABS can be smoothened with acetone. To make a proper 3D print with ABS you will need a heated print bed. The filament is available in 9 colors.\n\nPrinting Recommendations:\n\nApply Tape, adhesion spray or glue to a clean build plate to improve adhesion." + +[filament:Ultrafuse ABS Fusion+] +inherits = Ultrafuse ABS +filament_density = 1.08 +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 +filament_colour = #FFF8D9 +filament_notes = "Material Description\nABS Fusion+ made with Polyscope XILOY™ 3D is an engineering filament which has been optimized for 3D-printing. This special grade has been developed in collaboration with Polyscope Polymers - renowned for its material solutions in the automotive industry. ABS is a thermoplastic which is used in many applications. Although ABS has been classified as a standard material in 3D-printing it is known to be quite challenging to process. ABS Fusion+ combines the properties of ABS with an improved processability. The filament is based on an ABS grade which can be directly printed on glass without any adhesives or tape and has a higher success rate of prints due to extreme low warping." + +[filament:Ultrafuse ASA] +inherits = Ultrafuse ABS Fusion+ +filament_density = 1.07 +filament_colour = #FFF4CA +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ASA +min_fan_speed = 25 +max_fan_speed = 50 +bridge_fan_speed = 100 +disable_fan_first_layers = 4 +filament_notes = "Material Description\nUltrafuse ASA is a high-performance thermoplastic with similar mechanical properties as ABS. ASA offers additional benefits such as high outdoor weather resistance. The UV resistance, toughness, and rigidity make it an ideal material to 3D-print outdoor fixtures and appliances without losing its properties or color. When also taking into account the high heat resistance and high chemical resistance, this filament is a good choice for many types of applications.\n\nPrinting Recommendations:\nApply Magigoo PC, 3D lac or Dimafix to a clean build plate to improve adhesion." + +[filament:Ultrafuse HIPS] +inherits = Ultrafuse ABS +temperature = 250 +filament_density = 1.02 +filament_type = HIPS +min_fan_speed = 20 +max_fan_speed = 20 +filament_soluble = 1 +filament_notes = "Material Description\nUltrafuse HIPS is a high-quality engineering thermoplastic, which is well known in the 3D-printing industry as a support material for ABS. But this material has additional properties to offer like good impact resistance, good dimensional stability, and easy post-processing. HiPS is a great material to use as a support for ABS because there is a good compatibility between the two materials, and HIPS is an easy breakaway support. Now you have the opportunity to create ABS models with complex geometry. HIPS is easy to post process with glue or with sanding paper." + +[filament:Ultrafuse PA] +inherits = Fillamentum Nylon FX256 +filament_vendor = BASF +filament_density = 1.12 +filament_colour = #ECFAFF +first_layer_temperature = 240 +temperature = 240 +first_layer_bed_temperature = 80 +bed_temperature = 70 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 0 +fan_below_layer_time = 30 +slowdown_below_layer_time = 20 +min_print_speed = 15 +filament_notes = "Material Description\nThe key features of Ultrafuse PA are the high strength and high modulus. Furthermore, Ultrafuse PA shows a good thermal distortion stability.\n\nPrinting Recommendations:\nApply PVA glue, Kapton tape or PA adhesive to a clean buildplate to improve adhesion." + +[filament:Ultrafuse PA6 GF30] +inherits = Ultrafuse PA +filament_density = 1.17 +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_colour = #404040 +fan_always_on = 1 +min_fan_speed = 0 +max_fan_speed = 50 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +filament_notes = "Material Description\nUltrafuse® PA6 GF30 is a unique compound specifically developed for FFF printing. Due to the glass fiber content of 30%, parts tend to warp less. In addition the excellent layer adhesion and its compatibility with the water soluble support Ultrafuse® BVOH make this material the perfect solution to develop industrial applications on an FFF printer.\n\nWith its high wear and chemical resistance, high stiffness and strength, Ultrafuse® PA6 GF30 is perfect for a wide variety of applications in automotive, electronics or transportation.\n\nUltrafuse PA6 GF30 is designed for functional prototyping and demanding applications such as industrial tooling, transportation, electronics, small appliances, sports & leisure\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PA6 GF30 can be printed directly onto a clean build plate. For challenging prints, use Magigoo PA gluestick to improve adhesion." + +[filament:Ultrafuse PAHT-CF15] +inherits = Ultrafuse PA6 GF30 +filament_density = 1.23 +filament_notes = "Material Description\nPAHT CF15 is a high-performance 3D printing filament that opens new application fields in FFF printing. In parallel to its advanced mechanical properties, dimensional stability, and chemical resistance, it has very good processability. It works in any FFF printer with a hardened nozzle. In addition to that, it is compatible with water-soluble support material and HiPS, which allow printing complex geometries that work in challenging environments. PAHT CF15 has high heat resistance up to 130 °C and low moisture absorption.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PAHT-CF can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PC-ABS-FR] +inherits = Ultrafuse ABS +filament_colour = #505050 +filament_density = 1.17 +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_type = PC +min_fan_speed = 20 +max_fan_speed = 20 +bridge_fan_speed = 30 +disable_fan_first_layers = 4 +filament_notes = "Material Description\nUltrafuse® PC/ABS FR Black is a V-0 flame retardant blend of Polycarbonate and ABS – two of the most used thermoplastics for engineering & electrical applications. The combination of these two materials results in a premium material with a mix of the excellent mechanical properties of PC and the comparably low printing temperature of ABS. Combined with a halogen free flame retardant, parts printed with Ultrafuse® PC/ABS FR Black feature great tensile and impact strength, higher thermal resistance than ABS and can fulfill the requirements of the UL94 V-0 standard.\n\nPrinting Recommendations:\nApply Magigoo PC to a clean build plate to improve adhesion." + +[filament:Ultrafuse PET-CF15] +inherits = Ultrafuse PET +filament_density = 1.36 +filament_colour = #404040 +first_layer_temperature = 270 +temperature = 270 +first_layer_bed_temperature = 75 +bed_temperature = 75 +min_fan_speed = 60 +max_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +slowdown_below_layer_time = 15 +fan_below_layer_time = 30 +filament_notes = "Material Description\nPET CF15 is a Carbon Fiber reinforced PET which has precisely tuned material properties, for a wide range of technical applications. The filament is very strong and stiff and has high heat resistance. With its high dimensional stability and low abrasiveness, the filament offers an easy to print experience which allows direct printing on glass or a PEI sheet. It is compatible with HiPS for breakaway support and water soluble support and has an excellent surface finish.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nUltrafuse PET-CF15 can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion." + +[filament:Ultrafuse PLA] +inherits = *PLA* +filament_vendor = BASF +filament_density = 1.25 +full_fan_speed_layer = 3 +filament_notes = "Material Description\nPLA is one of the most used materials for 3D printing. Ultrafuse PLA is available in a wide range of colors. The glossy feel often attracts those who print display models or items for household use. Many appreciate the plant-based origin of this material. When properly cooled, PLA has a high maximum printing speed and sharp printed corners. Combining this with low warping of the print makes it a popular plastic for home printers, hobbyists, prototyping and schools.\n\nPrinting Recommendations:\nUltrafuse PLA can be printed directly onto a clean build plate." + +[filament:Ultrafuse PP] +inherits = Ultrafuse ABS +filament_density = 0.91 +filament_colour = #F0F0F0 +first_layer_temperature = 240 +temperature = 240 +first_layer_bed_temperature = 80 +bed_temperature = 70 +min_fan_speed = 100 +max_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +full_fan_speed_layer = 3 +fan_below_layer_time = 60 +slowdown_below_layer_time = 20 +min_print_speed = 10 +filament_type = PP +filament_max_volumetric_speed = 2.5 +filament_notes = "Material Description\nUltrafuse PP is high-performance thermoplastic with low density, high elasticity and high resistance to fatigue. The mechanical properties make it an ideal material for 3D-printing applications which have to endure high stress or strain. The filament has high chemical resistance and a high isolation value. PP is one of the most used materials in the world, due to its versatility and ability to engineer lightweight tough parts.\n\nPrinting Recommendations:\nApply PP tape or Magigoo PP adhesive to the buildplate for optimal adhesion." + +[filament:Ultrafuse PP-GF30] +inherits = Ultrafuse PP +filament_density = 1.07 +filament_colour = #404040 +first_layer_temperature = 260 +temperature = 250 +first_layer_bed_temperature = 90 +bed_temperature = 40 +min_fan_speed = 40 +max_fan_speed = 75 +fan_always_on = 1 +fan_below_layer_time = 30 +slowdown_below_layer_time = 15 +min_print_speed = 15 +filament_notes = "Ultrafuse PP GF30 is polypropylene, reinforced with 30% glass fiber content. The fibers in this compound are specially designed for 3D-printing filaments and are compatible with a wide range of standard FFF 3D-printers. The extreme stiffness makes this material highly suitable for demanding applications. Other key properties of PPGF30 are high heat resistance and improved UV-resistance. All these excellent properties make this filament highly suitable in an industrial environment.\n\nPrinting Recommendations:\nThis material contains fibers that have an abrasive effect on printer components. Use a hardened or Ruby nozzle with a diameter of 0.6 or larger for optimal performance and avoid damage to the nozzle.\n\nApply PP strapping tape or PPGF adhesive to a clean build plate for optimal adhesion." + +[filament:Ultrafuse TPC-45D] +inherits = *FLEX* +filament_vendor = BASF +extrusion_multiplier = 1 +filament_density = 1.15 +filament_colour = #0035EC +first_layer_temperature = 235 +temperature = 235 +first_layer_bed_temperature = 60 +bed_temperature = 60 +min_fan_speed = 10 +max_fan_speed = 50 +bridge_fan_speed = 80 +fan_below_layer_time = 30 +slowdown_below_layer_time = 15 +min_print_speed = 15 +fan_always_on = 1 +cooling = 1 +filament_max_volumetric_speed = 1.2 +filament_notes = "Material Description\nTPC 45D is a flexible, shore 45D, rubber-like Thermoplastic Copolyester Elastomer (TPE-C), which is derived from rapeseed oil and combines the best properties of elastomers (rubbers) and polyesters. The material delivers excellent adhesion in the Z-direction, meaning that the printed layers do not detach - even with extreme deformation.\n\nPrinting Recommendations:\nApply Magigoo Flex to a clean build plate to improve adhesion." + +[filament:Ultrafuse TPU-64D] +inherits = Ultrafuse TPC-45D +filament_density = 1.16 +first_layer_temperature = 230 +temperature = 225 +first_layer_bed_temperature = 40 +bed_temperature = 40 +min_fan_speed = 20 +max_fan_speed = 100 +filament_notes = "Material Description\nUltrafuse® TPU 64D is the hardest elastomer in BASF Forward AM’s flexible productline. The material shows a relatively high rigidity while maintaining a certain flexibility. This filament is the perfect match for industrial applications requiring rigid parts being resistant to impact, wear and tear. Due to its property profile, the material can be used as an alternative for parts made from ABS and rubbers. Ultrafuse® TPU 64D is easy to print on direct drive and bowden style printers and is compatible with soluble BVOH support to realize the most complex geometries.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse TPU-85A] +inherits = Ultrafuse TPU-64D +filament_density = 1.11 +first_layer_temperature = 225 +temperature = 220 +filament_notes = "Material Description\nUltrafuse® TPU 85A comes in its natural white color. Chemical properties (e.g. resistance against particular substances) and tolerance for solvents can be made available, if these factors are relevant for a specific application. Generally, these properties correspond to publicly available data on polyether based TPUs. This material is not FDA conform. Good flexibility at low temperature, good wear performance and good damping behavior are the key features of Ultrafuse® TPU 85A.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse TPU-95A] +inherits = Ultrafuse TPU-85A +filament_density = 1.14 +first_layer_temperature = 230 +temperature = 225 +filament_notes = "Material Description\nUltrafuse® TPU 95A comes with a well-balanced profile of flexibility and durability. On top of that, it allows for easier and faster printing then softer TPU grades. Parts printed with Ultrafuse® TPU 95A show a high elongation, good impact resistance, excellent layer adhesion and a good resistance to oils and common industrially used chemicals. Due to its good printing behavior, Ultrafuse® TPU 95A is a good choice for starting printing flexible materials on both direct drive and bowden style printers.\n\nPrinting Recommendations:\nUltrafuse TPU can be printed directly onto a clean build plate. A small amount of 3Dlac can make removal easier after printing." + +[filament:Ultrafuse rPET] +inherits = Ultrafuse PET +filament_density = 1.27 +filament_colour = #9DC5FF +first_layer_temperature = 235 +temperature = 235 +first_layer_bed_temperature = 80 +bed_temperature = 75 +min_fan_speed = 50 +max_fan_speed = 100 +fan_below_layer_time = 15 +filament_notes = "Material Description\nPET is mainly known by the well-known PET bottle material. This recycled has a natural transparent blueish look. It has excellent 3D printing properties and good mechanical characteristics." + +[filament:Ultrafuse Metal] +inherits = *ABSC* +filament_vendor = BASF +filament_density = 4.5 +extrusion_multiplier = 1.08 +first_layer_temperature = 250 +first_layer_bed_temperature = 100 +temperature = 250 +bed_temperature = 100 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 0 +cooling = 0 +fan_always_on = 0 +filament_max_volumetric_speed = 4 +filament_type = METAL +compatible_printers_condition = nozzle_diameter[0]>=0.4 +filament_colour = #FFFFFF + +[filament:Polymaker PC-Max] +inherits = *ABS* +filament_vendor = Polymaker +filament_cost = 77.3 +filament_density = 1.20 +filament_type = PC +bed_temperature = 115 +filament_colour = #FFF2EC +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 +bridge_fan_speed = 0 + +[filament:PrimaSelect PVA+] +inherits = *PLA* +filament_vendor = PrimaSelect +filament_cost = 122.1 +filament_density = 1.23 +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +temperature = 195 + +[filament:Prusa ABS] +inherits = *ABSC* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.08 +filament_spool_weight = 230 + +[filament:Prusa HIPS] +inherits = *Generic HIPS* +filament_vendor = Made for Prusa +first_layer_temperature = 220 +temperature = 220 + +[filament:Generic HIPS] +inherits = *ABS* +filament_vendor = Generic +filament_cost = 27.3 +filament_density = 1.04 +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 230 +max_fan_speed = 20 +min_fan_speed = 20 +temperature = 230 +compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) + +[filament:Prusa PETG] +inherits = *PET* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.27 +filament_spool_weight = 230 + +[filament:Verbatim PETG] +inherits = *PET* +filament_vendor = Verbatim +filament_cost = 27.90 +filament_density = 1.27 +filament_spool_weight = 235 + +[filament:Prusament PETG] +inherits = *PET* +filament_vendor = Prusa Polymers +first_layer_temperature = 240 +temperature = 250 +filament_cost = 36.29 +filament_density = 1.27 +filament_spool_weight = 201 +filament_type = PETG + +[filament:Prusa PLA] +inherits = *PLA* +filament_vendor = Made for Prusa +filament_cost = 27.82 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:Eolas Prints PLA] +inherits = *PLA* +filament_vendor = Eolas Prints +filament_cost = 23.50 +filament_density = 1.24 +filament_spool_weight = 0 +filament_colour = #4D9398 +temperature = 208 + +[filament:Eolas Prints PLA Matte] +inherits = Eolas Prints PLA +filament_cost = 25.50 +temperature = 212 + +[filament:Eolas Prints INGEO 850] +inherits = Eolas Prints PLA +filament_cost = 25.90 +temperature = 210 + +[filament:Eolas Prints INGEO 870] +inherits = Eolas Prints PLA +filament_cost = 25.90 +temperature = 215 +first_layer_bed_temperature = 68 +first_layer_temperature = 220 +bed_temperature = 65 + +[filament:Eolas Prints PETG] +inherits = *PET* +filament_vendor = Eolas Prints +filament_cost = 29.90 +filament_density = 1.27 +filament_spool_weight = 0 +filament_colour = #4D9398 +temperature = 240 +first_layer_bed_temperature = 85 +first_layer_temperature = 235 +bed_temperature = 90 + +[filament:Eolas Prints PETG - UV Resistant] +inherits = Eolas Prints PETG +filament_cost = 35.90 +temperature = 237 +first_layer_temperature = 232 + +[filament:Eolas Prints TPU 93A] +inherits = *FLEX* +filament_vendor = Eolas Prints +filament_cost = 34.99 +filament_density = 1.21 +filament_colour = #4D9398 +filament_max_volumetric_speed = 1.2 +temperature = 235 +first_layer_bed_temperature = 30 +bed_temperature = 30 +filament_retract_length = 0 +extrusion_multiplier = 1.16 + +[filament:Fiberlogy Easy PLA] +inherits = *PLA* +filament_vendor = Fiberlogy +filament_cost = 20 +filament_density = 1.24 +first_layer_temperature = 220 +temperature = 220 +filament_spool_weight = 330 + +[filament:Fiberlogy Easy PET-G] +inherits = *PET* +filament_vendor = Fiberlogy +filament_spool_weight = 330 +filament_cost = 20 +filament_density = 1.27 +first_layer_bed_temperature = 80 +bed_temperature = 80 +first_layer_temperature = 235 +temperature = 235 +min_fan_speed = 15 +max_fan_speed = 30 +bridge_fan_speed = 60 +disable_fan_first_layers = 5 +full_fan_speed_layer = 5 +slowdown_below_layer_time = 15 + +[filament:Fiberlogy ASA] +inherits = *ABS* +filament_vendor = Fiberlogy +filament_cost = 33 +filament_density = 1.07 +filament_spool_weight = 330 +fan_always_on = 0 +cooling = 1 +min_fan_speed = 10 +max_fan_speed = 15 +bridge_fan_speed = 30 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 260 +temperature = 260 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ASA +fan_below_layer_time = 30 +disable_fan_first_layers = 5 + +[filament:Fiberlogy Easy ABS] +inherits = Fiberlogy ASA +filament_cost = 22.67 +filament_density = 1.09 +fan_always_on = 0 +cooling = 1 +min_fan_speed = 10 +max_fan_speed = 15 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 250 +temperature = 250 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_type = ABS +fan_below_layer_time = 25 +disable_fan_first_layers = 5 + +[filament:Fiberlogy CPE HT] +inherits = *PET* +filament_vendor = Fiberlogy +filament_cost = 42.67 +filament_density = 1.18 +extrusion_multiplier = 0.98 +filament_spool_weight = 330 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 0 +max_fan_speed = 0 +bridge_fan_speed = 50 +min_print_speed = 15 +first_layer_temperature = 275 +temperature = 275 +first_layer_bed_temperature = 105 +bed_temperature = 105 +filament_type = CPE +fan_below_layer_time = 20 +slowdown_below_layer_time = 15 +disable_fan_first_layers = 5 + +[filament:Fiberlogy PCTG] +inherits = Fiberlogy CPE HT +filament_cost = 29.41 +filament_density = 1.23 +extrusion_multiplier = 1 +min_fan_speed = 10 +max_fan_speed = 15 +first_layer_temperature = 265 +temperature = 265 +first_layer_bed_temperature = 90 +bed_temperature = 90 +filament_type = PCTG + +[filament:Fiberlogy FiberFlex 40D] +inherits = *FLEX* +filament_vendor = Fiberlogy +fan_always_on = 1 +filament_max_volumetric_speed = 1.5 +extrusion_multiplier = 1.12 +first_layer_temperature = 230 +first_layer_bed_temperature = 60 +temperature = 230 +bed_temperature = 60 +bridge_fan_speed = 75 +min_fan_speed = 25 +max_fan_speed = 75 +filament_cost = 39.41 +filament_density = 1.16 +disable_fan_first_layers = 5 +full_fan_speed_layer = 5 +min_print_speed = 15 +cooling = 1 +filament_spool_weight = 330 + +[filament:Fiberlogy MattFlex 40D] +inherits = Fiberlogy FiberFlex 40D +filament_vendor = Fiberlogy +fan_always_on = 1 +filament_max_volumetric_speed = 1.35 +extrusion_multiplier = 1.1 +filament_cost = 49.11 + +[filament:Fiberlogy FiberFlex 30D] +inherits = Fiberlogy FiberFlex 40D +filament_max_volumetric_speed = 1.2 +extrusion_multiplier = 1.15 +first_layer_temperature = 240 +temperature = 240 +min_fan_speed = 25 +max_fan_speed = 60 +filament_density = 1.07 + +[filament:Fiberlogy FiberSatin] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 215 +temperature = 215 +extrusion_multiplier = 1 +filament_density = 1.2 +filament_cost = 32.35 + +[filament:Fiberlogy FiberSilk] +inherits = Fiberlogy FiberSatin +first_layer_temperature = 230 +temperature = 230 +extrusion_multiplier = 0.97 +filament_density = 1.22 +filament_cost = 32.35 + +[filament:Fiberlogy FiberWood] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 185 +temperature = 185 +extrusion_multiplier = 1 +filament_density = 1.23 +filament_cost = 38.66 + +[filament:Fiberlogy HD PLA] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 230 +temperature = 230 +extrusion_multiplier = 1 +filament_density = 1.24 +filament_cost = 30.59 + +[filament:Fiberlogy PLA Mineral] +inherits = Fiberlogy Easy PLA +first_layer_temperature = 195 +temperature = 190 +extrusion_multiplier = 0.98 +filament_density = 1.38 +filament_cost = 37.64 + +[filament:Fiberlogy Impact PLA] +inherits = Fiberlogy HD PLA +filament_density = 1.22 +filament_cost = 27.65 + +[filament:Fiberlogy Nylon PA12] +inherits = Fiberlogy ASA +filament_type = PA +filament_density = 1.01 +filament_cost = 48 +first_layer_bed_temperature = 105 +bed_temperature = 105 +first_layer_temperature = 265 +temperature = 265 +min_fan_speed = 10 +max_fan_speed = 15 +fan_below_layer_time = 20 +bridge_fan_speed = 30 +fan_always_on = 0 + +[filament:Fiberlogy Nylon PA12+CF15] +inherits = Fiberlogy Nylon PA12 +extrusion_multiplier = 0.97 +filament_density = 1.07 +filament_cost = 87.5 +first_layer_bed_temperature = 105 +bed_temperature = 105 +first_layer_temperature = 265 +temperature = 265 +min_fan_speed = 10 +max_fan_speed = 15 +fan_below_layer_time = 20 +bridge_fan_speed = 30 +fan_always_on = 0 + +[filament:Fiberlogy Nylon PA12+GF15] +inherits = Fiberlogy Nylon PA12+CF15 +filament_density = 1.13 + +[filament:Fiberlogy PP] +inherits = *ABS* +filament_vendor = Fiberlogy +filament_cost = 36.67 +filament_density = 1.05 +extrusion_multiplier = 1.05 +filament_spool_weight = 330 +fan_always_on = 1 +cooling = 1 +min_fan_speed = 0 +max_fan_speed = 25 +bridge_fan_speed = 70 +min_print_speed = 15 +slowdown_below_layer_time = 15 +first_layer_temperature = 245 +temperature = 245 +first_layer_bed_temperature = 0 +bed_temperature = 0 +filament_type = PP +fan_below_layer_time = 100 +disable_fan_first_layers = 5 +filament_max_volumetric_speed = 5 + +[filament:Filament PM PLA] +inherits = *PLA* +filament_vendor = Filament PM +filament_cost = 27.82 +filament_density = 1.24 +filament_spool_weight = 230 + +[filament:AmazonBasics PLA] +inherits = *PLA* +filament_vendor = AmazonBasics +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Overture PLA] +inherits = *PLA* +filament_vendor = Overture +filament_cost = 22 +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Hatchbox PLA] +inherits = *PLA* +filament_vendor = Hatchbox +filament_cost = 25.4 +filament_density = 1.27 +filament_spool_weight = 245 + +[filament:Esun PLA] +inherits = *PLA* +filament_vendor = Esun +filament_cost = 25.4 +filament_density = 1.24 +filament_spool_weight = 265 + +[filament:Das Filament PLA] +inherits = *PLA* +filament_vendor = Das Filament +filament_cost = 25.4 +filament_density = 1.24 + +[filament:EUMAKERS PLA] +inherits = *PLA* +filament_vendor = EUMAKERS +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Floreon3D PLA] +inherits = *PLA* +filament_vendor = Floreon3D +filament_cost = 25.4 +filament_density = 1.24 + +[filament:Prusament PLA] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +filament_cost = 36.29 +filament_density = 1.24 +filament_spool_weight = 201 +filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" + +[filament:Prusament PVB] +inherits = *PLA* +filament_vendor = Prusa Polymers +temperature = 215 +bed_temperature = 75 +first_layer_bed_temperature = 75 +filament_cost = 60.48 +filament_density = 1.09 +filament_spool_weight = 201 +filament_max_volumetric_speed = 8 +filament_type = PVB +filament_soluble = 1 +filament_colour = #FFFF6F +slowdown_below_layer_time = 20 + +[filament:Fillamentum Flexfill 98A] +inherits = *FLEX* +filament_vendor = Fillamentum +filament_cost = 82.26 +filament_density = 1.23 +filament_spool_weight = 230 +extrusion_multiplier = 1.1 +filament_max_volumetric_speed = 1.5 +fan_always_on = 1 +cooling = 0 +max_fan_speed = 60 +min_fan_speed = 60 +disable_fan_first_layers = 4 +full_fan_speed_layer = 6 + +[filament:ColorFabb VarioShore TPU] +inherits = Fillamentum Flexfill 98A +filament_vendor = ColorFabb +filament_colour = #BBBBBB +filament_cost = 71.35 +filament_density = 1.22 +filament_spool_weight = 0 +extrusion_multiplier = 0.85 +first_layer_temperature = 220 +temperature = 220 + +[filament:Taulman Bridge] +inherits = *common* +filament_vendor = Taulman +filament_cost = 40 +filament_density = 1.13 +bed_temperature = 110 +bridge_fan_speed = 40 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_soluble = 0 +filament_type = PA +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 260 +max_fan_speed = 0 +min_fan_speed = 0 +filament_max_volumetric_speed = 6 + +[filament:Fillamentum Nylon FX256] +inherits = *common* +filament_vendor = Fillamentum +filament_cost = 56.99 +filament_density = 1.01 +filament_spool_weight = 230 +bed_temperature = 90 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 6 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 6 +filament_soluble = 0 +filament_type = PA +first_layer_bed_temperature = 90 +first_layer_temperature = 250 +max_fan_speed = 0 +min_fan_speed = 0 +temperature = 250 + +[filament:Fiberthree F3 PA Pure Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_cost = 200.84 +filament_density = 1.2 +bed_temperature = 90 +first_layer_bed_temperature = 90 +first_layer_temperature = 285 +temperature = 285 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_soluble = 0 +filament_type = PA +max_fan_speed = 20 +min_fan_speed = 20 + +[filament:Fiberthree F3 PA-CF Pro] +inherits = *common* +filament_vendor = Fiberthree +filament_cost = 208.1 +filament_density = 1.25 +bed_temperature = 90 +first_layer_bed_temperature = 90 +first_layer_temperature = 285 +temperature = 285 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +min_print_speed = 15 +slowdown_below_layer_time = 10 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_soluble = 0 +filament_type = PA +max_fan_speed = 0 +min_fan_speed = 0 + +[filament:Fiberthree F3 PA-GF Pro] +inherits = Fiberthree F3 PA-CF Pro +filament_vendor = Fiberthree +filament_cost = 205.68 +filament_density = 1.27 +fan_always_on = 1 +max_fan_speed = 15 +min_fan_speed = 15 + +[filament:Taulman T-Glase] +inherits = *PET* +filament_vendor = Taulman +filament_cost = 40 +filament_density = 1.27 +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 + +[filament:Verbatim PLA] +inherits = *PLA* +filament_vendor = Verbatim +filament_cost = 42.99 +filament_density = 1.24 +filament_spool_weight = 235 + +[filament:Verbatim BVOH] +inherits = *common* +filament_vendor = Verbatim +filament_cost = 193.58 +filament_density = 1.14 +filament_spool_weight = 235 +bed_temperature = 60 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = PVA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +filament_vendor = Verbatim +filament_cost = 72 +filament_density = 0.89 +filament_spool_weight = 235 +bed_temperature = 100 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_type = PP +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 220 + +[filament:FormFutura Centaur PP] +inherits = *common* +filament_vendor = FormFutura +filament_cost = 70 +filament_density = 0.89 +filament_spool_weight = 212 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1.05 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 4 +filament_type = PP +first_layer_bed_temperature = 85 +bed_temperature = 85 +first_layer_temperature = 235 +max_fan_speed = 70 +min_fan_speed = 70 +temperature = 235 +filament_wipe = 0 +filament_retract_lift = 0 \ No newline at end of file diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 7793a2bd48..0d9888d404 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -36,10 +36,10 @@ static const std::string MODEL_PREFIX = "model:"; // are phased out, then we will revert to the original name. //static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; -// url to folder with profile archive zip -// TODO: Uncomment and delete 2nd when we have archive online -//static const std::string PROFILE_ARCHIVE_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Archive/Archive.zip"; -static const std::string PROFILE_ARCHIVE_URL = "https://raw.githubusercontent.com/kocikdav/PrusaSlicer-settings/master/live/Bundle/Archive.zip"; +// Url to index archive zip that contains latest indicies +static const std::string INDEX_ARCHIVE_URL= "https://files.prusa3d.com/wp-content/uploads/repository/vendor_indices.zip"; +// Url to folder with vendor profile files. Used when downloading new profiles that are not in resources folder. +static const std::string PROFILE_FOLDER_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; @@ -671,9 +671,24 @@ std::string AppConfig::version_check_url() const return from_settings.empty() ? VERSION_CHECK_URL : from_settings; } -const std::string& AppConfig::profile_archive_url() const +std::string AppConfig::index_archive_url() const { - return PROFILE_ARCHIVE_URL; +#if 0 + // this code is for debug & testing purposes only - changed url wont get trough inner checks anyway. + auto from_settings = get("index_archive_url"); + return from_settings.empty() ? INDEX_ARCHIVE_URL : from_settings; +#endif + return INDEX_ARCHIVE_URL; +} + +std::string AppConfig::profile_folder_url() const +{ +#if 0 + // this code is for debug & testing purposes only - changed url wont get trough inner checks anyway. + auto from_settings = get("profile_folder_url"); + return from_settings.empty() ? PROFILE_FOLDER_URL : from_settings; +#endif + return PROFILE_FOLDER_URL; } bool AppConfig::exists() diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 1ecd8cd643..78a7065d90 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -139,8 +139,11 @@ public: // Get the Slic3r version check url. // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. std::string version_check_url() const; - // Get the Slic3r url to vendor profile archive zip. - const std::string& profile_archive_url() const; + // Get the Slic3r url to vendor index archive zip. + std::string index_archive_url() const; + // Get the Slic3r url to folder with vendor profile files. + std::string profile_folder_url() const; + // Returns the original Slic3r version found in the ini file before it was overwritten // by the current version diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e9d3b09ffd..1f5fbc6b24 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2146,7 +2146,7 @@ namespace PresetUtils { if (! res.empty() && !fs::exists(fs::path(vendor_folder + res)) && !fs::exists(fs::path(rsrc_folder + res)) - && (fs::exists(fs::path(cache_folder + res)))) + && !fs::exists(fs::path(cache_folder + res))) return false; } } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index ed063415f0..81de207167 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1308,13 +1308,22 @@ std::pair PresetBundle::load_configbundle( try { pt::read_ini(ifs, tree); } catch (const boost::property_tree::ini_parser::ini_parser_error &err) { - throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); + // This throw was uncatched. While other similar problems later are just returning empty pair. + //throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); + BOOST_LOG_TRIVIAL(error) << format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str(); + return std::make_pair(PresetsConfigSubstitutions{}, 0); } } const VendorProfile *vendor_profile = nullptr; if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) { - auto vp = VendorProfile::from_ini(tree, path); + VendorProfile vp; + try { + vp = VendorProfile::from_ini(tree, path); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Failed to open profile file.") % path; + return std::make_pair(PresetsConfigSubstitutions{}, 0); + } if (vp.models.size() == 0 && !vp.templates_profile) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; return std::make_pair(PresetsConfigSubstitutions{}, 0); @@ -1360,7 +1369,7 @@ std::pair PresetBundle::load_configbundle( } else if (boost::starts_with(section.first, "filament:")) { presets = &this->filaments; preset_name = section.first.substr(9); - if (vendor_profile->templates_profile) { + if (vendor_profile && vendor_profile->templates_profile) { preset_name += " @Template"; } } else if (boost::starts_with(section.first, "sla_print:")) { diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 2de72ad024..6ba866ac14 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -141,6 +141,8 @@ BundleMap BundleMap::load() typedef std::pair DirData; std::vector dir_list { {vendor_dir, BundleLocation::IN_VENDOR}, {archive_dir, BundleLocation::IN_ARCHIVE}, {rsrc_vendor_dir, BundleLocation::IN_RESOURCES} }; for ( auto dir : dir_list) { + if (!fs::exists(dir.first)) + continue; for (const auto &dir_entry : boost::filesystem::directory_iterator(dir.first)) { if (Slic3r::is_ini_file(dir_entry)) { std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part @@ -226,26 +228,30 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt bool is_variants = false; + const fs::path vendor_dir_path = (fs::path(Slic3r::data_dir()) / "vendor").make_preferred(); + const fs::path cache_dir_path = (fs::path(Slic3r::data_dir()) / "cache").make_preferred(); + const fs::path rsrc_dir_path = (fs::path(resources_dir()) / "profiles").make_preferred(); + 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; + auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width) { + bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); + bitmap_width = bitmap.GetWidth(); }; bool found = false; - for (const std::string& res : { Slic3r::data_dir() + "/vendor/" + vendor.id + "/", Slic3r::resources_dir() + "/profiles/" + vendor.id + "/", Slic3r::data_dir() + "/cache/" + vendor.id + "/" } ) { - if (load_bitmap(GUI::from_u8(res + "/" + model.thumbnail), bitmap, bitmap_width)) { - found = true; - break; - } + for (const fs::path& res : { rsrc_dir_path / vendor.id / model.thumbnail + , vendor_dir_path / vendor.id / model.thumbnail + , cache_dir_path / vendor.id / model.thumbnail }) + { + if (!fs::exists(res)) + continue; + load_bitmap(GUI::from_u8(res.string()), bitmap, bitmap_width); + found = true; + break; } if (!found) { @@ -256,7 +262,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); } - auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + wxStaticText* 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); @@ -269,7 +275,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt titles.push_back(title); - auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); + wxStaticBitmap* bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); bitmaps.push_back(bitmap_widget); auto *variants_panel = new wxPanel(this); @@ -292,7 +298,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt is_variants = true; } - auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); + Checkbox* 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); @@ -1616,7 +1622,7 @@ PageVendors::PageVendors(ConfigWizard *parent) for (const auto &pair : wizard_p()->bundles) { const VendorProfile *vendor = pair.second.vendor_profile; if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - if (vendor->templates_profile) + if (vendor && vendor->templates_profile) continue; auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); @@ -2498,7 +2504,7 @@ void ConfigWizard::priv::update_materials(Technology technology) } } // template filament bundle has no printers - filament would be never added - if(pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) + if(pair.second.vendor_profile&& pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end()) { if (!filaments.containts(&filament)) { filaments.push(&filament); diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 0c3fed13f9..17c9fb25fb 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -369,11 +369,11 @@ struct Materials if (((printer == nullptr && printer_name == PageMaterials::EMPTY) || (printer != nullptr && is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor)))) && (type.empty() || get_type(preset) == type) && (vendor.empty() || get_vendor(preset) == vendor) && - !prst.vendor->templates_profile) { + prst.vendor && !prst.vendor->templates_profile) { cb(preset); } - else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor->templates_profile && + else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor && prst.vendor->templates_profile && (type.empty() || get_type(preset) == type) && (vendor.empty() || get_vendor(preset) == vendor)) { cb(preset); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6975006a62..3e561d04e5 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1,3388 +1,3394 @@ -#include "libslic3r/Technologies.hpp" -#include "GUI_App.hpp" -#include "GUI_Init.hpp" -#include "GUI_ObjectList.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "GUI_Factories.hpp" -#include "format.hpp" -#include "I18N.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libslic3r/Utils.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/I18N.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Color.hpp" - -#include "GUI.hpp" -#include "GUI_Utils.hpp" -#include "3DScene.hpp" -#include "MainFrame.hpp" -#include "Plater.hpp" -#include "GLCanvas3D.hpp" - -#include "../Utils/PresetUpdater.hpp" -#include "../Utils/PrintHost.hpp" -#include "../Utils/Process.hpp" -#include "../Utils/MacDarkMode.hpp" -#include "../Utils/AppUpdater.hpp" -#include "../Utils/WinRegistry.hpp" -#include "slic3r/Config/Snapshot.hpp" -#include "ConfigSnapshotDialog.hpp" -#include "FirmwareDialog.hpp" -#include "Preferences.hpp" -#include "Tab.hpp" -#include "SysInfoDialog.hpp" -#include "KBShortcutsDialog.hpp" -#include "UpdateDialogs.hpp" -#include "Mouse3DController.hpp" -#include "RemovableDriveManager.hpp" -#include "InstanceCheck.hpp" -#include "NotificationManager.hpp" -#include "UnsavedChangesDialog.hpp" -#include "SavePresetDialog.hpp" -#include "PrintHostDialogs.hpp" -#include "DesktopIntegrationDialog.hpp" -#include "SendSystemInfoDialog.hpp" -#include "Downloader.hpp" - -#include "BitmapCache.hpp" -#include "Notebook.hpp" - -#ifdef __WXMSW__ -#include -#include -#ifdef _MSW_DARK_MODE -#include -#endif // _MSW_DARK_MODE -#endif -#ifdef _WIN32 -#include -#endif - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG -#include -#include -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG - -// Needed for forcing menu icons back under gtk2 and gtk3 -#if defined(__WXGTK20__) || defined(__WXGTK3__) - #include -#endif - -using namespace std::literals; - -namespace Slic3r { -namespace GUI { - -class MainFrame; - -class SplashScreen : public wxSplashScreen -{ -public: - SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition) - : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize, -#ifdef __APPLE__ - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP -#else - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR -#endif // !__APPLE__ - ) - { - wxASSERT(bitmap.IsOk()); - -// int init_dpi = get_dpi_for_window(this); - this->SetPosition(pos); - // The size of the SplashScreen can be hanged after its moving to another display - // So, update it from a bitmap size - this->SetClientSize(bitmap.GetWidth(), bitmap.GetHeight()); - this->CenterOnScreen(); -// int new_dpi = get_dpi_for_window(this); - -// m_scale = (float)(new_dpi) / (float)(init_dpi); - m_main_bitmap = bitmap; - -// scale_bitmap(m_main_bitmap, m_scale); - - // init constant texts and scale fonts - init_constant_text(); - - // this font will be used for the action string - m_action_font = m_constant_text.credits_font.Bold(); - - // draw logo and constant info text - Decorate(m_main_bitmap); - } - - void SetText(const wxString& text) - { - set_bitmap(m_main_bitmap); - if (!text.empty()) { - wxBitmap bitmap(m_main_bitmap); - - wxMemoryDC memDC; - memDC.SelectObject(bitmap); - - memDC.SetFont(m_action_font); - memDC.SetTextForeground(wxColour(237, 107, 33)); - memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position); - - memDC.SelectObject(wxNullBitmap); - set_bitmap(bitmap); -#ifdef __WXOSX__ - // without this code splash screen wouldn't be updated under OSX - wxYield(); -#endif - } - } - - static wxBitmap MakeBitmap(wxBitmap bmp) - { - if (!bmp.IsOk()) - return wxNullBitmap; - - // create dark grey background for the splashscreen - // It will be 5/3 of the weight of the bitmap - int width = lround((double)5 / 3 * bmp.GetWidth()); - int height = bmp.GetHeight(); - - wxImage image(width, height); - unsigned char* imgdata_ = image.GetData(); - for (int i = 0; i < width * height; ++i) { - *imgdata_++ = 51; - *imgdata_++ = 51; - *imgdata_++ = 51; - } - - wxBitmap new_bmp(image); - - wxMemoryDC memDC; - memDC.SelectObject(new_bmp); - memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true); - - return new_bmp; - } - - void Decorate(wxBitmap& bmp) - { - if (!bmp.IsOk()) - return; - - // draw text to the box at the left of the splashscreen. - // this box will be 2/5 of the weight of the bitmap, and be at the left. - int width = lround(bmp.GetWidth() * 0.4); - - // load bitmap for logo - BitmapCache bmp_cache; - int logo_size = lround(width * 0.25); - wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size); - - wxCoord margin = int(m_scale * 20); - - wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight())); - banner_rect.Deflate(margin, 2 * margin); - - // use a memory DC to draw directly onto the bitmap - wxMemoryDC memDc(bmp); - - // draw logo - memDc.DrawBitmap(logo_bmp, margin, margin, true); - - // draw the (white) labels inside of our black box (at the left of the splashscreen) - memDc.SetTextForeground(wxColour(255, 255, 255)); - - memDc.SetFont(m_constant_text.title_font); - memDc.DrawLabel(m_constant_text.title, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); - - int title_height = memDc.GetTextExtent(m_constant_text.title).GetY(); - banner_rect.SetTop(banner_rect.GetTop() + title_height); - banner_rect.SetHeight(banner_rect.GetHeight() - title_height); - - memDc.SetFont(m_constant_text.version_font); - memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); - int version_height = memDc.GetTextExtent(m_constant_text.version).GetY(); - - memDc.SetFont(m_constant_text.credits_font); - memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); - int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY(); - int text_height = memDc.GetTextExtent("text").GetY(); - - // calculate position for the dynamic text - int logo_and_header_height = margin + logo_size + title_height + version_height; - m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height); - } - -private: - wxBitmap m_main_bitmap; - wxFont m_action_font; - int m_action_line_y_position; - float m_scale {1.0}; - - struct ConstantText - { - wxString title; - wxString version; - wxString credits; - - wxFont title_font; - wxFont version_font; - wxFont credits_font; - - void init(wxFont init_font) - { - // title - title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; - - // dynamically get the version to display - version = _L("Version") + " " + std::string(SLIC3R_VERSION); - - // credits infornation - credits = title + " " + - _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + - _L("Developed by Prusa Research.") + "\n\n" + - title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + ".\n\n" + - _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + - _L("Artwork model by Creative Tools"); - - title_font = version_font = credits_font = init_font; - } - } - m_constant_text; - - void init_constant_text() - { - m_constant_text.init(get_default_font(this)); - - // As default we use a system font for current display. - // Scale fonts in respect to banner width - - int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins - - float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX(); - scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale); - - float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX(); - scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale); - - // The width of the credits information string doesn't respect to the banner width some times. - // So, scale credits_font in the respect to the longest string width - int longest_string_width = word_wrap_string(m_constant_text.credits); - float font_scale = (float)text_banner_width / longest_string_width; - scale_font(m_constant_text.credits_font, font_scale); - } - - void set_bitmap(wxBitmap& bmp) - { - m_window->SetBitmap(bmp); - m_window->Refresh(); - m_window->Update(); - } - - void scale_bitmap(wxBitmap& bmp, float scale) - { - if (scale == 1.0) - return; - - wxImage image = bmp.ConvertToImage(); - if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) - return; - - int width = int(scale * image.GetWidth()); - int height = int(scale * image.GetHeight()); - image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); - - bmp = wxBitmap(std::move(image)); - } - - void scale_font(wxFont& font, float scale) - { -#ifdef __WXMSW__ - // Workaround for the font scaling in respect to the current active display, - // not for the primary display, as it's implemented in Font.cpp - // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp - // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) - wxNativeFontInfo nfi= *font.GetNativeFontInfo(); - float pointSizeNew = wxDisplay(this).GetScaleFactor() * scale * font.GetPointSize(); - nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); - nfi.pointSize = pointSizeNew; - font = wxFont(nfi); -#else - font.Scale(scale); -#endif //__WXMSW__ - } - - // wrap a string for the strings no longer then 55 symbols - // return extent of the longest string - int word_wrap_string(wxString& input) - { - size_t line_len = 55;// count of symbols in one line - int idx = -1; - size_t cur_len = 0; - - wxString longest_sub_string; - auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) { - if (cur_len > longest_sub_str.Len()) - longest_sub_str = input.SubString(i - cur_len + 1, i); - }; - - for (size_t i = 0; i < input.Len(); i++) - { - cur_len++; - if (input[i] == ' ') - idx = i; - if (input[i] == '\n') - { - get_longest_sub_string(longest_sub_string, cur_len, i); - idx = -1; - cur_len = 0; - } - if (cur_len >= line_len && idx >= 0) - { - get_longest_sub_string(longest_sub_string, cur_len, i); - input[idx] = '\n'; - cur_len = i - static_cast(idx); - } - } - - return GetTextExtent(longest_sub_string).GetX(); - } -}; - - -#ifdef __linux__ -bool static check_old_linux_datadir(const wxString& app_name) { - // If we are on Linux and the datadir does not exist yet, look into the old - // location where the datadir was before version 2.3. If we find it there, - // tell the user that he might wanna migrate to the new location. - // (https://github.com/prusa3d/PrusaSlicer/issues/2911) - // To be precise, the datadir should exist, it is created when single instance - // lock happens. Instead of checking for existence, check the contents. - - namespace fs = boost::filesystem; - - std::string new_path = Slic3r::data_dir(); - - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - std::string default_path = (dir + "/" + app_name).ToUTF8().data(); - - if (new_path != default_path) { - // This happens when the user specifies a custom --datadir. - // Do not show anything in that case. - return true; - } - - fs::path data_dir = fs::path(new_path); - if (! fs::is_directory(data_dir)) - return true; // This should not happen. - - int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator()); - - if (file_count <= 1) { // just cache dir with an instance lock - std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data(); - - if (fs::is_directory(old_path)) { - wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration " - "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n" - "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, " - "an old %1% configuration directory was detected in \n%3%.\n\n" - "Consider moving the contents of the old directory to the new location in order to access " - "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old " - "location again.\n\n" - "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); - wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str()); - RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); - dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application")); - if (dlg.ShowModal() != wxID_NO) - return false; - } - } else { - // If the new directory exists, be silent. The user likely already saw the message. - } - return true; -} -#endif - -#ifdef _WIN32 -#if 0 // External Updater is replaced with AppUpdater.cpp -static bool run_updater_win() -{ - // find updater exe - boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe"; - // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst - std::string msg; - bool res = create_process(path_updater, L"/silent", msg); - if (!res) - BOOST_LOG_TRIVIAL(error) << msg; - return res; -} -#endif // 0 -#endif // _WIN32 - -struct FileWildcards { - std::string_view title; - std::vector file_extensions; -}; - - - -static const FileWildcards file_wildcards_by_type[FT_SIZE] = { - /* FT_STL */ { "STL files"sv, { ".stl"sv } }, - /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, - /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, - /* FT_STEP */ { "STEP files"sv, { ".stp"sv, ".step"sv } }, - /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, - /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, - /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, - /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv, ".step"sv, ".stp"sv } }, - /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, - /* FT_FONTS */ { "Font files"sv, { ".ttc"sv, ".ttf"sv } }, - /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, - - /* FT_INI */ { "INI files"sv, { ".ini"sv } }, - /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, - - /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, - - /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, -}; - -#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR -wxString file_wildcards(FileType file_type) -{ - const FileWildcards& data = file_wildcards_by_type[file_type]; - std::string title; - std::string mask; - - // Generate cumulative first item - for (const std::string_view& ext : data.file_extensions) { - if (title.empty()) { - title = "*"; - title += ext; - mask = title; - } - else { - title += ", *"; - title += ext; - mask += ";*"; - mask += ext; - } - mask += ";*"; - mask += boost::to_upper_copy(std::string(ext)); - } - - wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); - - // Adds an item for each of the extensions - if (data.file_extensions.size() > 1) { - for (const std::string_view& ext : data.file_extensions) { - title = "*"; - title += ext; - ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title); - } - } - - return ret; -} -#else -// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. -// The function accepts a custom extension parameter. If the parameter is provided, the custom extension -// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips -// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template). -wxString file_wildcards(FileType file_type, const std::string &custom_extension) -{ - const FileWildcards& data = file_wildcards_by_type[file_type]; - std::string title; - std::string mask; - std::string custom_ext_lower; - - if (! custom_extension.empty()) { - // Generate an extension into the title mask and into the list of extensions. - custom_ext_lower = boost::to_lower_copy(custom_extension); - const std::string custom_ext_upper = boost::to_upper_copy(custom_extension); - if (custom_ext_lower == custom_extension) { - // Add a lower case version. - title = std::string("*") + custom_ext_lower; - mask = title; - // Add an upper case version. - mask += ";*"; - mask += custom_ext_upper; - } else if (custom_ext_upper == custom_extension) { - // Add an upper case version. - title = std::string("*") + custom_ext_upper; - mask = title; - // Add a lower case version. - mask += ";*"; - mask += custom_ext_lower; - } else { - // Add the mixed case version only. - title = std::string("*") + custom_extension; - mask = title; - } - } - - for (const std::string_view &ext : data.file_extensions) - // Only add an extension if it was not added first as the custom extension. - if (ext != custom_ext_lower) { - if (title.empty()) { - title = "*"; - title += ext; - mask = title; - } else { - title += ", *"; - title += ext; - mask += ";*"; - mask += ext; - } - mask += ";*"; - mask += boost::to_upper_copy(std::string(ext)); - } - - return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); -} -#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR - -static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } - -#ifdef WIN32 -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) -static void register_win32_dpi_event() -{ - enum { WM_DPICHANGED_ = 0x02e0 }; - - wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { - const int dpi = wParam & 0xffff; - const auto rect = reinterpret_cast(lParam); - const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right)); - - DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect); - win->GetEventHandler()->AddPendingEvent(evt); - - return true; - }); -} -#endif // !wxVERSION_EQUAL_OR_GREATER_THAN - -static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; - -static void register_win32_device_notification_event() -{ - wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. - auto main_frame = dynamic_cast(win); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); - if (plater == nullptr) - // Maybe some other top level window like a dialog or maybe a pop-up menu? - return true; - PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; - switch (wParam) { - case DBT_DEVICEARRIVAL: - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); - else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { - PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; -// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) { -// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name); - if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name))); - } - break; - case DBT_DEVICEREMOVECOMPLETE: - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); - else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { - PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; -// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) -// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name); - if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name))); - } - break; - default: - break; - } - return true; - }); - - wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. - auto main_frame = dynamic_cast(win); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); - if (plater == nullptr) - // Maybe some other top level window like a dialog or maybe a pop-up menu? - return true; - wchar_t sPath[MAX_PATH]; - if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) { - struct _ITEMIDLIST* pidl = *reinterpret_cast(wParam); - if (! SHGetPathFromIDList(pidl, sPath)) { - BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed"; - return false; - } - } - switch (lParam) { - case SHCNE_MEDIAINSERTED: - { - //printf("SHCNE_MEDIAINSERTED %S\n", sPath); - plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); - break; - } - case SHCNE_MEDIAREMOVED: - { - //printf("SHCNE_MEDIAREMOVED %S\n", sPath); - plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); - break; - } - default: -// printf("Unknown\n"); - break; - } - return true; - }); - - wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - auto main_frame = dynamic_cast(Slic3r::GUI::find_toplevel_parent(win)); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); -// if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) { - if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) { - RAWINPUT raw; - UINT rawSize = sizeof(RAWINPUT); - ::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); - if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid)) - return true; - } - return false; - }); - - wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - COPYDATASTRUCT* copy_data_structure = { 0 }; - copy_data_structure = (COPYDATASTRUCT*)lParam; - if (copy_data_structure->dwData == 1) { - LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData; - Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments)); - } - return true; - }); -} -#endif // WIN32 - -static void generic_exception_handle() -{ - // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage - // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0 - // - // wxLogError typically goes around exception handling and display an error dialog some time - // after an error is logged even if exception handling and OnExceptionInMainLoop() take place. - // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates - // errors if multiple have been collected and displays just one error message for all of them. - // Otherwise we would get multiple error messages for one missing png, for example. - // - // If a custom error message window (or some other solution) were to be used, it would be necessary - // to turn off wxLogError() usage in wx APIs, most notably in wxImage - // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a - - try { - throw; - } catch (const std::bad_alloc& ex) { - // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) - // and terminate the app so it is at least certain to happen now. - wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. " - "If you are sure you have enough RAM on your system, this may also be a bug and we would " - "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME); - wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR); - BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); - std::terminate(); - } catch (const boost::io::bad_format_string& ex) { - wxString errmsg = _L("PrusaSlicer has encountered a localization error. " - "Please report to PrusaSlicer team, what language was active and in which scenario " - "this issue happened. Thank you.\n\nThe application will now terminate."); - wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR); - BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); - std::terminate(); - throw; - } catch (const std::exception& ex) { - wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what())); - BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); - throw; - } -} - -void GUI_App::post_init() -{ - assert(initialized()); - if (! this->initialized()) - throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); - - if (this->is_gcode_viewer()) { - if (! this->init_params->input_files.empty()) - this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); - } - else if (this->init_params->start_downloader) { - start_download(this->init_params->download_url); - } else { - if (! this->init_params->preset_substitutions.empty()) - show_substitutions_info(this->init_params->preset_substitutions); - -#if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - this->gui->mainframe->load_config(m_print_config); -#endif - if (! this->init_params->load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - this->mainframe->load_config_file(this->init_params->load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (!this->init_params->input_files.empty()) { - const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); - if (!res.empty() && this->init_params->input_files.size() == 1) { - // Update application titlebar when opening a project file - const std::string& filename = this->init_params->input_files.front(); - if (boost::algorithm::iends_with(filename, ".amf") || - boost::algorithm::iends_with(filename, ".amf.xml") || - boost::algorithm::iends_with(filename, ".3mf")) - this->plater()->set_project_filename(from_u8(filename)); - } - if (this->init_params->delete_after_load) { - for (const std::string& p : this->init_params->input_files) { - boost::system::error_code ec; - boost::filesystem::remove(boost::filesystem::path(p), ec); - if (ec) { - BOOST_LOG_TRIVIAL(error) << ec.message(); - } - } - } - } - if (! this->init_params->extra_config.empty()) - this->mainframe->load_config(this->init_params->extra_config); - } - - // show "Did you know" notification - if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) - plater_->get_notification_manager()->push_hint_notification(true); - - // The extra CallAfter() is needed because of Mac, where this is the only way - // to popup a modal dialog on start without screwing combo boxes. - // This is ugly but I honestly found no better way to do it. - // Neither wxShowEvent nor wxWindowCreateEvent work reliably. - if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. - if (! this->check_updates(false)) - // Configuration is not compatible and reconfigure was refused by the user. Application is closing. - return; - CallAfter([this] { - // preset_updater->sync downloads profile updates on background so it must begin after config wizard finished. - bool cw_showed = this->config_wizard_startup(); - this->preset_updater->sync(preset_bundle); - this->app_version_check(false); - if (! cw_showed) { - // The CallAfter is needed as well, without it, GL extensions did not show. - // Also, we only want to show this when the wizard does not, so the new user - // sees something else than "we want something" on the first start. - show_send_system_info_dialog_if_needed(); - } - }); - } - - // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini. - app_config->set("version", SLIC3R_VERSION); - app_config->save(); - -#ifdef _WIN32 - // Sets window property to mainframe so other instances can indentify it. - OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); -#endif //WIN32 -} - -IMPLEMENT_APP(GUI_App) - -GUI_App::GUI_App(EAppMode mode) - : wxApp() - , m_app_mode(mode) - , m_em_unit(10) - , m_imgui(new ImGuiWrapper()) - , m_removable_drive_manager(std::make_unique()) - , m_other_instance_message_handler(std::make_unique()) - , m_downloader(std::make_unique()) -{ - //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp - this->init_app_config(); - // init app downloader after path to datadir is set - m_app_updater = std::make_unique(); -} - -GUI_App::~GUI_App() -{ - if (app_config != nullptr) - delete app_config; - - if (preset_bundle != nullptr) - delete preset_bundle; - - if (preset_updater != nullptr) - delete preset_updater; -} - -// If formatted for github, plaintext with OpenGL extensions enclosed into
. -// Otherwise HTML formatted for the system info dialog. -std::string GUI_App::get_gl_info(bool for_github) -{ - return OpenGLManager::get_gl_info().to_string(for_github); -} - -wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) -{ -#if ENABLE_GL_CORE_PROFILE -#if ENABLE_OPENGL_DEBUG_OPTION - return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0), - init_params != nullptr ? init_params->opengl_debug : false); -#else - return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0)); -#endif // ENABLE_OPENGL_DEBUG_OPTION -#else - return m_opengl_mgr.init_glcontext(canvas); -#endif // ENABLE_GL_CORE_PROFILE -} - -bool GUI_App::init_opengl() -{ -#ifdef __linux__ - bool status = m_opengl_mgr.init_gl(); - m_opengl_initialized = true; - return status; -#else - return m_opengl_mgr.init_gl(); -#endif -} - -// gets path to PrusaSlicer.ini, returns semver from first line comment -static boost::optional parse_semver_from_ini(std::string path) -{ - std::ifstream stream(path); - std::stringstream buffer; - buffer << stream.rdbuf(); - std::string body = buffer.str(); - size_t start = body.find("PrusaSlicer "); - if (start == std::string::npos) - return boost::none; - body = body.substr(start + 12); - size_t end = body.find_first_of(" \n"); - if (end < body.size()) - body.resize(end); - return Semver::parse(body); -} - -void GUI_App::init_app_config() -{ - // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. - -// SetAppName(SLIC3R_APP_KEY); - SetAppName(SLIC3R_APP_KEY "-alpha"); -// SetAppName(SLIC3R_APP_KEY "-beta"); - - -// SetAppDisplayName(SLIC3R_APP_NAME); - - // Set the Slic3r data directory at the Slic3r XS module. - // Unix: ~/ .Slic3r - // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" - // Mac : "~/Library/Application Support/Slic3r" - - if (data_dir().empty()) { - #ifndef __linux__ - set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); - #else - // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. - // https://github.com/prusa3d/PrusaSlicer/issues/2911 - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); - #endif - } else { - m_datadir_redefined = true; - } - - if (!app_config) - app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer); - - // load settings - m_app_conf_exists = app_config->exists(); - if (m_app_conf_exists) { - std::string error = app_config->load(); - if (!error.empty()) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - if (is_editor()) { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - else { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - } - } -} - -// returns old config path to copy from if such exists, -// returns an empty string if such config path does not exists or if it cannot be loaded. -std::string GUI_App::check_older_app_config(Semver current_version, bool backup) -{ - std::string older_data_dir_path; - - // If the config folder is redefined - do not check - if (m_datadir_redefined) - return {}; - - // find other version app config (alpha / beta / release) - std::string config_path = app_config->config_path(); - boost::filesystem::path parent_file_path(config_path); - std::string filename = parent_file_path.filename().string(); - parent_file_path.remove_filename().remove_filename(); - - std::vector candidates; - - if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename); - if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename); - if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename); - - Semver last_semver = current_version; - for (const auto& candidate : candidates) { - if (boost::filesystem::exists(candidate)) { - // parse - boost::optionalother_semver = parse_semver_from_ini(candidate.string()); - if (other_semver && *other_semver > last_semver) { - last_semver = *other_semver; - older_data_dir_path = candidate.parent_path().string(); - } - } - } - if (older_data_dir_path.empty()) - return {}; - BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path; - // ask about using older data folder - - InfoDialog msg(nullptr - , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION) - , backup ? - format_wxstr(_L( - "The active configuration was created by %1% %2%," - "\nwhile a newer configuration was found in %3%" - "\ncreated by %1% %4%." - "\n\nShall the newer configuration be imported?" - "\nIf so, your active configuration will be backed up before importing the new configuration." - ) - , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string()) - : format_wxstr(_L( - "An existing configuration was found in %3%" - "\ncreated by %1% %2%." - "\n\nShall this configuration be imported?" - ) - , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path) - , true, wxYES_NO); - - if (backup) { - msg.SetButtonLabel(wxID_YES, _L("Import")); - msg.SetButtonLabel(wxID_NO, _L("Don't import")); - } - - if (msg.ShowModal() == wxID_YES) { - std::string snapshot_id; - if (backup) { - const Config::Snapshot* snapshot{ nullptr }; - if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "", - _u8L("Continue and import newer configuration?"), &snapshot)) - return {}; - if (snapshot) { - // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail. - snapshot_id = snapshot->id; - assert(! snapshot_id.empty()); - app_config->set("on_snapshot", snapshot_id); - } else - BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot"; - } - - // load app config from older file - std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string()); - if (!error.empty()) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - if (is_editor()) { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - else { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - } - if (!snapshot_id.empty()) - app_config->set("on_snapshot", snapshot_id); - m_app_conf_exists = true; - return older_data_dir_path; - } - return {}; -} - -void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) -{ - BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; - m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); -} - -bool GUI_App::OnInit() -{ - try { - return on_init_inner(); - } catch (const std::exception&) { - generic_exception_handle(); - return false; - } -} - -bool GUI_App::on_init_inner() -{ - // Set initialization of image handlers before any UI actions - See GH issue #7469 - wxInitAllImageHandlers(); - -#if defined(_WIN32) && ! defined(_WIN64) - // Win32 32bit build. - if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { - RichMessageDialog dlg(nullptr, - _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows." - "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system." - "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/." - "\nDo you wish to continue?"), - "PrusaSlicer", wxICON_QUESTION | wxYES_NO); - if (dlg.ShowModal() != wxID_YES) - return false; - } -#endif // _WIN64 - - // Forcing back menu icons under gtk2 and gtk3. Solution is based on: - // https://docs.gtk.org/gtk3/class.Settings.html - // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb - // TODO: Find workaround for GTK4 -#if defined(__WXGTK20__) || defined(__WXGTK3__) - g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL); -#endif - - // Verify resources path - const wxString resources_dir = from_u8(Slic3r::resources_dir()); - wxCHECK_MSG(wxDirExists(resources_dir), false, - wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - -#ifdef __linux__ - if (! check_old_linux_datadir(GetAppName())) { - std::cerr << "Quitting, user chose to move their data to new location." << std::endl; - return false; - } -#endif - - // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. -// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); - // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible - // performance when working on high resolution multi-display setups. -// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); - -// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; - - // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action. - // Like here, before the show InfoDialog in check_older_app_config() - - // If load_language() fails, the application closes. - load_language(wxString(), true); -#ifdef _MSW_DARK_MODE - bool init_dark_color_mode = app_config->get("dark_color_mode") == "1"; - bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; - NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled); -#endif - // initialize label colors and fonts - init_ui_colours(); - init_fonts(); - - std::string older_data_dir_path; - if (m_app_conf_exists) { - if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION)) - // Only copying configuration if it was saved with a newer slicer than the one currently running. - older_data_dir_path = check_older_app_config(app_config->orig_version(), true); - } else { - // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration. - older_data_dir_path = check_older_app_config(Semver(), false); - } - -#ifdef _MSW_DARK_MODE - // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed - if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1"; - init_dark_color_mode != new_dark_color_mode) { - NppDarkMode::SetDarkMode(new_dark_color_mode); - init_ui_colours(); - update_ui_colours_from_appconfig(); - } - if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; - init_sys_menu_enabled != new_sys_menu_enabled) - NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled); -#endif - - if (is_editor()) { - std::string msg = Http::tls_global_init(); - std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); - bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); - - if (!msg.empty() && !ssl_accept) { - RichMessageDialog - dlg(nullptr, - wxString::Format(_L("%s\nDo you want to continue?"), msg), - "PrusaSlicer", wxICON_QUESTION | wxYES_NO); - dlg.ShowCheckBox(_L("Remember my choice")); - if (dlg.ShowModal() != wxID_YES) return false; - - app_config->set("tls_cert_store_accepted", - dlg.IsCheckBoxChecked() ? "yes" : "no"); - app_config->set("tls_accepted_cert_store_location", - dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); - } - } - - SplashScreen* scrn = nullptr; - if (app_config->get("show_splash_screen") == "1") { - // make a bitmap with dark grey banner on the left side - wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); - - // Detect position (display) to show the splash screen - // Now this position is equal to the mainframe position - wxPoint splashscreen_pos = wxDefaultPosition; - bool default_splashscreen_pos = true; - if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") { - auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); - default_splashscreen_pos = metrics == boost::none; - if (!default_splashscreen_pos) - splashscreen_pos = metrics->get_rect().GetPosition(); - } - - if (!default_splashscreen_pos) { - // workaround for crash related to the positioning of the window on secondary monitor - get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos"); - get_app_config()->save(); - } - - // create splash screen with updated bmp - scrn = new SplashScreen(bmp.IsOk() ? bmp : get_bmp_bundle("PrusaSlicer", 400)->GetPreferredBitmapSizeAtScale(1.0), - wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); - - if (!default_splashscreen_pos) - // revert "restore_win_position" value if application wasn't crashed - get_app_config()->set("restore_win_position", "1"); -#ifndef __linux__ - wxYield(); -#endif - scrn->SetText(_L("Loading configuration")+ dots); - } - - preset_bundle = new PresetBundle(); - - // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory - // supplied as argument to --datadir; in that case we should still run the wizard - preset_bundle->setup_directories(); - - if (! older_data_dir_path.empty()) { - preset_bundle->import_newer_configs(older_data_dir_path); - app_config->save(); - } - - if (is_editor()) { -#ifdef __WXMSW__ - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); -#endif // __WXMSW__ - - preset_updater = new PresetUpdater(); - Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); - Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { - std::string evt_string = into_u8(evt.GetString()); - if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { - auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); - this->plater_->get_notification_manager()->push_notification( notif_type - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) - , _u8L("See Releases page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; } - ); - } - } - }); - Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) { - //lm:This does not force a render. The progress bar only updateswhen the mouse is moved. - if (this->plater_ != nullptr) - this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f ); - }); - - Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr) - this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload); - if(!evt.GetString().IsEmpty()) - show_error(nullptr, evt.GetString()); - }); - - Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { - show_error(nullptr, evt.GetString()); - }); - - } - else { -#ifdef __WXMSW__ - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); -#endif // __WXMSW__ - } - - // Suppress the '- default -' presets. - preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); - try { - // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only. - // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force - // installation of a compatible system preset, thus nullifying the system preset substitutions. - init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); - } catch (const std::exception &ex) { - show_error(nullptr, ex.what()); - } - -#ifdef WIN32 -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - register_win32_dpi_event(); -#endif // !wxVERSION_EQUAL_OR_GREATER_THAN - register_win32_device_notification_event(); -#endif // WIN32 - - // Let the libslic3r know the callback, which will translate messages on demand. - Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); - - // application frame - if (scrn && is_editor()) - scrn->SetText(_L("Preparing settings tabs") + dots); - - mainframe = new MainFrame(); - // hide settings tabs after first Layout - if (is_editor()) - mainframe->select_tab(size_t(0)); - - sidebar().obj_list()->init_objects(); // propagate model objects to object list -// update_mode(); // !!! do that later - SetTopWindow(mainframe); - - plater_->init_notification_manager(); - - m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); - - if (is_gcode_viewer()) { - mainframe->update_layout(); - if (plater_ != nullptr) - // ensure the selected technology is ptFFF - plater_->set_printer_technology(ptFFF); - } - else - load_current_presets(); - - // Save the active profiles as a "saved into project". - update_saved_preset_from_current_preset(); - - if (plater_ != nullptr) { - // Save the names of active presets and project specific config into ProjectDirtyStateManager. - plater_->reset_project_dirty_initial_presets(); - // Update Project dirty state, update application title bar. - plater_->update_project_dirty_from_presets(); - } - - mainframe->Show(true); - - obj_list()->set_min_height(); - - update_mode(); // update view mode after fix of the object_list size - -#ifdef __APPLE__ - other_instance_message_handler()->bring_instance_forward(); -#endif //__APPLE__ - - Bind(wxEVT_IDLE, [this](wxIdleEvent& event) - { - if (! plater_) - return; - - this->obj_manipul()->update_if_dirty(); - - // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT - // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. -#ifdef __linux__ - if (! m_post_initialized && m_opengl_initialized) { -#else - if (! m_post_initialized) { -#endif - m_post_initialized = true; -#ifdef WIN32 - this->mainframe->register_win32_callbacks(); -#endif - this->post_init(); - } - - if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1") - app_config->save(); - }); - - m_initialized = true; - - if (const std::string& crash_reason = app_config->get("restore_win_position"); - boost::starts_with(crash_reason,"crashed")) - { - wxString preferences_item = _L("Restore window position on start"); - InfoDialog dialog(nullptr, - _L("PrusaSlicer started after a crash"), - format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n" - "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n" - "More precise reason for the crash: \"%1%\".\n" - "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n" - "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". " - "Otherwise, the application will most likely crash again next time."), - "" + from_u8(crash_reason) + "", - "#2939", - "#5573", - "" + preferences_item + ""), - true, wxYES_NO); - - dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item)); - dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item)); - - auto answer = dialog.ShowModal(); - if (answer == wxID_YES) - app_config->set("restore_win_position", "0"); - else if (answer == wxID_NO) - app_config->set("restore_win_position", "1"); - app_config->save(); - } - - return true; -} - -unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) -{ - double r = colour.Red(); - double g = colour.Green(); - double b = colour.Blue(); - - return std::round(std::sqrt( - r * r * .241 + - g * g * .691 + - b * b * .068 - )); -} - -bool GUI_App::dark_mode() -{ -#if __APPLE__ - // The check for dark mode returns false positive on 10.12 and 10.13, - // which allowed setting dark menu bar and dock area, which is - // is detected as dark mode. We must run on at least 10.14 where the - // proper dark mode was first introduced. - return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode(); -#else - if (wxGetApp().app_config->has("dark_color_mode")) - return wxGetApp().app_config->get("dark_color_mode") == "1"; - return check_dark_mode(); -#endif -} - -const wxColour GUI_App::get_label_default_clr_system() -{ - return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); -} - -const wxColour GUI_App::get_label_default_clr_modified() -{ - return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1); -} - -const std::vector GUI_App::get_mode_default_palette() -{ - return { "#7DF028", "#FFDC00", "#E70000" }; -} - -void GUI_App::init_ui_colours() -{ - m_color_label_modified = get_label_default_clr_modified(); - m_color_label_sys = get_label_default_clr_system(); - m_mode_palette = get_mode_default_palette(); - - bool is_dark_mode = dark_mode(); -#ifdef _WIN32 - m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); - m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); - m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); - m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); - m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); -#else - m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); -#endif - m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); -} - -void GUI_App::update_ui_colours_from_appconfig() -{ - // load label colors - if (app_config->has("label_clr_sys")) { - auto str = app_config->get("label_clr_sys"); - if (!str.empty()) - m_color_label_sys = wxColour(str); - } - - if (app_config->has("label_clr_modified")) { - auto str = app_config->get("label_clr_modified"); - if (!str.empty()) - m_color_label_modified = wxColour(str); - } - - // load mode markers colors - if (app_config->has("mode_palette")) { - const auto colors = app_config->get("mode_palette"); - if (!colors.empty()) { - m_mode_palette.clear(); - if (!unescape_strings_cstyle(colors, m_mode_palette)) - m_mode_palette = get_mode_default_palette(); - } - } -} - -void GUI_App::update_label_colours() -{ - for (Tab* tab : tabs_list) - tab->update_label_colours(); -} - -#ifdef _WIN32 -static bool is_focused(HWND hWnd) -{ - HWND hFocusedWnd = ::GetFocus(); - return hFocusedWnd && hWnd == hFocusedWnd; -} - -static bool is_default(wxWindow* win) -{ - wxTopLevelWindow* tlw = find_toplevel_parent(win); - if (!tlw) - return false; - - return win == tlw->GetDefaultItem(); -} -#endif - -void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) -{ -#ifdef _WIN32 - bool is_focused_button = false; - bool is_default_button = false; - if (wxButton* btn = dynamic_cast(window)) { - if (!(btn->GetWindowStyle() & wxNO_BORDER)) { - btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER); - highlited = true; - } - // button marking - { - auto mark_button = [this, btn, highlited](const bool mark) { - if (btn->GetLabel().IsEmpty()) - btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default); - else - btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default)); - btn->Refresh(); - btn->Update(); - }; - - // hovering - btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); - // focusing - btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); - - is_focused_button = is_focused(btn->GetHWND()); - is_default_button = is_default(btn); - if (is_focused_button || is_default_button) - mark_button(is_focused_button); - } - } - else if (wxTextCtrl* text = dynamic_cast(window)) { - if (text->GetBorder() != wxBORDER_SIMPLE) - text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); - } - else if (wxCheckListBox* list = dynamic_cast(window)) { - list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); - list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - for (size_t i = 0; i < list->GetCount(); i++) - if (wxOwnerDrawn* item = list->GetItem(i)) { - item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - item->SetTextColour(m_color_label_default); - } - return; - } - else if (dynamic_cast(window)) - window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); - - if (!just_font) - window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - if (!is_focused_button && !is_default_button) - window->SetForegroundColour(m_color_label_default); -#endif -} - -// recursive function for scaling fonts for all controls in Window -#ifdef _WIN32 -static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false) -{ - bool is_btn = dynamic_cast(window) != nullptr; - if (!(just_buttons_update && !is_btn)) - wxGetApp().UpdateDarkUI(window, is_btn); - - auto children = window->GetChildren(); - for (auto child : children) { - update_dark_children_ui(child); - } -} -#endif - -// Note: Don't use this function for Dialog contains ScalableButtons -void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/) -{ -#ifdef _WIN32 - update_dark_children_ui(dlg, just_buttons_update); -#endif -} -void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) -{ -#ifdef _WIN32 - UpdateDarkUI(dvc, highlited ? dark_mode() : false); -#ifdef _MSW_DARK_MODE - dvc->RefreshHeaderDarkMode(&m_normal_font); -#endif //_MSW_DARK_MODE - if (dvc->HasFlag(wxDV_ROW_LINES)) - dvc->SetAlternateRowColour(m_color_highlight_default); - if (dvc->GetBorder() != wxBORDER_SIMPLE) - dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE); -#endif -} - -void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent) -{ -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(parent); - - auto children = parent->GetChildren(); - for (auto child : children) { - if (dynamic_cast(child)) - child->SetForegroundColour(m_color_label_default); - } -#endif -} - -void GUI_App::init_fonts() -{ - m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); - m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - -#ifdef __WXMAC__ - m_small_font.SetPointSize(11); - m_bold_font.SetPointSize(13); -#endif /*__WXMAC__*/ - - // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as - // DEFAULT in wxGtk. Use the TELETYPE family as a work-around - m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); - m_code_font.SetPointSize(m_normal_font.GetPointSize()); -} - -void GUI_App::update_fonts(const MainFrame *main_frame) -{ - /* Only normal and bold fonts are used for an application rescale, - * because of under MSW small and normal fonts are the same. - * To avoid same rescaling twice, just fill this values - * from rescaled MainFrame - */ - if (main_frame == nullptr) - main_frame = this->mainframe; - m_normal_font = main_frame->normal_font(); - m_small_font = m_normal_font; - m_bold_font = main_frame->normal_font().Bold(); - m_link_font = m_bold_font.Underlined(); - m_em_unit = main_frame->em_unit(); - m_code_font.SetPointSize(m_normal_font.GetPointSize()); -} - -void GUI_App::set_label_clr_modified(const wxColour& clr) -{ - if (m_color_label_modified == clr) - return; - m_color_label_modified = clr; - const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - app_config->set("label_clr_modified", str); - app_config->save(); -} - -void GUI_App::set_label_clr_sys(const wxColour& clr) -{ - if (m_color_label_sys == clr) - return; - m_color_label_sys = clr; - const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - app_config->set("label_clr_sys", str); - app_config->save(); -} - -const std::string& GUI_App::get_mode_btn_color(int mode_id) -{ - assert(0 <= mode_id && size_t(mode_id) < m_mode_palette.size()); - return m_mode_palette[mode_id]; -} - -std::vector GUI_App::get_mode_palette() -{ - return { wxColor(m_mode_palette[0]), - wxColor(m_mode_palette[1]), - wxColor(m_mode_palette[2]) }; -} - -void GUI_App::set_mode_palette(const std::vector& palette) -{ - bool save = false; - - for (size_t mode = 0; mode < palette.size(); ++mode) { - const wxColour& clr = palette[mode]; - std::string color_str = clr == wxTransparentColour ? std::string("") : encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - if (m_mode_palette[mode] != color_str) { - m_mode_palette[mode] = color_str; - save = true; - } - } - - if (save) { - mainframe->update_mode_markers(); - app_config->set("mode_palette", escape_strings_cstyle(m_mode_palette)); - app_config->save(); - } -} - -bool GUI_App::tabs_as_menu() const -{ - return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); -} - -wxSize GUI_App::get_min_size() const -{ - return wxSize(76*m_em_unit, 49 * m_em_unit); -} - -float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const -{ -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit*0.1f; -#endif // __APPLE__ - - const std::string& use_val = app_config->get("use_custom_toolbar_size"); - const std::string& val = app_config->get("custom_toolbar_size"); - const std::string& auto_val = app_config->get("auto_toolbar_size"); - - if (val.empty() || auto_val.empty() || use_val.empty()) - return icon_sc; - - int int_val = use_val == "0" ? 100 : atoi(val.c_str()); - // correct value in respect to auto_toolbar_size - int_val = std::min(atoi(auto_val.c_str()), int_val); - - if (is_limited && int_val < 50) - int_val = 50; - - return 0.01f * int_val * icon_sc; -} - -void GUI_App::set_auto_toolbar_icon_scale(float scale) const -{ -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit * 0.1f; -#endif // __APPLE__ - - long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); - std::string val = std::to_string(int_val); - - app_config->set("auto_toolbar_size", val); -} - -// check user printer_presets for the containing information about "Print Host upload" -void GUI_App::check_printer_presets() -{ - std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); - if (preset_names.empty()) - return; - - wxString msg_text = _L("You have the following presets with saved options for \"Print Host upload\"") + ":"; - for (const std::string& preset_name : preset_names) - msg_text += "\n \"" + from_u8(preset_name) + "\","; - msg_text.RemoveLast(); - msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" - "Settings will be available in physical printers settings.") + "\n\n" + - _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" - "Note: This name can be changed later from the physical printers settings"); - - //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - - preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); -} - -void GUI_App::recreate_GUI(const wxString& msg_name) -{ - m_is_recreating_gui = true; - - mainframe->shutdown(); - - wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); - dlg.Pulse(); - dlg.Update(10, _L("Recreating") + dots); - - MainFrame *old_main_frame = mainframe; - mainframe = new MainFrame(); - if (is_editor()) - // hide settings tabs after first Layout - mainframe->select_tab(size_t(0)); - // Propagate model objects to object list. - sidebar().obj_list()->init_objects(); - SetTopWindow(mainframe); - - dlg.Update(30, _L("Recreating") + dots); - old_main_frame->Destroy(); - - dlg.Update(80, _L("Loading of current presets") + dots); - m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); - load_current_presets(); - mainframe->Show(true); - - dlg.Update(90, _L("Loading of a mode view") + dots); - - obj_list()->set_min_height(); - update_mode(); - - // #ys_FIXME_delete_after_testing Do we still need this ? -// CallAfter([]() { -// // Run the config wizard, don't offer the "reset user profile" checkbox. -// config_wizard_startup(true); -// }); - - m_is_recreating_gui = false; -} - -void GUI_App::system_info() -{ - SysInfoDialog dlg; - dlg.ShowModal(); -} - -void GUI_App::keyboard_shortcuts() -{ - KBShortcutsDialog dlg; - dlg.ShowModal(); -} - -// static method accepting a wxWindow object as first parameter -bool GUI_App::catch_error(std::function cb, - // wxMessageDialog* message_dialog, - const std::string& err /*= ""*/) -{ - if (!err.empty()) { - if (cb) - cb(); - // if (message_dialog) - // message_dialog->(err, "Error", wxOK | wxICON_ERROR); - show_error(/*this*/nullptr, err); - return true; - } - return false; -} - -// static method accepting a wxWindow object as first parameter -void fatal_error(wxWindow* parent) -{ - show_error(parent, ""); - // exit 1; // #ys_FIXME -} - -#ifdef _WIN32 - -#ifdef _MSW_DARK_MODE -static void update_scrolls(wxWindow* window) -{ - wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst(); - while (node) - { - wxWindow* win = node->GetData(); - if (dynamic_cast(win) || - dynamic_cast(win) || - dynamic_cast(win)) - NppDarkMode::SetDarkExplorerTheme(win->GetHWND()); - - update_scrolls(win); - node = node->GetNext(); - } -} -#endif //_MSW_DARK_MODE - - -#ifdef _MSW_DARK_MODE -void GUI_App::force_menu_update() -{ - NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1"); -} -#endif //_MSW_DARK_MODE - -void GUI_App::force_colors_update() -{ -#ifdef _MSW_DARK_MODE - NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); - if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl()) - NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND); - NppDarkMode::SetDarkTitleBar(mainframe->GetHWND()); - NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND()); -#endif //_MSW_DARK_MODE - m_force_colors_update = true; -} -#endif //_WIN32 - -// Called after the Preferences dialog is closed and the program settings are saved. -// Update the UI based on the current preferences. -void GUI_App::update_ui_from_settings() -{ - update_label_colours(); -#ifdef _WIN32 - // Upadte UI colors before Update UI from settings - if (m_force_colors_update) { - m_force_colors_update = false; - mainframe->force_color_changed(); - mainframe->diff_dialog.force_color_changed(); - mainframe->preferences_dialog->force_color_changed(); - mainframe->printhost_queue_dlg()->force_color_changed(); -#ifdef _MSW_DARK_MODE - update_scrolls(mainframe); - if (mainframe->is_dlg_layout()) { - // update for tabs bar - UpdateDarkUI(&mainframe->m_settings_dialog); - mainframe->m_settings_dialog.Fit(); - mainframe->m_settings_dialog.Refresh(); - // update scrollbars - update_scrolls(&mainframe->m_settings_dialog); - } -#endif //_MSW_DARK_MODE - } -#endif - mainframe->update_ui_from_settings(); -} - -void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) -{ - const std::string name = into_u8(window->GetName()); - - window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) { - window_pos_save(window, name); - event.Skip(); - }); - - window_pos_restore(window, name, default_maximized); - - on_window_geometry(window, [=]() { - window_pos_sanitize(window); - }); -} - -void GUI_App::load_project(wxWindow *parent, wxString& input_file) const -{ - input_file.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one file (3MF/AMF):"), - app_config->get_last_dir(), "", - file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - input_file = dialog.GetPath(); -} - -void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const -{ - input_files.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one or more files (STL/3MF/STEP/OBJ/AMF/PRUSA):"), - from_u8(app_config->get_last_dir()), "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - dialog.GetPaths(input_files); -} - -void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const -{ - input_file.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"), - app_config->get_last_dir(), "", - file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - input_file = dialog.GetPath(); -} - -bool GUI_App::switch_language() -{ - if (select_language()) { - recreate_GUI(_L("Changing of an application language") + dots); - return true; - } else { - return false; - } -} - -#ifdef __linux__ -static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, - const wxLanguageInfo* system_language) -{ - constexpr size_t max_len = 50; - char path[max_len] = ""; - std::vector locales; - const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_')); - - // Call locale -a so we can parse the output to get the list of available locales - // We expect lines such as "en_US.utf8". Pick ones starting with the language code - // we are switching to. Lines with different formatting will be removed later. - FILE* fp = popen("locale -a", "r"); - if (fp != NULL) { - while (fgets(path, max_len, fp) != NULL) { - std::string line(path); - line = line.substr(0, line.find('\n')); - if (boost::starts_with(line, lang_prefix)) - locales.push_back(line); - } - pclose(fp); - } - - // locales now contain all candidates for this language. - // Sort them so ones containing anything about UTF-8 are at the end. - std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b) - { - auto has_utf8 = [](const std::string & s) { - auto S = boost::to_upper_copy(s); - return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos; - }; - return ! has_utf8(a) && has_utf8(b); - }); - - // Remove the suffix behind a dot, if there is one. - for (std::string& s : locales) - s = s.substr(0, s.find(".")); - - // We just hope that dear Linux "locale -a" returns country codes - // in ISO 3166-1 alpha-2 code (two letter) format. - // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes - // To be sure, remove anything not looking as expected - // (any number of lowercase letters, underscore, two uppercase letters). - locales.erase(std::remove_if(locales.begin(), - locales.end(), - [](const std::string& s) { - return ! std::regex_match(s, - std::regex("^[a-z]+_[A-Z]{2}$")); - }), - locales.end()); - - if (system_language) { - // Is there a candidate matching a country code of a system language? Move it to the end, - // while maintaining the order of matches, so that the best match ends up at the very end. - std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2); - int cnt = locales.size(); - for (int i = 0; i < cnt; ++i) - if (locales[i].find(system_country) != std::string::npos) { - locales.emplace_back(std::move(locales[i])); - locales[i].clear(); - } - } - - // Now try them one by one. - for (auto it = locales.rbegin(); it != locales.rend(); ++ it) - if (! it->empty()) { - const std::string &locale = *it; - const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale)); - if (wxLocale::IsAvailable(lang->Language)) - return lang; - } - return language; -} -#endif - -int GUI_App::GetSingleChoiceIndex(const wxString& message, - const wxString& caption, - const wxArrayString& choices, - int initialSelection) -{ -#ifdef _WIN32 - wxSingleChoiceDialog dialog(nullptr, message, caption, choices); - wxGetApp().UpdateDlgDarkUI(&dialog); - - dialog.SetSelection(initialSelection); - return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1; -#else - return wxGetSingleChoiceIndex(message, caption, choices, initialSelection); -#endif -} - -// select language from the list of installed languages -bool GUI_App::select_language() -{ - wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); - std::vector language_infos; - language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); - for (size_t i = 0; i < translations.GetCount(); ++ i) { - const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]); - if (langinfo != nullptr) - language_infos.emplace_back(langinfo); - } - sort_remove_duplicates(language_infos); - std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; }); - - wxArrayString names; - names.Alloc(language_infos.size()); - - // Some valid language should be selected since the application start up. - const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage()); - int init_selection = -1; - int init_selection_alt = -1; - int init_selection_default = -1; - for (size_t i = 0; i < language_infos.size(); ++ i) { - if (wxLanguage(language_infos[i]->Language) == current_language) - // The dictionary matches the active language and country. - init_selection = i; - else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) || - // if the active language is Slovak, mark the Czech language as active. - (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk")) - // The dictionary matches the active language, it does not necessarily match the country. - init_selection_alt = i; - if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en") - // This will be the default selection if the active language does not match any dictionary. - init_selection_default = i; - names.Add(language_infos[i]->Description); - } - if (init_selection == -1) - // This is the dictionary matching the active language. - init_selection = init_selection_alt; - if (init_selection != -1) - // This is the language to highlight in the choice dialog initially. - init_selection_default = init_selection; - - const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default); - // Try to load a new language. - if (index != -1 && (init_selection == -1 || init_selection != index)) { - const wxLanguageInfo *new_language_info = language_infos[index]; - if (this->load_language(new_language_info->CanonicalName, false)) { - // Save language at application config. - // Which language to save as the selected dictionary language? - // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its - // stability in the future: - // wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - // 2) Current locale language may not match the dictionary name, see GH issue #3901 - // m_wxLocale->GetCanonicalName() - // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name. - app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data()); - app_config->save(); - return true; - } - } - - return false; -} - -// Load gettext translation files and activate them at the start of the application, -// based on the "translation_language" key stored in the application config. -bool GUI_App::load_language(wxString language, bool initial) -{ - if (initial) { - // There is a static list of lookup path prefixes in wxWidgets. Add ours. - wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir())); - // Get the active language from PrusaSlicer.ini, or empty string if the key does not exist. - language = app_config->get("translation_language"); - if (! language.empty()) - BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language; - - // Get the system language. - { - const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); - if (lang_system != wxLANGUAGE_UNKNOWN) { - m_language_info_system = wxLocale::GetLanguageInfo(lang_system); - BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data(); - } - } - { - // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. - wxLocale temp_locale; -#ifdef __WXOSX__ - // ysFIXME - temporary workaround till it isn't fixed in wxWidgets: - // Use English as an initial language, because of under OSX it try to load "inappropriate" language for wxLANGUAGE_DEFAULT. - // For example in our case it's trying to load "en_CZ" and as a result PrusaSlicer catch warning message. - // But wxWidgets guys work on it. - temp_locale.Init(wxLANGUAGE_ENGLISH); -#else - temp_locale.Init(); -#endif // __WXOSX__ - // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). - wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); - // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer - // and try to match them with the system specific "preferred languages". - // There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage(). - // The last parameter gets added to the list of detected dictionaries. This is a workaround - // for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way. - wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - if (! best_language.IsEmpty()) { - m_language_info_best = wxLocale::FindLanguageInfo(best_language); - BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data(); - } - #ifdef __linux__ - wxString lc_all; - if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) { - // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL. - // Disregard the "best" suggestion in case LC_ALL is provided. - m_language_info_best = nullptr; - } - #endif - } - } - - const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language); - if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) { - // Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI). - language_info = nullptr; - BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data(); - } - - if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) { - BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data(); - language_info = nullptr; - } - - if (language_info == nullptr) { - // PrusaSlicer does not support the Right to Left languages yet. - if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft) - language_info = m_language_info_system; - if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft) - language_info = m_language_info_best; - if (language_info == nullptr) - language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US); - } - - BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data(); - - // Alternate language code. - wxLanguage language_dict = wxLanguage(language_info->Language); - if (language_info->CanonicalName.BeforeFirst('_') == "sk") { - // Slovaks understand Czech well. Give them the Czech translation. - language_dict = wxLANGUAGE_CZECH; - BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language"; - } - - // Select language for locales. This language may be different from the language of the dictionary. - if (language_info == m_language_info_best || language_info == m_language_info_system) { - // The current language matches user's default profile exactly. That's great. - } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) { - // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language. - // This allows a Swiss guy to use a German dictionary without forcing him to German locales. - language_info = m_language_info_best; - } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_')) - language_info = m_language_info_system; - -#ifdef __linux__ - // If we can't find this locale , try to use different one for the language - // instead of just reporting that it is impossible to switch. - if (! wxLocale::IsAvailable(language_info->Language)) { - std::string original_lang = into_u8(language_info->CanonicalName); - language_info = linux_get_existing_locale_language(language_info, m_language_info_system); - BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.") - % original_lang % language_info->CanonicalName.ToUTF8().data(); - } -#endif - - if (! wxLocale::IsAvailable(language_info->Language)) { - // Loading the language dictionary failed. - wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed."; -#if !defined(_WIN32) && !defined(__APPLE__) - // likely some linux system - message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"; -#endif - if (initial) - message + "\n\nApplication will close."; - wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); - if (initial) - std::exit(EXIT_FAILURE); - else - return false; - } - - // Release the old locales, create new locales. - //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now. - m_wxLocale.release(); - m_wxLocale = Slic3r::make_unique(); - m_wxLocale->Init(language_info->Language); - // Override language at the active wxTranslations class (which is stored in the active m_wxLocale) - // to load possibly different dictionary, for example, load Czech dictionary for Slovak language. - wxTranslations::Get()->SetLanguage(language_dict); - { - // UKR Localization specific workaround till the wxWidgets doesn't fixed: - // From wxWidgets 3.1.6 calls setlocation(0, wxInfoLanguage->LocaleTag), see (https://github.com/prusa3d/wxWidgets/commit/deef116a09748796711d1e3509965ee208dcdf0b#diff-7de25e9a71c4dce61bbf76492c589623d5b93fd1bb105ceaf0662075d15f4472), - // where LocaleTag is a Tag of locale in BCP 47 - like notation. - // For Ukrainian Language LocaleTag == "uk". - // But setlocale(0, "uk") returns "English_United Kingdom.1252" instead of "uk", - // and, as a result, locales are set to English_United Kingdom - - if (language_info->CanonicalName == "uk") - setlocale(0, language_info->GetCanonicalWithRegion().data()); - } - m_wxLocale->AddCatalog(SLIC3R_APP_KEY); - m_imgui->set_language(into_u8(language_info->CanonicalName)); - //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. - //wxSetlocale(LC_NUMERIC, "C"); - Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); - return true; -} - -Tab* GUI_App::get_tab(Preset::Type type) -{ - for (Tab* tab: tabs_list) - if (tab->type() == type) - return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab - return nullptr; -} - -ConfigOptionMode GUI_App::get_mode() -{ - if (!app_config->has("view_mode")) - return comSimple; - - const auto mode = app_config->get("view_mode"); - return mode == "expert" ? comExpert : - mode == "simple" ? comSimple : comAdvanced; -} - -void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) -{ - const std::string mode_str = mode == comExpert ? "expert" : - mode == comSimple ? "simple" : "advanced"; - app_config->set("view_mode", mode_str); - app_config->save(); - update_mode(); -} - -// Update view mode according to selected menu -void GUI_App::update_mode() -{ - sidebar().update_mode(); - -#ifdef _WIN32 //_MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); -#endif - - for (auto tab : tabs_list) - tab->update_mode(); - - plater()->update_menus(); - plater()->canvas3D()->update_gizmos_on_off_state(); -} - -void GUI_App::add_config_menu(wxMenuBar *menu) -{ - auto local_menu = new wxMenu(); - wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); - - const auto config_wizard_name = _(ConfigWizard::name(true)); - const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str()); - // Cmd+, is standard on OS X - what about other operating systems? - if (is_editor()) { - local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); - local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); - local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); - local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); - local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - //if (DesktopIntegrationDialog::integration_possible()) - local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); -#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - local_menu->AppendSeparator(); - } - local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + -#ifdef __APPLE__ - "\tCtrl+,", -#else - "\tCtrl+P", -#endif - _L("Application preferences")); - wxMenu* mode_menu = nullptr; - if (is_editor()) { - local_menu->AppendSeparator(); - mode_menu = new wxMenu(); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode")); -// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode")); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert); - - local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); - } - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); - if (is_editor()) { - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer")); - // TODO: for when we're able to flash dictionaries - // local_menu->Append(config_id_base + FirmwareMenuDict, _L("Flash Language File"), _L("Upload a language dictionary file into a Prusa printer")); - } - - local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { - switch (event.GetId() - config_id_base) { - case ConfigMenuWizard: - run_wizard(ConfigWizard::RR_USER); - break; - case ConfigMenuUpdateConf: - check_updates(true); - break; - case ConfigMenuUpdateApp: - app_version_check(true); - break; -#ifdef __linux__ - case ConfigMenuDesktopIntegration: - show_desktop_integration_dialog(); - break; -#endif - case ConfigMenuTakeSnapshot: - // Take a configuration snapshot. - if (wxString action_name = _L("Taking a configuration snapshot"); - check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) { - wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name")); - UpdateDlgDarkUI(&dlg); - - // set current normal font for dialog children, - // because of just dlg.SetFont(normal_font()) has no result; - for (auto child : dlg.GetChildren()) - child->SetFont(normal_font()); - - if (dlg.ShowModal() == wxID_OK) - if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error( - *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); - snapshot != nullptr) - app_config->set("on_snapshot", snapshot->id); - } - break; - case ConfigMenuSnapshots: - if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) { - std::string on_snapshot; - if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) - on_snapshot = app_config->get("on_snapshot"); - ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); - dlg.ShowModal(); - if (!dlg.snapshot_to_activate().empty()) { - if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && - ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "", - GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate()))) - break; - try { - app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); - // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system - // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users - // are known to be creative and mess with the config files in various ways. - if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); - ! all_substitutions.empty()) - show_substitutions_info(all_substitutions); - - // Load the currently selected preset into the GUI, update the preset selection box. - load_current_presets(); - } catch (std::exception &ex) { - GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); - } - } - } - break; - case ConfigMenuPreferences: - { - open_preferences(); - break; - } - case ConfigMenuLanguage: - { - /* Before change application language, let's check unsaved changes on 3D-Scene - * and draw user's attention to the application restarting after a language change - */ - { - // the dialog needs to be destroyed before the call to switch_language() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); - title += " - " + _L("Language selection"); - //wxMessageDialog dialog(nullptr, - MessageDialog dialog(nullptr, - _L("Switching the language will trigger application restart.\n" - "You will lose content of the plater.") + "\n\n" + - _L("Do you want to proceed?"), - title, - wxICON_QUESTION | wxOK | wxCANCEL); - if (dialog.ShowModal() == wxID_CANCEL) - return; - } - - switch_language(); - break; - } - case ConfigMenuFlashFirmware: - FirmwareDialog::run(mainframe); - break; - default: - break; - } - }); - - using std::placeholders::_1; - - if (mode_menu != nullptr) { - auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert); - } - - menu->Append(local_menu, _L("&Configuration")); -} - -void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) -{ - mainframe->preferences_dialog->show(highlight_option, tab_name); - - if (mainframe->preferences_dialog->recreate_GUI()) - recreate_GUI(_L("Restart application") + dots); - -#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) -#else - if (mainframe->preferences_dialog->seq_top_layer_only_changed()) -#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - this->plater_->refresh_print(); - -#ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 - - if (mainframe->preferences_dialog->settings_layout_changed()) { - // hide full main_sizer for mainFrame - mainframe->GetSizer()->Show(false); - mainframe->update_layout(); - mainframe->select_tab(size_t(0)); - } -} - -bool GUI_App::has_unsaved_preset_changes() const -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) - return true; - } - return false; -} - -bool GUI_App::has_current_preset_changes() const -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - return true; - } - return false; -} - -void GUI_App::update_saved_preset_from_current_preset() -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab* tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology)) - tab->update_saved_preset_from_current_preset(); - } -} - -std::vector GUI_App::get_active_preset_collections() const -{ - std::vector ret; - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* tab : tabs_list) - if (tab->supports_printer_technology(printer_technology)) - ret.push_back(tab->get_presets()); - return ret; -} - -// To notify the user whether he is aware that some preset changes will be lost, -// UnsavedChangesDialog: "Discard / Save / Cancel" -// This is called when: -// - Close Application & Current project isn't saved -// - Load Project & Current project isn't saved -// - Undo / Redo with change of print technologie -// - Loading snapshot -// - Loading config_file/bundle -// UnsavedChangesDialog: "Don't save / Save / Cancel" -// This is called when: -// - Exporting config_bundle -// - Taking snapshot -bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/) -{ - if (has_current_preset_changes()) { - const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; - int act_buttons = ActionButtons::SAVE; - if (dont_save_insted_of_discard) - act_buttons |= ActionButtons::DONT_SAVE; - UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); - std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); - if (act == "none" && dlg.ShowModal() == wxID_CANCEL) - return false; - - if (dlg.save_preset()) // save selected changes - { - for (const std::pair& nt : dlg.get_names_and_types()) - preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); - - load_current_presets(false); - - // if we saved changes to the new presets, we should to - // synchronize config.ini with the current selections. - preset_bundle->export_selections(*app_config); - - MessageDialog(nullptr, dlg.msg_success_saved_modifications(dlg.get_names_and_types().size())).ShowModal(); - } - } - - return true; -} - -void GUI_App::apply_keeped_preset_modifications() -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab* tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology)) - tab->apply_config_from_cache(); - } - load_current_presets(false); -} - -// This is called when creating new project or load another project -// OR close ConfigWizard -// to ask the user what should we do with unsaved changes for presets. -// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel" -// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" -// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" -// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed -bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/) -{ - if (has_current_preset_changes()) { - bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr; - - const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project"; - UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons); - std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); - if (act == "none" && dlg.ShowModal() == wxID_CANCEL) - return false; - - auto reset_modifications = [this, is_called_from_configwizard]() { - if (is_called_from_configwizard) - return; // no need to discared changes. It will be done fromConfigWizard closing - - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - tab->m_presets->discard_current_changes(); - } - load_current_presets(false); - }; - - if (dlg.discard()) - reset_modifications(); - else // save selected changes - { - const auto& preset_names_and_types = dlg.get_names_and_types(); - if (dlg.save_preset()) { - for (const std::pair& nt : preset_names_and_types) - preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); - - // if we saved changes to the new presets, we should to - // synchronize config.ini with the current selections. - preset_bundle->export_selections(*app_config); - - wxString text = dlg.msg_success_saved_modifications(preset_names_and_types.size()); - if (!is_called_from_configwizard) - text += "\n\n" + _L("For new project all modifications will be reseted"); - - MessageDialog(nullptr, text).ShowModal(); - reset_modifications(); - } - else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) { - // execute this part of code only if not all modifications are keeping to the new project - // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected - for (const std::pair& nt : preset_names_and_types) { - Preset::Type type = nt.second; - Tab* tab = get_tab(type); - std::vector selected_options = dlg.get_selected_options(type); - if (type == Preset::TYPE_PRINTER) { - auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); - if (it != selected_options.end()) { - // erase "extruders_count" option from the list - selected_options.erase(it); - // cache the extruders count - static_cast(tab)->cache_extruder_cnt(); - } - } - tab->cache_config_diff(selected_options); - if (!is_called_from_configwizard) - tab->m_presets->discard_current_changes(); - } - if (is_called_from_configwizard) - *postponed_apply_of_keeped_changes = true; - else - apply_keeped_preset_modifications(); - } - } - } - - return true; -} - -bool GUI_App::can_load_project() -{ - int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified.")); - if (saved_project == wxID_CANCEL || - (plater()->is_project_dirty() && saved_project == wxID_NO && - !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved.")))) - return false; - return true; -} - -bool GUI_App::check_print_host_queue() -{ - wxString dirty; - std::vector> jobs; - // Get ongoing jobs from dialog - mainframe->m_printhost_queue_dlg->get_active_jobs(jobs); - if (jobs.empty()) - return true; - // Show dialog - wxString job_string = wxString(); - for (const auto& job : jobs) { - job_string += format_wxstr(" %1% : %2% \n", job.first, job.second); - } - wxString message; - message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?")); - //wxMessageDialog dialog(mainframe, - MessageDialog dialog(mainframe, - message, - wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")), - wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - if (dialog.ShowModal() == wxID_YES) - return true; - - // TODO: If already shown, bring forward - mainframe->m_printhost_queue_dlg->Show(); - return false; -} - -bool GUI_App::checked_tab(Tab* tab) -{ - bool ret = true; - if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end()) - ret = false; - return ret; -} - -// Update UI / Tabs to reflect changes in the currently loaded presets -void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) -{ - // check printer_presets for the containing information about "Print Host upload" - // and create physical printer from it, if any exists - if (check_printer_presets_) - check_printer_presets(); - - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - this->plater()->set_printer_technology(printer_technology); - for (Tab *tab : tabs_list) - if (tab->supports_printer_technology(printer_technology)) { - if (tab->type() == Preset::TYPE_PRINTER) { - static_cast(tab)->update_pages(); - // Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change(). - this->plater()->force_print_bed_update(); - } - tab->load_current_preset(); - } -} - -bool GUI_App::OnExceptionInMainLoop() -{ - generic_exception_handle(); - return false; -} - -#ifdef __APPLE__ -// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run -// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App -// to a G-code viewer. -void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames) -{ - size_t num_gcodes = 0; - for (const wxString &filename : fileNames) - if (is_gcode_file(into_u8(filename))) - ++ num_gcodes; - if (fileNames.size() == num_gcodes) { - // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder, - // just G-codes were passed. Switch to G-code viewer mode. - m_app_mode = EAppMode::GCodeViewer; - unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/"); - if(app_config != nullptr) - delete app_config; - app_config = nullptr; - init_app_config(); - } - wxApp::OSXStoreOpenFiles(fileNames); -} -// wxWidgets override to get an event on open files. -void GUI_App::MacOpenFiles(const wxArrayString &fileNames) -{ - std::vector files; - std::vector gcode_files; - std::vector non_gcode_files; - for (const auto& filename : fileNames) { - if (is_gcode_file(into_u8(filename))) - gcode_files.emplace_back(filename); - else { - files.emplace_back(into_u8(filename)); - non_gcode_files.emplace_back(filename); - } - } - if (m_app_mode == EAppMode::GCodeViewer) { - // Running in G-code viewer. - // Load the first G-code into the G-code viewer. - // Or if no G-codes, send other files to slicer. - if (! gcode_files.empty()) { - if (m_post_initialized) - this->plater()->load_gcode(gcode_files.front()); - else - this->init_params->input_files = { into_u8(gcode_files.front()) }; - } - if (!non_gcode_files.empty()) - start_new_slicer(non_gcode_files, true); - } else { - if (! files.empty()) { - if (m_post_initialized) { - wxArrayString input_files; - for (size_t i = 0; i < non_gcode_files.size(); ++i) - input_files.push_back(non_gcode_files[i]); - this->plater()->load_files(input_files); - } else { - for (const auto &f : non_gcode_files) - this->init_params->input_files.emplace_back(into_u8(f)); - } - } - for (const wxString &filename : gcode_files) - start_new_gcodeviewer(&filename); - } -} - -void GUI_App::MacOpenURL(const wxString& url) -{ - if (app_config && app_config->get("downloader_url_registered") != "1") - { - BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; - return; - } - start_download(boost::nowide::narrow(url)); -} - -#endif /* __APPLE */ - -Sidebar& GUI_App::sidebar() -{ - return plater_->sidebar(); -} - -ObjectManipulation* GUI_App::obj_manipul() -{ - // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) - return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr; -} - -ObjectSettings* GUI_App::obj_settings() -{ - return sidebar().obj_settings(); -} - -ObjectList* GUI_App::obj_list() -{ - return sidebar().obj_list(); -} - -ObjectLayers* GUI_App::obj_layers() -{ - return sidebar().obj_layers(); -} - -Plater* GUI_App::plater() -{ - return plater_; -} - -const Plater* GUI_App::plater() const -{ - return plater_; -} - -Model& GUI_App::model() -{ - return plater_->model(); -} -wxBookCtrlBase* GUI_App::tab_panel() const -{ - return mainframe->m_tabpanel; -} - -NotificationManager* GUI_App::notification_manager() -{ - return plater_->get_notification_manager(); -} - -GalleryDialog* GUI_App::gallery_dialog() -{ - return mainframe->gallery_dialog(); -} - -Downloader* GUI_App::downloader() -{ - return m_downloader.get(); -} - -// extruders count from selected printer preset -int GUI_App::extruders_cnt() const -{ - const Preset& preset = preset_bundle->printers.get_selected_preset(); - return preset.printer_technology() == ptSLA ? 1 : - preset.config.option("nozzle_diameter")->values.size(); -} - -// extruders count from edited printer preset -int GUI_App::extruders_edited_cnt() const -{ - const Preset& preset = preset_bundle->printers.get_edited_preset(); - return preset.printer_technology() == ptSLA ? 1 : - preset.config.option("nozzle_diameter")->values.size(); -} - -wxString GUI_App::current_language_code_safe() const -{ - // Translate the language code to a code, for which Prusa Research maintains translations. - const std::map mapping { - { "cs", "cs_CZ", }, - { "sk", "cs_CZ", }, - { "de", "de_DE", }, - { "es", "es_ES", }, - { "fr", "fr_FR", }, - { "it", "it_IT", }, - { "ja", "ja_JP", }, - { "ko", "ko_KR", }, - { "pl", "pl_PL", }, - { "uk", "uk_UA", }, - { "zh", "zh_CN", }, - { "ru", "ru_RU", }, - }; - wxString language_code = this->current_language_code().BeforeFirst('_'); - auto it = mapping.find(language_code); - if (it != mapping.end()) - language_code = it->second; - else - language_code = "en_US"; - return language_code; -} - -void GUI_App::open_web_page_localized(const std::string &http_address) -{ - open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); -} - -// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). -// Because of we can't to print the multi-part objects with SLA technology. -bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) -{ - if (model_has_multi_part_objects(model())) { - show_info(nullptr, - _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + - _L("Please check your object list before preset changing."), - caption); - return false; - } - if (model_has_connectors(model())) { - show_info(nullptr, - _L("SLA technology doesn't support cut with connectors") + "\n\n" + - _L("Please check your object list before preset changing."), - caption); - return false; - } - return true; -} - -bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) -{ - wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - - if (reason == ConfigWizard::RR_USER) { - // Cancel sync before starting wizard to prevent two downloads at same time - preset_updater->cancel_sync(); - if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) - return false; - } - - auto wizard = new ConfigWizard(mainframe); - const bool res = wizard->run(reason, start_page); - - if (res) { - load_current_presets(); - - // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() - if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) - may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); - } - - return res; -} - -void GUI_App::show_desktop_integration_dialog() -{ -#ifdef __linux__ - //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - DesktopIntegrationDialog dialog(mainframe); - dialog.ShowModal(); -#endif //__linux__ -} - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG -void GUI_App::gcode_thumbnails_debug() -{ - const std::string BEGIN_MASK = "; thumbnail begin"; - const std::string END_MASK = "; thumbnail end"; - std::string gcode_line; - bool reading_image = false; - unsigned int width = 0; - unsigned int height = 0; - - wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dialog.ShowModal() != wxID_OK) - return; - - std::string in_filename = into_u8(dialog.GetPath()); - std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string(); - - boost::nowide::ifstream in_file(in_filename.c_str()); - std::vector rows; - std::string row; - if (in_file.good()) - { - while (std::getline(in_file, gcode_line)) - { - if (in_file.good()) - { - if (boost::starts_with(gcode_line, BEGIN_MASK)) - { - reading_image = true; - gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); - std::string::size_type x_pos = gcode_line.find('x'); - std::string width_str = gcode_line.substr(0, x_pos); - width = (unsigned int)::atoi(width_str.c_str()); - std::string height_str = gcode_line.substr(x_pos + 1); - height = (unsigned int)::atoi(height_str.c_str()); - row.clear(); - } - else if (reading_image && boost::starts_with(gcode_line, END_MASK)) - { - std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; - boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); - if (out_file.good()) - { - std::string decoded; - decoded.resize(boost::beast::detail::base64::decoded_size(row.size())); - decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first); - - out_file.write(decoded.c_str(), decoded.size()); - out_file.close(); - } - - reading_image = false; - width = 0; - height = 0; - rows.clear(); - } - else if (reading_image) - row += gcode_line.substr(2); - } - } - - in_file.close(); - } -} -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG - -void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) -{ - if (name.empty()) { return; } - const auto config_key = (boost::format("window_%1%") % name).str(); - - WindowMetrics metrics = WindowMetrics::from_window(window); - app_config->set(config_key, metrics.serialize()); - app_config->save(); -} - -void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized) -{ - if (name.empty()) { return; } - const auto config_key = (boost::format("window_%1%") % name).str(); - - if (! app_config->has(config_key)) { - window->Maximize(default_maximized); - return; - } - - auto metrics = WindowMetrics::deserialize(app_config->get(config_key)); - if (! metrics) { - window->Maximize(default_maximized); - return; - } - - const wxRect& rect = metrics->get_rect(); - - if (app_config->get("restore_win_position") == "1") { - // workaround for crash related to the positioning of the window on secondary monitor - app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str()); - app_config->save(); - window->SetPosition(rect.GetPosition()); - - // workaround for crash related to the positioning of the window on secondary monitor - app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str()); - app_config->save(); - window->SetSize(rect.GetSize()); - - // revert "restore_win_position" value if application wasn't crashed - app_config->set("restore_win_position", "1"); - app_config->save(); - } - else - window->CenterOnScreen(); - - window->Maximize(metrics->get_maximized()); -} - -void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) -{ - /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window); - wxRect display; - if (display_idx == wxNOT_FOUND) { - display = wxDisplay(0u).GetClientArea(); - window->Move(display.GetTopLeft()); - } else { - display = wxDisplay(display_idx).GetClientArea(); - } - - auto metrics = WindowMetrics::from_window(window); - metrics.sanitize_for_display(display); - if (window->GetScreenRect() != metrics.get_rect()) { - window->SetSize(metrics.get_rect()); - } -} - -bool GUI_App::config_wizard_startup() -{ - if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { - run_wizard(ConfigWizard::RR_DATA_EMPTY); - return true; - } else if (get_app_config()->legacy_datadir()) { - // Looks like user has legacy pre-vendorbundle data directory, - // explain what this is and run the wizard - - MsgDataLegacy dlg; - dlg.ShowModal(); - - run_wizard(ConfigWizard::RR_DATA_LEGACY); - return true; - } - return false; -} - -bool GUI_App::check_updates(const bool verbose) -{ - PresetUpdater::UpdateResult updater_result; - try { - updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); - if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { - mainframe->Close(); - // Applicaiton is closing. - return false; - } - else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { - m_app_conf_exists = true; - } - else if (verbose && updater_result == PresetUpdater::R_NOOP) { - MsgNoUpdates dlg; - dlg.ShowModal(); - } - } - catch (const std::exception & ex) { - show_error(nullptr, ex.what()); - } - // Applicaiton will continue. - return true; -} - -bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) -{ - bool launch = true; - - // warning dialog containes a "Remember my choice" checkbox - std::string option_key = "suppress_hyperlinks"; - if (force_remember_choice || app_config->get(option_key).empty()) { - if (app_config->get(option_key).empty()) { - RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - dialog.ShowCheckBox(_L("Remember my choice")); - auto answer = dialog.ShowModal(); - launch = answer == wxID_YES; - if (dialog.IsCheckBoxChecked()) { - wxString preferences_item = _L("Suppress to open hyperlink in browser"); - wxString msg = - _L("PrusaSlicer will remember your choice.") + "\n\n" + - _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + - format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); - - MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); - if (msg_dlg.ShowModal() == wxID_CANCEL) - return false; - app_config->set(option_key, answer == wxID_NO ? "1" : "0"); - } - } - if (launch) - launch = app_config->get(option_key) != "1"; - } - // warning dialog doesn't containe a "Remember my choice" checkbox - // and will be shown only when "Suppress to open hyperlink in browser" is ON. - else if (app_config->get(option_key) == "1") { - MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - launch = dialog.ShowModal() == wxID_YES; - } - - return launch && wxLaunchDefaultBrowser(url, flags); -} - -// static method accepting a wxWindow object as first parameter -// void warning_catcher{ -// my($self, $message_dialog) = @_; -// return sub{ -// my $message = shift; -// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ; -// my @params = ($message, 'Warning', wxOK | wxICON_WARNING); -// $message_dialog -// ? $message_dialog->(@params) -// : Wx::MessageDialog->new($self, @params)->ShowModal; -// }; -// } - -// Do we need this function??? -// void GUI_App::notify(message) { -// auto frame = GetTopWindow(); -// // try harder to attract user attention on OS X -// if (!frame->IsActive()) -// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO); -// -// // There used to be notifier using a Growl application for OSX, but Growl is dead. -// // The notifier also supported the Linux X D - bus notifications, but that support was broken. -// //TODO use wxNotificationMessage ? -// } - - -#ifdef __WXMSW__ -void GUI_App::associate_3mf_files() -{ - associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); -} - -void GUI_App::associate_stl_files() -{ - associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); -} - -void GUI_App::associate_gcode_files() -{ - associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); -} -#endif // __WXMSW__ - -void GUI_App::on_version_read(wxCommandEvent& evt) -{ - app_config->set("version_online", into_u8(evt.GetString())); - app_config->save(); - std::string opt = app_config->get("notify_release"); - if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { - return; - } - if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { - return; - } - // notification - /* - this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) - , _u8L("See Download page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } - ); - */ - // updater - // read triggered_by_user that was set when calling GUI_App::app_version_check - app_updater(m_app_updater->get_triggered_by_user()); -} - -void GUI_App::app_updater(bool from_user) -{ - DownloadAppData app_data = m_app_updater->get_app_data(); - - if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION))) - { - BOOST_LOG_TRIVIAL(info) << "There is no newer version online."; - MsgNoAppUpdates no_update_dialog; - no_update_dialog.ShowModal(); - return; - - } - - assert(!app_data.url.empty()); - assert(!app_data.target_path.empty()); - - // dialog with new version info - AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); - auto dialog_result = dialog.ShowModal(); - // checkbox "do not show again" - if (dialog.disable_version_check()) { - app_config->set("notify_release", "none"); - } - // Doesn't wish to update - if (dialog_result != wxID_OK) { - return; - } - // dialog with new version download (installer or app dependent on system) including path selection - AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); - dialog_result = dwnld_dlg.ShowModal(); - // Doesn't wish to download - if (dialog_result != wxID_OK) { - return; - } - app_data.target_path =dwnld_dlg.get_download_path(); - - // start download - this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); - app_data.start_after = dwnld_dlg.run_after_download(); - m_app_updater->set_app_data(std::move(app_data)); - m_app_updater->sync_download(); -} - -void GUI_App::app_version_check(bool from_user) -{ - if (from_user) { - if (m_app_updater->get_download_ongoing()) { - MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO); - if (msgdlg.ShowModal() != wxID_YES) - return; - } - } - std::string version_check_url = app_config->version_check_url(); - m_app_updater->sync_version(version_check_url, from_user); -} - -void GUI_App::start_download(std::string url) -{ - if (!plater_) { - BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr."; - return; - } - //lets always init so if the download dest folder was changed, new dest is used - boost::filesystem::path dest_folder(app_config->get("url_downloader_dest")); - if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) { - std::string msg = _utf8("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard."); - BOOST_LOG_TRIVIAL(error) << msg; - show_error(nullptr, msg); - return; - } - m_downloader->init(dest_folder); - m_downloader->start_download(url); -} - -} // GUI -} //Slic3r +#include "libslic3r/Technologies.hpp" +#include "GUI_App.hpp" +#include "GUI_Init.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "GUI_Factories.hpp" +#include "format.hpp" +#include "I18N.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libslic3r/Utils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/I18N.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" + +#include "GUI.hpp" +#include "GUI_Utils.hpp" +#include "3DScene.hpp" +#include "MainFrame.hpp" +#include "Plater.hpp" +#include "GLCanvas3D.hpp" + +#include "../Utils/PresetUpdater.hpp" +#include "../Utils/PrintHost.hpp" +#include "../Utils/Process.hpp" +#include "../Utils/MacDarkMode.hpp" +#include "../Utils/AppUpdater.hpp" +#include "../Utils/WinRegistry.hpp" +#include "slic3r/Config/Snapshot.hpp" +#include "ConfigSnapshotDialog.hpp" +#include "FirmwareDialog.hpp" +#include "Preferences.hpp" +#include "Tab.hpp" +#include "SysInfoDialog.hpp" +#include "KBShortcutsDialog.hpp" +#include "UpdateDialogs.hpp" +#include "Mouse3DController.hpp" +#include "RemovableDriveManager.hpp" +#include "InstanceCheck.hpp" +#include "NotificationManager.hpp" +#include "UnsavedChangesDialog.hpp" +#include "SavePresetDialog.hpp" +#include "PrintHostDialogs.hpp" +#include "DesktopIntegrationDialog.hpp" +#include "SendSystemInfoDialog.hpp" +#include "Downloader.hpp" + +#include "BitmapCache.hpp" +#include "Notebook.hpp" + +#ifdef __WXMSW__ +#include +#include +#ifdef _MSW_DARK_MODE +#include +#endif // _MSW_DARK_MODE +#endif +#ifdef _WIN32 +#include +#endif + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG +#include +#include +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + +// Needed for forcing menu icons back under gtk2 and gtk3 +#if defined(__WXGTK20__) || defined(__WXGTK3__) + #include +#endif + +using namespace std::literals; + +namespace Slic3r { +namespace GUI { + +class MainFrame; + +class SplashScreen : public wxSplashScreen +{ +public: + SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition) + : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize, +#ifdef __APPLE__ + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP +#else + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR +#endif // !__APPLE__ + ) + { + wxASSERT(bitmap.IsOk()); + +// int init_dpi = get_dpi_for_window(this); + this->SetPosition(pos); + // The size of the SplashScreen can be hanged after its moving to another display + // So, update it from a bitmap size + this->SetClientSize(bitmap.GetWidth(), bitmap.GetHeight()); + this->CenterOnScreen(); +// int new_dpi = get_dpi_for_window(this); + +// m_scale = (float)(new_dpi) / (float)(init_dpi); + m_main_bitmap = bitmap; + +// scale_bitmap(m_main_bitmap, m_scale); + + // init constant texts and scale fonts + init_constant_text(); + + // this font will be used for the action string + m_action_font = m_constant_text.credits_font.Bold(); + + // draw logo and constant info text + Decorate(m_main_bitmap); + } + + void SetText(const wxString& text) + { + set_bitmap(m_main_bitmap); + if (!text.empty()) { + wxBitmap bitmap(m_main_bitmap); + + wxMemoryDC memDC; + memDC.SelectObject(bitmap); + + memDC.SetFont(m_action_font); + memDC.SetTextForeground(wxColour(237, 107, 33)); + memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position); + + memDC.SelectObject(wxNullBitmap); + set_bitmap(bitmap); +#ifdef __WXOSX__ + // without this code splash screen wouldn't be updated under OSX + wxYield(); +#endif + } + } + + static wxBitmap MakeBitmap(wxBitmap bmp) + { + if (!bmp.IsOk()) + return wxNullBitmap; + + // create dark grey background for the splashscreen + // It will be 5/3 of the weight of the bitmap + int width = lround((double)5 / 3 * bmp.GetWidth()); + int height = bmp.GetHeight(); + + wxImage image(width, height); + unsigned char* imgdata_ = image.GetData(); + for (int i = 0; i < width * height; ++i) { + *imgdata_++ = 51; + *imgdata_++ = 51; + *imgdata_++ = 51; + } + + wxBitmap new_bmp(image); + + wxMemoryDC memDC; + memDC.SelectObject(new_bmp); + memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true); + + return new_bmp; + } + + void Decorate(wxBitmap& bmp) + { + if (!bmp.IsOk()) + return; + + // draw text to the box at the left of the splashscreen. + // this box will be 2/5 of the weight of the bitmap, and be at the left. + int width = lround(bmp.GetWidth() * 0.4); + + // load bitmap for logo + BitmapCache bmp_cache; + int logo_size = lround(width * 0.25); + wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size); + + wxCoord margin = int(m_scale * 20); + + wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight())); + banner_rect.Deflate(margin, 2 * margin); + + // use a memory DC to draw directly onto the bitmap + wxMemoryDC memDc(bmp); + + // draw logo + memDc.DrawBitmap(logo_bmp, margin, margin, true); + + // draw the (white) labels inside of our black box (at the left of the splashscreen) + memDc.SetTextForeground(wxColour(255, 255, 255)); + + memDc.SetFont(m_constant_text.title_font); + memDc.DrawLabel(m_constant_text.title, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); + + int title_height = memDc.GetTextExtent(m_constant_text.title).GetY(); + banner_rect.SetTop(banner_rect.GetTop() + title_height); + banner_rect.SetHeight(banner_rect.GetHeight() - title_height); + + memDc.SetFont(m_constant_text.version_font); + memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); + int version_height = memDc.GetTextExtent(m_constant_text.version).GetY(); + + memDc.SetFont(m_constant_text.credits_font); + memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); + int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY(); + int text_height = memDc.GetTextExtent("text").GetY(); + + // calculate position for the dynamic text + int logo_and_header_height = margin + logo_size + title_height + version_height; + m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height); + } + +private: + wxBitmap m_main_bitmap; + wxFont m_action_font; + int m_action_line_y_position; + float m_scale {1.0}; + + struct ConstantText + { + wxString title; + wxString version; + wxString credits; + + wxFont title_font; + wxFont version_font; + wxFont credits_font; + + void init(wxFont init_font) + { + // title + title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; + + // dynamically get the version to display + version = _L("Version") + " " + std::string(SLIC3R_VERSION); + + // credits infornation + credits = title + " " + + _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + + _L("Developed by Prusa Research.") + "\n\n" + + title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + ".\n\n" + + _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + + _L("Artwork model by Creative Tools"); + + title_font = version_font = credits_font = init_font; + } + } + m_constant_text; + + void init_constant_text() + { + m_constant_text.init(get_default_font(this)); + + // As default we use a system font for current display. + // Scale fonts in respect to banner width + + int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins + + float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX(); + scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale); + + float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX(); + scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale); + + // The width of the credits information string doesn't respect to the banner width some times. + // So, scale credits_font in the respect to the longest string width + int longest_string_width = word_wrap_string(m_constant_text.credits); + float font_scale = (float)text_banner_width / longest_string_width; + scale_font(m_constant_text.credits_font, font_scale); + } + + void set_bitmap(wxBitmap& bmp) + { + m_window->SetBitmap(bmp); + m_window->Refresh(); + m_window->Update(); + } + + void scale_bitmap(wxBitmap& bmp, float scale) + { + if (scale == 1.0) + return; + + wxImage image = bmp.ConvertToImage(); + if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) + return; + + int width = int(scale * image.GetWidth()); + int height = int(scale * image.GetHeight()); + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + + bmp = wxBitmap(std::move(image)); + } + + void scale_font(wxFont& font, float scale) + { +#ifdef __WXMSW__ + // Workaround for the font scaling in respect to the current active display, + // not for the primary display, as it's implemented in Font.cpp + // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp + // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) + wxNativeFontInfo nfi= *font.GetNativeFontInfo(); + float pointSizeNew = wxDisplay(this).GetScaleFactor() * scale * font.GetPointSize(); + nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); + nfi.pointSize = pointSizeNew; + font = wxFont(nfi); +#else + font.Scale(scale); +#endif //__WXMSW__ + } + + // wrap a string for the strings no longer then 55 symbols + // return extent of the longest string + int word_wrap_string(wxString& input) + { + size_t line_len = 55;// count of symbols in one line + int idx = -1; + size_t cur_len = 0; + + wxString longest_sub_string; + auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) { + if (cur_len > longest_sub_str.Len()) + longest_sub_str = input.SubString(i - cur_len + 1, i); + }; + + for (size_t i = 0; i < input.Len(); i++) + { + cur_len++; + if (input[i] == ' ') + idx = i; + if (input[i] == '\n') + { + get_longest_sub_string(longest_sub_string, cur_len, i); + idx = -1; + cur_len = 0; + } + if (cur_len >= line_len && idx >= 0) + { + get_longest_sub_string(longest_sub_string, cur_len, i); + input[idx] = '\n'; + cur_len = i - static_cast(idx); + } + } + + return GetTextExtent(longest_sub_string).GetX(); + } +}; + + +#ifdef __linux__ +bool static check_old_linux_datadir(const wxString& app_name) { + // If we are on Linux and the datadir does not exist yet, look into the old + // location where the datadir was before version 2.3. If we find it there, + // tell the user that he might wanna migrate to the new location. + // (https://github.com/prusa3d/PrusaSlicer/issues/2911) + // To be precise, the datadir should exist, it is created when single instance + // lock happens. Instead of checking for existence, check the contents. + + namespace fs = boost::filesystem; + + std::string new_path = Slic3r::data_dir(); + + wxString dir; + if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) + dir = wxFileName::GetHomeDir() + wxS("/.config"); + std::string default_path = (dir + "/" + app_name).ToUTF8().data(); + + if (new_path != default_path) { + // This happens when the user specifies a custom --datadir. + // Do not show anything in that case. + return true; + } + + fs::path data_dir = fs::path(new_path); + if (! fs::is_directory(data_dir)) + return true; // This should not happen. + + int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator()); + + if (file_count <= 1) { // just cache dir with an instance lock + std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data(); + + if (fs::is_directory(old_path)) { + wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration " + "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n" + "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, " + "an old %1% configuration directory was detected in \n%3%.\n\n" + "Consider moving the contents of the old directory to the new location in order to access " + "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old " + "location again.\n\n" + "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); + wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str()); + RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); + dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application")); + if (dlg.ShowModal() != wxID_NO) + return false; + } + } else { + // If the new directory exists, be silent. The user likely already saw the message. + } + return true; +} +#endif + +#ifdef _WIN32 +#if 0 // External Updater is replaced with AppUpdater.cpp +static bool run_updater_win() +{ + // find updater exe + boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe"; + // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst + std::string msg; + bool res = create_process(path_updater, L"/silent", msg); + if (!res) + BOOST_LOG_TRIVIAL(error) << msg; + return res; +} +#endif // 0 +#endif // _WIN32 + +struct FileWildcards { + std::string_view title; + std::vector file_extensions; +}; + + + +static const FileWildcards file_wildcards_by_type[FT_SIZE] = { + /* FT_STL */ { "STL files"sv, { ".stl"sv } }, + /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, + /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, + /* FT_STEP */ { "STEP files"sv, { ".stp"sv, ".step"sv } }, + /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, + /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, + /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, + /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv, ".step"sv, ".stp"sv } }, + /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, + /* FT_FONTS */ { "Font files"sv, { ".ttc"sv, ".ttf"sv } }, + /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, + + /* FT_INI */ { "INI files"sv, { ".ini"sv } }, + /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, + + /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, + + /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, +}; + +#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR +wxString file_wildcards(FileType file_type) +{ + const FileWildcards& data = file_wildcards_by_type[file_type]; + std::string title; + std::string mask; + + // Generate cumulative first item + for (const std::string_view& ext : data.file_extensions) { + if (title.empty()) { + title = "*"; + title += ext; + mask = title; + } + else { + title += ", *"; + title += ext; + mask += ";*"; + mask += ext; + } + mask += ";*"; + mask += boost::to_upper_copy(std::string(ext)); + } + + wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); + + // Adds an item for each of the extensions + if (data.file_extensions.size() > 1) { + for (const std::string_view& ext : data.file_extensions) { + title = "*"; + title += ext; + ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title); + } + } + + return ret; +} +#else +// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. +// The function accepts a custom extension parameter. If the parameter is provided, the custom extension +// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips +// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template). +wxString file_wildcards(FileType file_type, const std::string &custom_extension) +{ + const FileWildcards& data = file_wildcards_by_type[file_type]; + std::string title; + std::string mask; + std::string custom_ext_lower; + + if (! custom_extension.empty()) { + // Generate an extension into the title mask and into the list of extensions. + custom_ext_lower = boost::to_lower_copy(custom_extension); + const std::string custom_ext_upper = boost::to_upper_copy(custom_extension); + if (custom_ext_lower == custom_extension) { + // Add a lower case version. + title = std::string("*") + custom_ext_lower; + mask = title; + // Add an upper case version. + mask += ";*"; + mask += custom_ext_upper; + } else if (custom_ext_upper == custom_extension) { + // Add an upper case version. + title = std::string("*") + custom_ext_upper; + mask = title; + // Add a lower case version. + mask += ";*"; + mask += custom_ext_lower; + } else { + // Add the mixed case version only. + title = std::string("*") + custom_extension; + mask = title; + } + } + + for (const std::string_view &ext : data.file_extensions) + // Only add an extension if it was not added first as the custom extension. + if (ext != custom_ext_lower) { + if (title.empty()) { + title = "*"; + title += ext; + mask = title; + } else { + title += ", *"; + title += ext; + mask += ";*"; + mask += ext; + } + mask += ";*"; + mask += boost::to_upper_copy(std::string(ext)); + } + + return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); +} +#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR + +static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } + +#ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) +static void register_win32_dpi_event() +{ + enum { WM_DPICHANGED_ = 0x02e0 }; + + wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { + const int dpi = wParam & 0xffff; + const auto rect = reinterpret_cast(lParam); + const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right)); + + DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect); + win->GetEventHandler()->AddPendingEvent(evt); + + return true; + }); +} +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN + +static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; + +static void register_win32_device_notification_event() +{ + wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. + auto main_frame = dynamic_cast(win); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); + if (plater == nullptr) + // Maybe some other top level window like a dialog or maybe a pop-up menu? + return true; + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + switch (wParam) { + case DBT_DEVICEARRIVAL: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); + else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; +// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) { +// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name); + if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) + plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name))); + } + break; + case DBT_DEVICEREMOVECOMPLETE: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); + else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; +// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) +// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name); + if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) + plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name))); + } + break; + default: + break; + } + return true; + }); + + wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. + auto main_frame = dynamic_cast(win); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); + if (plater == nullptr) + // Maybe some other top level window like a dialog or maybe a pop-up menu? + return true; + wchar_t sPath[MAX_PATH]; + if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) { + struct _ITEMIDLIST* pidl = *reinterpret_cast(wParam); + if (! SHGetPathFromIDList(pidl, sPath)) { + BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed"; + return false; + } + } + switch (lParam) { + case SHCNE_MEDIAINSERTED: + { + //printf("SHCNE_MEDIAINSERTED %S\n", sPath); + plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); + break; + } + case SHCNE_MEDIAREMOVED: + { + //printf("SHCNE_MEDIAREMOVED %S\n", sPath); + plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); + break; + } + default: +// printf("Unknown\n"); + break; + } + return true; + }); + + wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + auto main_frame = dynamic_cast(Slic3r::GUI::find_toplevel_parent(win)); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); +// if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) { + if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) { + RAWINPUT raw; + UINT rawSize = sizeof(RAWINPUT); + ::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); + if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid)) + return true; + } + return false; + }); + + wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + COPYDATASTRUCT* copy_data_structure = { 0 }; + copy_data_structure = (COPYDATASTRUCT*)lParam; + if (copy_data_structure->dwData == 1) { + LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData; + Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments)); + } + return true; + }); +} +#endif // WIN32 + +static void generic_exception_handle() +{ + // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage + // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0 + // + // wxLogError typically goes around exception handling and display an error dialog some time + // after an error is logged even if exception handling and OnExceptionInMainLoop() take place. + // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates + // errors if multiple have been collected and displays just one error message for all of them. + // Otherwise we would get multiple error messages for one missing png, for example. + // + // If a custom error message window (or some other solution) were to be used, it would be necessary + // to turn off wxLogError() usage in wx APIs, most notably in wxImage + // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a + + try { + throw; + } catch (const std::bad_alloc& ex) { + // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) + // and terminate the app so it is at least certain to happen now. + wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME); + wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR); + BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); + std::terminate(); + } catch (const boost::io::bad_format_string& ex) { + wxString errmsg = _L("PrusaSlicer has encountered a localization error. " + "Please report to PrusaSlicer team, what language was active and in which scenario " + "this issue happened. Thank you.\n\nThe application will now terminate."); + wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR); + BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); + std::terminate(); + throw; + } catch (const std::exception& ex) { + wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what())); + BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); + throw; + } +} + +void GUI_App::post_init() +{ + assert(initialized()); + if (! this->initialized()) + throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); + + if (this->is_gcode_viewer()) { + if (! this->init_params->input_files.empty()) + this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); + } + else if (this->init_params->start_downloader) { + start_download(this->init_params->download_url); + } else { + if (! this->init_params->preset_substitutions.empty()) + show_substitutions_info(this->init_params->preset_substitutions); + +#if 0 + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + this->gui->mainframe->load_config(m_print_config); +#endif + if (! this->init_params->load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + this->mainframe->load_config_file(this->init_params->load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (!this->init_params->input_files.empty()) { + const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); + if (!res.empty() && this->init_params->input_files.size() == 1) { + // Update application titlebar when opening a project file + const std::string& filename = this->init_params->input_files.front(); + if (boost::algorithm::iends_with(filename, ".amf") || + boost::algorithm::iends_with(filename, ".amf.xml") || + boost::algorithm::iends_with(filename, ".3mf")) + this->plater()->set_project_filename(from_u8(filename)); + } + if (this->init_params->delete_after_load) { + for (const std::string& p : this->init_params->input_files) { + boost::system::error_code ec; + boost::filesystem::remove(boost::filesystem::path(p), ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << ec.message(); + } + } + } + } + if (! this->init_params->extra_config.empty()) + this->mainframe->load_config(this->init_params->extra_config); + } + + // show "Did you know" notification + if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) + plater_->get_notification_manager()->push_hint_notification(true); + + // The extra CallAfter() is needed because of Mac, where this is the only way + // to popup a modal dialog on start without screwing combo boxes. + // This is ugly but I honestly found no better way to do it. + // Neither wxShowEvent nor wxWindowCreateEvent work reliably. + if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. + if (! this->check_updates(false)) + // Configuration is not compatible and reconfigure was refused by the user. Application is closing. + return; + CallAfter([this] { + // preset_updater->sync downloads profile updates on background so it must begin after config wizard finished. + bool cw_showed = this->config_wizard_startup(); + this->preset_updater->sync(preset_bundle); + this->app_version_check(false); + if (! cw_showed) { + // The CallAfter is needed as well, without it, GL extensions did not show. + // Also, we only want to show this when the wizard does not, so the new user + // sees something else than "we want something" on the first start. + show_send_system_info_dialog_if_needed(); + } + }); + } + + // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini. + app_config->set("version", SLIC3R_VERSION); + app_config->save(); + +#ifdef _WIN32 + // Sets window property to mainframe so other instances can indentify it. + OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); +#endif //WIN32 +} + +IMPLEMENT_APP(GUI_App) + +GUI_App::GUI_App(EAppMode mode) + : wxApp() + , m_app_mode(mode) + , m_em_unit(10) + , m_imgui(new ImGuiWrapper()) + , m_removable_drive_manager(std::make_unique()) + , m_other_instance_message_handler(std::make_unique()) + , m_downloader(std::make_unique()) +{ + //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp + this->init_app_config(); + // init app downloader after path to datadir is set + m_app_updater = std::make_unique(); +} + +GUI_App::~GUI_App() +{ + if (app_config != nullptr) + delete app_config; + + if (preset_bundle != nullptr) + delete preset_bundle; + + if (preset_updater != nullptr) + delete preset_updater; +} + +// If formatted for github, plaintext with OpenGL extensions enclosed into
. +// Otherwise HTML formatted for the system info dialog. +std::string GUI_App::get_gl_info(bool for_github) +{ + return OpenGLManager::get_gl_info().to_string(for_github); +} + +wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) +{ +#if ENABLE_GL_CORE_PROFILE +#if ENABLE_OPENGL_DEBUG_OPTION + return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0), + init_params != nullptr ? init_params->opengl_debug : false); +#else + return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0)); +#endif // ENABLE_OPENGL_DEBUG_OPTION +#else + return m_opengl_mgr.init_glcontext(canvas); +#endif // ENABLE_GL_CORE_PROFILE +} + +bool GUI_App::init_opengl() +{ +#ifdef __linux__ + bool status = m_opengl_mgr.init_gl(); + m_opengl_initialized = true; + return status; +#else + return m_opengl_mgr.init_gl(); +#endif +} + +// gets path to PrusaSlicer.ini, returns semver from first line comment +static boost::optional parse_semver_from_ini(std::string path) +{ + std::ifstream stream(path); + std::stringstream buffer; + buffer << stream.rdbuf(); + std::string body = buffer.str(); + size_t start = body.find("PrusaSlicer "); + if (start == std::string::npos) + return boost::none; + body = body.substr(start + 12); + size_t end = body.find_first_of(" \n"); + if (end < body.size()) + body.resize(end); + return Semver::parse(body); +} + +void GUI_App::init_app_config() +{ + // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. + +// SetAppName(SLIC3R_APP_KEY); + SetAppName(SLIC3R_APP_KEY "-alpha"); +// SetAppName(SLIC3R_APP_KEY "-beta"); + + +// SetAppDisplayName(SLIC3R_APP_NAME); + + // Set the Slic3r data directory at the Slic3r XS module. + // Unix: ~/ .Slic3r + // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" + // Mac : "~/Library/Application Support/Slic3r" + + if (data_dir().empty()) { + #ifndef __linux__ + set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); + #else + // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. + // https://github.com/prusa3d/PrusaSlicer/issues/2911 + wxString dir; + if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) + dir = wxFileName::GetHomeDir() + wxS("/.config"); + set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); + #endif + } else { + m_datadir_redefined = true; + } + + if (!app_config) + app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer); + + // load settings + m_app_conf_exists = app_config->exists(); + if (m_app_conf_exists) { + std::string error = app_config->load(); + if (!error.empty()) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + if (is_editor()) { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + else { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + } + } +} + +// returns old config path to copy from if such exists, +// returns an empty string if such config path does not exists or if it cannot be loaded. +std::string GUI_App::check_older_app_config(Semver current_version, bool backup) +{ + std::string older_data_dir_path; + + // If the config folder is redefined - do not check + if (m_datadir_redefined) + return {}; + + // find other version app config (alpha / beta / release) + std::string config_path = app_config->config_path(); + boost::filesystem::path parent_file_path(config_path); + std::string filename = parent_file_path.filename().string(); + parent_file_path.remove_filename().remove_filename(); + + std::vector candidates; + + if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename); + if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename); + if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename); + + Semver last_semver = current_version; + for (const auto& candidate : candidates) { + if (boost::filesystem::exists(candidate)) { + // parse + boost::optionalother_semver = parse_semver_from_ini(candidate.string()); + if (other_semver && *other_semver > last_semver) { + last_semver = *other_semver; + older_data_dir_path = candidate.parent_path().string(); + } + } + } + if (older_data_dir_path.empty()) + return {}; + BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path; + // ask about using older data folder + + InfoDialog msg(nullptr + , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION) + , backup ? + format_wxstr(_L( + "The active configuration was created by %1% %2%," + "\nwhile a newer configuration was found in %3%" + "\ncreated by %1% %4%." + "\n\nShall the newer configuration be imported?" + "\nIf so, your active configuration will be backed up before importing the new configuration." + ) + , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string()) + : format_wxstr(_L( + "An existing configuration was found in %3%" + "\ncreated by %1% %2%." + "\n\nShall this configuration be imported?" + ) + , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path) + , true, wxYES_NO); + + if (backup) { + msg.SetButtonLabel(wxID_YES, _L("Import")); + msg.SetButtonLabel(wxID_NO, _L("Don't import")); + } + + if (msg.ShowModal() == wxID_YES) { + std::string snapshot_id; + if (backup) { + const Config::Snapshot* snapshot{ nullptr }; + if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "", + _u8L("Continue and import newer configuration?"), &snapshot)) + return {}; + if (snapshot) { + // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail. + snapshot_id = snapshot->id; + assert(! snapshot_id.empty()); + app_config->set("on_snapshot", snapshot_id); + } else + BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot"; + } + + // load app config from older file + std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string()); + if (!error.empty()) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + if (is_editor()) { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + else { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + } + if (!snapshot_id.empty()) + app_config->set("on_snapshot", snapshot_id); + m_app_conf_exists = true; + return older_data_dir_path; + } + return {}; +} + +void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) +{ + BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; + m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); +} + +bool GUI_App::OnInit() +{ + try { + return on_init_inner(); + } catch (const std::exception&) { + generic_exception_handle(); + return false; + } +} + +bool GUI_App::on_init_inner() +{ + // Set initialization of image handlers before any UI actions - See GH issue #7469 + wxInitAllImageHandlers(); + +#if defined(_WIN32) && ! defined(_WIN64) + // Win32 32bit build. + if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { + RichMessageDialog dlg(nullptr, + _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows." + "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system." + "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/." + "\nDo you wish to continue?"), + "PrusaSlicer", wxICON_QUESTION | wxYES_NO); + if (dlg.ShowModal() != wxID_YES) + return false; + } +#endif // _WIN64 + + // Forcing back menu icons under gtk2 and gtk3. Solution is based on: + // https://docs.gtk.org/gtk3/class.Settings.html + // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb + // TODO: Find workaround for GTK4 +#if defined(__WXGTK20__) || defined(__WXGTK3__) + g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL); +#endif + + // Verify resources path + const wxString resources_dir = from_u8(Slic3r::resources_dir()); + wxCHECK_MSG(wxDirExists(resources_dir), false, + wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); + +#ifdef __linux__ + if (! check_old_linux_datadir(GetAppName())) { + std::cerr << "Quitting, user chose to move their data to new location." << std::endl; + return false; + } +#endif + + // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. +// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); + // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible + // performance when working on high resolution multi-display setups. +// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); + +// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; + + // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action. + // Like here, before the show InfoDialog in check_older_app_config() + + // If load_language() fails, the application closes. + load_language(wxString(), true); +#ifdef _MSW_DARK_MODE + bool init_dark_color_mode = app_config->get("dark_color_mode") == "1"; + bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; + NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled); +#endif + // initialize label colors and fonts + init_ui_colours(); + init_fonts(); + + std::string older_data_dir_path; + if (m_app_conf_exists) { + if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION)) + // Only copying configuration if it was saved with a newer slicer than the one currently running. + older_data_dir_path = check_older_app_config(app_config->orig_version(), true); + } else { + // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration. + older_data_dir_path = check_older_app_config(Semver(), false); + } + +#ifdef _MSW_DARK_MODE + // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed + if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1"; + init_dark_color_mode != new_dark_color_mode) { + NppDarkMode::SetDarkMode(new_dark_color_mode); + init_ui_colours(); + update_ui_colours_from_appconfig(); + } + if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; + init_sys_menu_enabled != new_sys_menu_enabled) + NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled); +#endif + + if (is_editor()) { + std::string msg = Http::tls_global_init(); + std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); + bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); + + if (!msg.empty() && !ssl_accept) { + RichMessageDialog + dlg(nullptr, + wxString::Format(_L("%s\nDo you want to continue?"), msg), + "PrusaSlicer", wxICON_QUESTION | wxYES_NO); + dlg.ShowCheckBox(_L("Remember my choice")); + if (dlg.ShowModal() != wxID_YES) return false; + + app_config->set("tls_cert_store_accepted", + dlg.IsCheckBoxChecked() ? "yes" : "no"); + app_config->set("tls_accepted_cert_store_location", + dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); + } + } + + SplashScreen* scrn = nullptr; + if (app_config->get("show_splash_screen") == "1") { + // make a bitmap with dark grey banner on the left side + wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); + + // Detect position (display) to show the splash screen + // Now this position is equal to the mainframe position + wxPoint splashscreen_pos = wxDefaultPosition; + bool default_splashscreen_pos = true; + if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") { + auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); + default_splashscreen_pos = metrics == boost::none; + if (!default_splashscreen_pos) + splashscreen_pos = metrics->get_rect().GetPosition(); + } + + if (!default_splashscreen_pos) { + // workaround for crash related to the positioning of the window on secondary monitor + get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos"); + get_app_config()->save(); + } + + // create splash screen with updated bmp + scrn = new SplashScreen(bmp.IsOk() ? bmp : get_bmp_bundle("PrusaSlicer", 400)->GetPreferredBitmapSizeAtScale(1.0), + wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); + + if (!default_splashscreen_pos) + // revert "restore_win_position" value if application wasn't crashed + get_app_config()->set("restore_win_position", "1"); +#ifndef __linux__ + wxYield(); +#endif + scrn->SetText(_L("Loading configuration")+ dots); + } + + preset_bundle = new PresetBundle(); + + // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory + // supplied as argument to --datadir; in that case we should still run the wizard + preset_bundle->setup_directories(); + + if (! older_data_dir_path.empty()) { + preset_bundle->import_newer_configs(older_data_dir_path); + app_config->save(); + } + + if (is_editor()) { +#ifdef __WXMSW__ + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); +#endif // __WXMSW__ + + preset_updater = new PresetUpdater(); + Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); + Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { + std::string evt_string = into_u8(evt.GetString()); + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { + auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); + this->plater_->get_notification_manager()->push_notification( notif_type + , NotificationManager::NotificationLevel::ImportantNotificationLevel + , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) + , _u8L("See Releases page.") + , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; } + ); + } + } + }); + Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) { + //lm:This does not force a render. The progress bar only updateswhen the mouse is moved. + if (this->plater_ != nullptr) + this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f ); + }); + + Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) { + if (this->plater_ != nullptr) + this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload); + if(!evt.GetString().IsEmpty()) + show_error(nullptr, evt.GetString()); + }); + + Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { + show_error(nullptr, evt.GetString()); + }); + + } + else { +#ifdef __WXMSW__ + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); +#endif // __WXMSW__ + } + + std::string delayed_error_load_presets; + // Suppress the '- default -' presets. + preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); + try { + // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only. + // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force + // installation of a compatible system preset, thus nullifying the system preset substitutions. + init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); + } catch (const std::exception &ex) { + delayed_error_load_presets = ex.what(); + } + +#ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + register_win32_dpi_event(); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN + register_win32_device_notification_event(); +#endif // WIN32 + + // Let the libslic3r know the callback, which will translate messages on demand. + Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); + + // application frame + if (scrn && is_editor()) + scrn->SetText(_L("Preparing settings tabs") + dots); + + if (!delayed_error_load_presets.empty()) + show_error(nullptr, delayed_error_load_presets); + + mainframe = new MainFrame(); + // hide settings tabs after first Layout + if (is_editor()) + mainframe->select_tab(size_t(0)); + + sidebar().obj_list()->init_objects(); // propagate model objects to object list +// update_mode(); // !!! do that later + SetTopWindow(mainframe); + + plater_->init_notification_manager(); + + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); + + if (is_gcode_viewer()) { + mainframe->update_layout(); + if (plater_ != nullptr) + // ensure the selected technology is ptFFF + plater_->set_printer_technology(ptFFF); + } + else + load_current_presets(); + + // Save the active profiles as a "saved into project". + update_saved_preset_from_current_preset(); + + if (plater_ != nullptr) { + // Save the names of active presets and project specific config into ProjectDirtyStateManager. + plater_->reset_project_dirty_initial_presets(); + // Update Project dirty state, update application title bar. + plater_->update_project_dirty_from_presets(); + } + + mainframe->Show(true); + + obj_list()->set_min_height(); + + update_mode(); // update view mode after fix of the object_list size + +#ifdef __APPLE__ + other_instance_message_handler()->bring_instance_forward(); +#endif //__APPLE__ + + Bind(wxEVT_IDLE, [this](wxIdleEvent& event) + { + if (! plater_) + return; + + this->obj_manipul()->update_if_dirty(); + + // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT + // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. +#ifdef __linux__ + if (! m_post_initialized && m_opengl_initialized) { +#else + if (! m_post_initialized) { +#endif + m_post_initialized = true; +#ifdef WIN32 + this->mainframe->register_win32_callbacks(); +#endif + this->post_init(); + } + + if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1") + app_config->save(); + }); + + m_initialized = true; + + if (const std::string& crash_reason = app_config->get("restore_win_position"); + boost::starts_with(crash_reason,"crashed")) + { + wxString preferences_item = _L("Restore window position on start"); + InfoDialog dialog(nullptr, + _L("PrusaSlicer started after a crash"), + format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n" + "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n" + "More precise reason for the crash: \"%1%\".\n" + "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n" + "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". " + "Otherwise, the application will most likely crash again next time."), + "" + from_u8(crash_reason) + "", + "#2939", + "#5573", + "" + preferences_item + ""), + true, wxYES_NO); + + dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item)); + dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item)); + + auto answer = dialog.ShowModal(); + if (answer == wxID_YES) + app_config->set("restore_win_position", "0"); + else if (answer == wxID_NO) + app_config->set("restore_win_position", "1"); + app_config->save(); + } + + return true; +} + +unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) +{ + double r = colour.Red(); + double g = colour.Green(); + double b = colour.Blue(); + + return std::round(std::sqrt( + r * r * .241 + + g * g * .691 + + b * b * .068 + )); +} + +bool GUI_App::dark_mode() +{ +#if __APPLE__ + // The check for dark mode returns false positive on 10.12 and 10.13, + // which allowed setting dark menu bar and dock area, which is + // is detected as dark mode. We must run on at least 10.14 where the + // proper dark mode was first introduced. + return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode(); +#else + if (wxGetApp().app_config->has("dark_color_mode")) + return wxGetApp().app_config->get("dark_color_mode") == "1"; + return check_dark_mode(); +#endif +} + +const wxColour GUI_App::get_label_default_clr_system() +{ + return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); +} + +const wxColour GUI_App::get_label_default_clr_modified() +{ + return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1); +} + +const std::vector GUI_App::get_mode_default_palette() +{ + return { "#7DF028", "#FFDC00", "#E70000" }; +} + +void GUI_App::init_ui_colours() +{ + m_color_label_modified = get_label_default_clr_modified(); + m_color_label_sys = get_label_default_clr_system(); + m_mode_palette = get_mode_default_palette(); + + bool is_dark_mode = dark_mode(); +#ifdef _WIN32 + m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); + m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); + m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); + m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); + m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); +#else + m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); +#endif + m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); +} + +void GUI_App::update_ui_colours_from_appconfig() +{ + // load label colors + if (app_config->has("label_clr_sys")) { + auto str = app_config->get("label_clr_sys"); + if (!str.empty()) + m_color_label_sys = wxColour(str); + } + + if (app_config->has("label_clr_modified")) { + auto str = app_config->get("label_clr_modified"); + if (!str.empty()) + m_color_label_modified = wxColour(str); + } + + // load mode markers colors + if (app_config->has("mode_palette")) { + const auto colors = app_config->get("mode_palette"); + if (!colors.empty()) { + m_mode_palette.clear(); + if (!unescape_strings_cstyle(colors, m_mode_palette)) + m_mode_palette = get_mode_default_palette(); + } + } +} + +void GUI_App::update_label_colours() +{ + for (Tab* tab : tabs_list) + tab->update_label_colours(); +} + +#ifdef _WIN32 +static bool is_focused(HWND hWnd) +{ + HWND hFocusedWnd = ::GetFocus(); + return hFocusedWnd && hWnd == hFocusedWnd; +} + +static bool is_default(wxWindow* win) +{ + wxTopLevelWindow* tlw = find_toplevel_parent(win); + if (!tlw) + return false; + + return win == tlw->GetDefaultItem(); +} +#endif + +void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) +{ +#ifdef _WIN32 + bool is_focused_button = false; + bool is_default_button = false; + if (wxButton* btn = dynamic_cast(window)) { + if (!(btn->GetWindowStyle() & wxNO_BORDER)) { + btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER); + highlited = true; + } + // button marking + { + auto mark_button = [this, btn, highlited](const bool mark) { + if (btn->GetLabel().IsEmpty()) + btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default); + else + btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default)); + btn->Refresh(); + btn->Update(); + }; + + // hovering + btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); + btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); + // focusing + btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); + btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); + + is_focused_button = is_focused(btn->GetHWND()); + is_default_button = is_default(btn); + if (is_focused_button || is_default_button) + mark_button(is_focused_button); + } + } + else if (wxTextCtrl* text = dynamic_cast(window)) { + if (text->GetBorder() != wxBORDER_SIMPLE) + text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); + } + else if (wxCheckListBox* list = dynamic_cast(window)) { + list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); + list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + for (size_t i = 0; i < list->GetCount(); i++) + if (wxOwnerDrawn* item = list->GetItem(i)) { + item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + item->SetTextColour(m_color_label_default); + } + return; + } + else if (dynamic_cast(window)) + window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); + + if (!just_font) + window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + if (!is_focused_button && !is_default_button) + window->SetForegroundColour(m_color_label_default); +#endif +} + +// recursive function for scaling fonts for all controls in Window +#ifdef _WIN32 +static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false) +{ + bool is_btn = dynamic_cast(window) != nullptr; + if (!(just_buttons_update && !is_btn)) + wxGetApp().UpdateDarkUI(window, is_btn); + + auto children = window->GetChildren(); + for (auto child : children) { + update_dark_children_ui(child); + } +} +#endif + +// Note: Don't use this function for Dialog contains ScalableButtons +void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/) +{ +#ifdef _WIN32 + update_dark_children_ui(dlg, just_buttons_update); +#endif +} +void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) +{ +#ifdef _WIN32 + UpdateDarkUI(dvc, highlited ? dark_mode() : false); +#ifdef _MSW_DARK_MODE + dvc->RefreshHeaderDarkMode(&m_normal_font); +#endif //_MSW_DARK_MODE + if (dvc->HasFlag(wxDV_ROW_LINES)) + dvc->SetAlternateRowColour(m_color_highlight_default); + if (dvc->GetBorder() != wxBORDER_SIMPLE) + dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE); +#endif +} + +void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent) +{ +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(parent); + + auto children = parent->GetChildren(); + for (auto child : children) { + if (dynamic_cast(child)) + child->SetForegroundColour(m_color_label_default); + } +#endif +} + +void GUI_App::init_fonts() +{ + m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); + m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + +#ifdef __WXMAC__ + m_small_font.SetPointSize(11); + m_bold_font.SetPointSize(13); +#endif /*__WXMAC__*/ + + // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as + // DEFAULT in wxGtk. Use the TELETYPE family as a work-around + m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); + m_code_font.SetPointSize(m_normal_font.GetPointSize()); +} + +void GUI_App::update_fonts(const MainFrame *main_frame) +{ + /* Only normal and bold fonts are used for an application rescale, + * because of under MSW small and normal fonts are the same. + * To avoid same rescaling twice, just fill this values + * from rescaled MainFrame + */ + if (main_frame == nullptr) + main_frame = this->mainframe; + m_normal_font = main_frame->normal_font(); + m_small_font = m_normal_font; + m_bold_font = main_frame->normal_font().Bold(); + m_link_font = m_bold_font.Underlined(); + m_em_unit = main_frame->em_unit(); + m_code_font.SetPointSize(m_normal_font.GetPointSize()); +} + +void GUI_App::set_label_clr_modified(const wxColour& clr) +{ + if (m_color_label_modified == clr) + return; + m_color_label_modified = clr; + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + app_config->set("label_clr_modified", str); + app_config->save(); +} + +void GUI_App::set_label_clr_sys(const wxColour& clr) +{ + if (m_color_label_sys == clr) + return; + m_color_label_sys = clr; + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + app_config->set("label_clr_sys", str); + app_config->save(); +} + +const std::string& GUI_App::get_mode_btn_color(int mode_id) +{ + assert(0 <= mode_id && size_t(mode_id) < m_mode_palette.size()); + return m_mode_palette[mode_id]; +} + +std::vector GUI_App::get_mode_palette() +{ + return { wxColor(m_mode_palette[0]), + wxColor(m_mode_palette[1]), + wxColor(m_mode_palette[2]) }; +} + +void GUI_App::set_mode_palette(const std::vector& palette) +{ + bool save = false; + + for (size_t mode = 0; mode < palette.size(); ++mode) { + const wxColour& clr = palette[mode]; + std::string color_str = clr == wxTransparentColour ? std::string("") : encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + if (m_mode_palette[mode] != color_str) { + m_mode_palette[mode] = color_str; + save = true; + } + } + + if (save) { + mainframe->update_mode_markers(); + app_config->set("mode_palette", escape_strings_cstyle(m_mode_palette)); + app_config->save(); + } +} + +bool GUI_App::tabs_as_menu() const +{ + return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); +} + +wxSize GUI_App::get_min_size() const +{ + return wxSize(76*m_em_unit, 49 * m_em_unit); +} + +float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const +{ +#ifdef __APPLE__ + const float icon_sc = 1.0f; // for Retina display will be used its own scale +#else + const float icon_sc = m_em_unit*0.1f; +#endif // __APPLE__ + + const std::string& use_val = app_config->get("use_custom_toolbar_size"); + const std::string& val = app_config->get("custom_toolbar_size"); + const std::string& auto_val = app_config->get("auto_toolbar_size"); + + if (val.empty() || auto_val.empty() || use_val.empty()) + return icon_sc; + + int int_val = use_val == "0" ? 100 : atoi(val.c_str()); + // correct value in respect to auto_toolbar_size + int_val = std::min(atoi(auto_val.c_str()), int_val); + + if (is_limited && int_val < 50) + int_val = 50; + + return 0.01f * int_val * icon_sc; +} + +void GUI_App::set_auto_toolbar_icon_scale(float scale) const +{ +#ifdef __APPLE__ + const float icon_sc = 1.0f; // for Retina display will be used its own scale +#else + const float icon_sc = m_em_unit * 0.1f; +#endif // __APPLE__ + + long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); + std::string val = std::to_string(int_val); + + app_config->set("auto_toolbar_size", val); +} + +// check user printer_presets for the containing information about "Print Host upload" +void GUI_App::check_printer_presets() +{ + std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); + if (preset_names.empty()) + return; + + wxString msg_text = _L("You have the following presets with saved options for \"Print Host upload\"") + ":"; + for (const std::string& preset_name : preset_names) + msg_text += "\n \"" + from_u8(preset_name) + "\","; + msg_text.RemoveLast(); + msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" + "Settings will be available in physical printers settings.") + "\n\n" + + _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" + "Note: This name can be changed later from the physical printers settings"); + + //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); + MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); + + preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); +} + +void GUI_App::recreate_GUI(const wxString& msg_name) +{ + m_is_recreating_gui = true; + + mainframe->shutdown(); + + wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); + dlg.Pulse(); + dlg.Update(10, _L("Recreating") + dots); + + MainFrame *old_main_frame = mainframe; + mainframe = new MainFrame(); + if (is_editor()) + // hide settings tabs after first Layout + mainframe->select_tab(size_t(0)); + // Propagate model objects to object list. + sidebar().obj_list()->init_objects(); + SetTopWindow(mainframe); + + dlg.Update(30, _L("Recreating") + dots); + old_main_frame->Destroy(); + + dlg.Update(80, _L("Loading of current presets") + dots); + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); + load_current_presets(); + mainframe->Show(true); + + dlg.Update(90, _L("Loading of a mode view") + dots); + + obj_list()->set_min_height(); + update_mode(); + + // #ys_FIXME_delete_after_testing Do we still need this ? +// CallAfter([]() { +// // Run the config wizard, don't offer the "reset user profile" checkbox. +// config_wizard_startup(true); +// }); + + m_is_recreating_gui = false; +} + +void GUI_App::system_info() +{ + SysInfoDialog dlg; + dlg.ShowModal(); +} + +void GUI_App::keyboard_shortcuts() +{ + KBShortcutsDialog dlg; + dlg.ShowModal(); +} + +// static method accepting a wxWindow object as first parameter +bool GUI_App::catch_error(std::function cb, + // wxMessageDialog* message_dialog, + const std::string& err /*= ""*/) +{ + if (!err.empty()) { + if (cb) + cb(); + // if (message_dialog) + // message_dialog->(err, "Error", wxOK | wxICON_ERROR); + show_error(/*this*/nullptr, err); + return true; + } + return false; +} + +// static method accepting a wxWindow object as first parameter +void fatal_error(wxWindow* parent) +{ + show_error(parent, ""); + // exit 1; // #ys_FIXME +} + +#ifdef _WIN32 + +#ifdef _MSW_DARK_MODE +static void update_scrolls(wxWindow* window) +{ + wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst(); + while (node) + { + wxWindow* win = node->GetData(); + if (dynamic_cast(win) || + dynamic_cast(win) || + dynamic_cast(win)) + NppDarkMode::SetDarkExplorerTheme(win->GetHWND()); + + update_scrolls(win); + node = node->GetNext(); + } +} +#endif //_MSW_DARK_MODE + + +#ifdef _MSW_DARK_MODE +void GUI_App::force_menu_update() +{ + NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1"); +} +#endif //_MSW_DARK_MODE + +void GUI_App::force_colors_update() +{ +#ifdef _MSW_DARK_MODE + NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); + if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl()) + NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND); + NppDarkMode::SetDarkTitleBar(mainframe->GetHWND()); + NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND()); +#endif //_MSW_DARK_MODE + m_force_colors_update = true; +} +#endif //_WIN32 + +// Called after the Preferences dialog is closed and the program settings are saved. +// Update the UI based on the current preferences. +void GUI_App::update_ui_from_settings() +{ + update_label_colours(); +#ifdef _WIN32 + // Upadte UI colors before Update UI from settings + if (m_force_colors_update) { + m_force_colors_update = false; + mainframe->force_color_changed(); + mainframe->diff_dialog.force_color_changed(); + mainframe->preferences_dialog->force_color_changed(); + mainframe->printhost_queue_dlg()->force_color_changed(); +#ifdef _MSW_DARK_MODE + update_scrolls(mainframe); + if (mainframe->is_dlg_layout()) { + // update for tabs bar + UpdateDarkUI(&mainframe->m_settings_dialog); + mainframe->m_settings_dialog.Fit(); + mainframe->m_settings_dialog.Refresh(); + // update scrollbars + update_scrolls(&mainframe->m_settings_dialog); + } +#endif //_MSW_DARK_MODE + } +#endif + mainframe->update_ui_from_settings(); +} + +void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) +{ + const std::string name = into_u8(window->GetName()); + + window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) { + window_pos_save(window, name); + event.Skip(); + }); + + window_pos_restore(window, name, default_maximized); + + on_window_geometry(window, [=]() { + window_pos_sanitize(window); + }); +} + +void GUI_App::load_project(wxWindow *parent, wxString& input_file) const +{ + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one file (3MF/AMF):"), + app_config->get_last_dir(), "", + file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + +void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const +{ + input_files.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one or more files (STL/3MF/STEP/OBJ/AMF/PRUSA):"), + from_u8(app_config->get_last_dir()), "", + file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + dialog.GetPaths(input_files); +} + +void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const +{ + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"), + app_config->get_last_dir(), "", + file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + +bool GUI_App::switch_language() +{ + if (select_language()) { + recreate_GUI(_L("Changing of an application language") + dots); + return true; + } else { + return false; + } +} + +#ifdef __linux__ +static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, + const wxLanguageInfo* system_language) +{ + constexpr size_t max_len = 50; + char path[max_len] = ""; + std::vector locales; + const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_')); + + // Call locale -a so we can parse the output to get the list of available locales + // We expect lines such as "en_US.utf8". Pick ones starting with the language code + // we are switching to. Lines with different formatting will be removed later. + FILE* fp = popen("locale -a", "r"); + if (fp != NULL) { + while (fgets(path, max_len, fp) != NULL) { + std::string line(path); + line = line.substr(0, line.find('\n')); + if (boost::starts_with(line, lang_prefix)) + locales.push_back(line); + } + pclose(fp); + } + + // locales now contain all candidates for this language. + // Sort them so ones containing anything about UTF-8 are at the end. + std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b) + { + auto has_utf8 = [](const std::string & s) { + auto S = boost::to_upper_copy(s); + return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos; + }; + return ! has_utf8(a) && has_utf8(b); + }); + + // Remove the suffix behind a dot, if there is one. + for (std::string& s : locales) + s = s.substr(0, s.find(".")); + + // We just hope that dear Linux "locale -a" returns country codes + // in ISO 3166-1 alpha-2 code (two letter) format. + // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes + // To be sure, remove anything not looking as expected + // (any number of lowercase letters, underscore, two uppercase letters). + locales.erase(std::remove_if(locales.begin(), + locales.end(), + [](const std::string& s) { + return ! std::regex_match(s, + std::regex("^[a-z]+_[A-Z]{2}$")); + }), + locales.end()); + + if (system_language) { + // Is there a candidate matching a country code of a system language? Move it to the end, + // while maintaining the order of matches, so that the best match ends up at the very end. + std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2); + int cnt = locales.size(); + for (int i = 0; i < cnt; ++i) + if (locales[i].find(system_country) != std::string::npos) { + locales.emplace_back(std::move(locales[i])); + locales[i].clear(); + } + } + + // Now try them one by one. + for (auto it = locales.rbegin(); it != locales.rend(); ++ it) + if (! it->empty()) { + const std::string &locale = *it; + const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale)); + if (wxLocale::IsAvailable(lang->Language)) + return lang; + } + return language; +} +#endif + +int GUI_App::GetSingleChoiceIndex(const wxString& message, + const wxString& caption, + const wxArrayString& choices, + int initialSelection) +{ +#ifdef _WIN32 + wxSingleChoiceDialog dialog(nullptr, message, caption, choices); + wxGetApp().UpdateDlgDarkUI(&dialog); + + dialog.SetSelection(initialSelection); + return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1; +#else + return wxGetSingleChoiceIndex(message, caption, choices, initialSelection); +#endif +} + +// select language from the list of installed languages +bool GUI_App::select_language() +{ + wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); + std::vector language_infos; + language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); + for (size_t i = 0; i < translations.GetCount(); ++ i) { + const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]); + if (langinfo != nullptr) + language_infos.emplace_back(langinfo); + } + sort_remove_duplicates(language_infos); + std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; }); + + wxArrayString names; + names.Alloc(language_infos.size()); + + // Some valid language should be selected since the application start up. + const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage()); + int init_selection = -1; + int init_selection_alt = -1; + int init_selection_default = -1; + for (size_t i = 0; i < language_infos.size(); ++ i) { + if (wxLanguage(language_infos[i]->Language) == current_language) + // The dictionary matches the active language and country. + init_selection = i; + else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) || + // if the active language is Slovak, mark the Czech language as active. + (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk")) + // The dictionary matches the active language, it does not necessarily match the country. + init_selection_alt = i; + if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en") + // This will be the default selection if the active language does not match any dictionary. + init_selection_default = i; + names.Add(language_infos[i]->Description); + } + if (init_selection == -1) + // This is the dictionary matching the active language. + init_selection = init_selection_alt; + if (init_selection != -1) + // This is the language to highlight in the choice dialog initially. + init_selection_default = init_selection; + + const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default); + // Try to load a new language. + if (index != -1 && (init_selection == -1 || init_selection != index)) { + const wxLanguageInfo *new_language_info = language_infos[index]; + if (this->load_language(new_language_info->CanonicalName, false)) { + // Save language at application config. + // Which language to save as the selected dictionary language? + // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its + // stability in the future: + // wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); + // 2) Current locale language may not match the dictionary name, see GH issue #3901 + // m_wxLocale->GetCanonicalName() + // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name. + app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data()); + app_config->save(); + return true; + } + } + + return false; +} + +// Load gettext translation files and activate them at the start of the application, +// based on the "translation_language" key stored in the application config. +bool GUI_App::load_language(wxString language, bool initial) +{ + if (initial) { + // There is a static list of lookup path prefixes in wxWidgets. Add ours. + wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir())); + // Get the active language from PrusaSlicer.ini, or empty string if the key does not exist. + language = app_config->get("translation_language"); + if (! language.empty()) + BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language; + + // Get the system language. + { + const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); + if (lang_system != wxLANGUAGE_UNKNOWN) { + m_language_info_system = wxLocale::GetLanguageInfo(lang_system); + BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data(); + } + } + { + // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. + wxLocale temp_locale; +#ifdef __WXOSX__ + // ysFIXME - temporary workaround till it isn't fixed in wxWidgets: + // Use English as an initial language, because of under OSX it try to load "inappropriate" language for wxLANGUAGE_DEFAULT. + // For example in our case it's trying to load "en_CZ" and as a result PrusaSlicer catch warning message. + // But wxWidgets guys work on it. + temp_locale.Init(wxLANGUAGE_ENGLISH); +#else + temp_locale.Init(); +#endif // __WXOSX__ + // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). + wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); + // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer + // and try to match them with the system specific "preferred languages". + // There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage(). + // The last parameter gets added to the list of detected dictionaries. This is a workaround + // for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way. + wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); + if (! best_language.IsEmpty()) { + m_language_info_best = wxLocale::FindLanguageInfo(best_language); + BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data(); + } + #ifdef __linux__ + wxString lc_all; + if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) { + // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL. + // Disregard the "best" suggestion in case LC_ALL is provided. + m_language_info_best = nullptr; + } + #endif + } + } + + const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language); + if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) { + // Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI). + language_info = nullptr; + BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data(); + } + + if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) { + BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data(); + language_info = nullptr; + } + + if (language_info == nullptr) { + // PrusaSlicer does not support the Right to Left languages yet. + if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft) + language_info = m_language_info_system; + if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft) + language_info = m_language_info_best; + if (language_info == nullptr) + language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US); + } + + BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data(); + + // Alternate language code. + wxLanguage language_dict = wxLanguage(language_info->Language); + if (language_info->CanonicalName.BeforeFirst('_') == "sk") { + // Slovaks understand Czech well. Give them the Czech translation. + language_dict = wxLANGUAGE_CZECH; + BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language"; + } + + // Select language for locales. This language may be different from the language of the dictionary. + if (language_info == m_language_info_best || language_info == m_language_info_system) { + // The current language matches user's default profile exactly. That's great. + } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) { + // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language. + // This allows a Swiss guy to use a German dictionary without forcing him to German locales. + language_info = m_language_info_best; + } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_')) + language_info = m_language_info_system; + +#ifdef __linux__ + // If we can't find this locale , try to use different one for the language + // instead of just reporting that it is impossible to switch. + if (! wxLocale::IsAvailable(language_info->Language)) { + std::string original_lang = into_u8(language_info->CanonicalName); + language_info = linux_get_existing_locale_language(language_info, m_language_info_system); + BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.") + % original_lang % language_info->CanonicalName.ToUTF8().data(); + } +#endif + + if (! wxLocale::IsAvailable(language_info->Language)) { + // Loading the language dictionary failed. + wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed."; +#if !defined(_WIN32) && !defined(__APPLE__) + // likely some linux system + message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"; +#endif + if (initial) + message + "\n\nApplication will close."; + wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); + if (initial) + std::exit(EXIT_FAILURE); + else + return false; + } + + // Release the old locales, create new locales. + //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now. + m_wxLocale.release(); + m_wxLocale = Slic3r::make_unique(); + m_wxLocale->Init(language_info->Language); + // Override language at the active wxTranslations class (which is stored in the active m_wxLocale) + // to load possibly different dictionary, for example, load Czech dictionary for Slovak language. + wxTranslations::Get()->SetLanguage(language_dict); + { + // UKR Localization specific workaround till the wxWidgets doesn't fixed: + // From wxWidgets 3.1.6 calls setlocation(0, wxInfoLanguage->LocaleTag), see (https://github.com/prusa3d/wxWidgets/commit/deef116a09748796711d1e3509965ee208dcdf0b#diff-7de25e9a71c4dce61bbf76492c589623d5b93fd1bb105ceaf0662075d15f4472), + // where LocaleTag is a Tag of locale in BCP 47 - like notation. + // For Ukrainian Language LocaleTag == "uk". + // But setlocale(0, "uk") returns "English_United Kingdom.1252" instead of "uk", + // and, as a result, locales are set to English_United Kingdom + + if (language_info->CanonicalName == "uk") + setlocale(0, language_info->GetCanonicalWithRegion().data()); + } + m_wxLocale->AddCatalog(SLIC3R_APP_KEY); + m_imgui->set_language(into_u8(language_info->CanonicalName)); + //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. + //wxSetlocale(LC_NUMERIC, "C"); + Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); + return true; +} + +Tab* GUI_App::get_tab(Preset::Type type) +{ + for (Tab* tab: tabs_list) + if (tab->type() == type) + return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab + return nullptr; +} + +ConfigOptionMode GUI_App::get_mode() +{ + if (!app_config->has("view_mode")) + return comSimple; + + const auto mode = app_config->get("view_mode"); + return mode == "expert" ? comExpert : + mode == "simple" ? comSimple : comAdvanced; +} + +void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) +{ + const std::string mode_str = mode == comExpert ? "expert" : + mode == comSimple ? "simple" : "advanced"; + app_config->set("view_mode", mode_str); + app_config->save(); + update_mode(); +} + +// Update view mode according to selected menu +void GUI_App::update_mode() +{ + sidebar().update_mode(); + +#ifdef _WIN32 //_MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); +#endif + + for (auto tab : tabs_list) + tab->update_mode(); + + plater()->update_menus(); + plater()->canvas3D()->update_gizmos_on_off_state(); +} + +void GUI_App::add_config_menu(wxMenuBar *menu) +{ + auto local_menu = new wxMenu(); + wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); + + const auto config_wizard_name = _(ConfigWizard::name(true)); + const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str()); + // Cmd+, is standard on OS X - what about other operating systems? + if (is_editor()) { + local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); + local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); + local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); + local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + //if (DesktopIntegrationDialog::integration_possible()) + local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); +#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + local_menu->AppendSeparator(); + } + local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + +#ifdef __APPLE__ + "\tCtrl+,", +#else + "\tCtrl+P", +#endif + _L("Application preferences")); + wxMenu* mode_menu = nullptr; + if (is_editor()) { + local_menu->AppendSeparator(); + mode_menu = new wxMenu(); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode")); +// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode")); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode")); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode")); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert); + + local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); + } + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); + if (is_editor()) { + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer")); + // TODO: for when we're able to flash dictionaries + // local_menu->Append(config_id_base + FirmwareMenuDict, _L("Flash Language File"), _L("Upload a language dictionary file into a Prusa printer")); + } + + local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { + switch (event.GetId() - config_id_base) { + case ConfigMenuWizard: + run_wizard(ConfigWizard::RR_USER); + break; + case ConfigMenuUpdateConf: + check_updates(true); + break; + case ConfigMenuUpdateApp: + app_version_check(true); + break; +#ifdef __linux__ + case ConfigMenuDesktopIntegration: + show_desktop_integration_dialog(); + break; +#endif + case ConfigMenuTakeSnapshot: + // Take a configuration snapshot. + if (wxString action_name = _L("Taking a configuration snapshot"); + check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) { + wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name")); + UpdateDlgDarkUI(&dlg); + + // set current normal font for dialog children, + // because of just dlg.SetFont(normal_font()) has no result; + for (auto child : dlg.GetChildren()) + child->SetFont(normal_font()); + + if (dlg.ShowModal() == wxID_OK) + if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error( + *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); + snapshot != nullptr) + app_config->set("on_snapshot", snapshot->id); + } + break; + case ConfigMenuSnapshots: + if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) { + std::string on_snapshot; + if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) + on_snapshot = app_config->get("on_snapshot"); + ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); + dlg.ShowModal(); + if (!dlg.snapshot_to_activate().empty()) { + if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && + ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "", + GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate()))) + break; + try { + app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); + // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system + // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users + // are known to be creative and mess with the config files in various ways. + if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); + ! all_substitutions.empty()) + show_substitutions_info(all_substitutions); + + // Load the currently selected preset into the GUI, update the preset selection box. + load_current_presets(); + } catch (std::exception &ex) { + GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); + } + } + } + break; + case ConfigMenuPreferences: + { + open_preferences(); + break; + } + case ConfigMenuLanguage: + { + /* Before change application language, let's check unsaved changes on 3D-Scene + * and draw user's attention to the application restarting after a language change + */ + { + // the dialog needs to be destroyed before the call to switch_language() + // or sometimes the application crashes into wxDialogBase() destructor + // so we put it into an inner scope + wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); + title += " - " + _L("Language selection"); + //wxMessageDialog dialog(nullptr, + MessageDialog dialog(nullptr, + _L("Switching the language will trigger application restart.\n" + "You will lose content of the plater.") + "\n\n" + + _L("Do you want to proceed?"), + title, + wxICON_QUESTION | wxOK | wxCANCEL); + if (dialog.ShowModal() == wxID_CANCEL) + return; + } + + switch_language(); + break; + } + case ConfigMenuFlashFirmware: + FirmwareDialog::run(mainframe); + break; + default: + break; + } + }); + + using std::placeholders::_1; + + if (mode_menu != nullptr) { + auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert); + } + + menu->Append(local_menu, _L("&Configuration")); +} + +void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) +{ + mainframe->preferences_dialog->show(highlight_option, tab_name); + + if (mainframe->preferences_dialog->recreate_GUI()) + recreate_GUI(_L("Restart application") + dots); + +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) +#else + if (mainframe->preferences_dialog->seq_top_layer_only_changed()) +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + this->plater_->refresh_print(); + +#ifdef _WIN32 + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); + } + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); + } +#endif // _WIN32 + + if (mainframe->preferences_dialog->settings_layout_changed()) { + // hide full main_sizer for mainFrame + mainframe->GetSizer()->Show(false); + mainframe->update_layout(); + mainframe->select_tab(size_t(0)); + } +} + +bool GUI_App::has_unsaved_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) + return true; + } + return false; +} + +bool GUI_App::has_current_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + return true; + } + return false; +} + +void GUI_App::update_saved_preset_from_current_preset() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->update_saved_preset_from_current_preset(); + } +} + +std::vector GUI_App::get_active_preset_collections() const +{ + std::vector ret; + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* tab : tabs_list) + if (tab->supports_printer_technology(printer_technology)) + ret.push_back(tab->get_presets()); + return ret; +} + +// To notify the user whether he is aware that some preset changes will be lost, +// UnsavedChangesDialog: "Discard / Save / Cancel" +// This is called when: +// - Close Application & Current project isn't saved +// - Load Project & Current project isn't saved +// - Undo / Redo with change of print technologie +// - Loading snapshot +// - Loading config_file/bundle +// UnsavedChangesDialog: "Don't save / Save / Cancel" +// This is called when: +// - Exporting config_bundle +// - Taking snapshot +bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/) +{ + if (has_current_preset_changes()) { + const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; + int act_buttons = ActionButtons::SAVE; + if (dont_save_insted_of_discard) + act_buttons |= ActionButtons::DONT_SAVE; + UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); + std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); + if (act == "none" && dlg.ShowModal() == wxID_CANCEL) + return false; + + if (dlg.save_preset()) // save selected changes + { + for (const std::pair& nt : dlg.get_names_and_types()) + preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + + load_current_presets(false); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + + MessageDialog(nullptr, dlg.msg_success_saved_modifications(dlg.get_names_and_types().size())).ShowModal(); + } + } + + return true; +} + +void GUI_App::apply_keeped_preset_modifications() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->apply_config_from_cache(); + } + load_current_presets(false); +} + +// This is called when creating new project or load another project +// OR close ConfigWizard +// to ask the user what should we do with unsaved changes for presets. +// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel" +// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" +// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" +// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed +bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/) +{ + if (has_current_preset_changes()) { + bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr; + + const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project"; + UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons); + std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); + if (act == "none" && dlg.ShowModal() == wxID_CANCEL) + return false; + + auto reset_modifications = [this, is_called_from_configwizard]() { + if (is_called_from_configwizard) + return; // no need to discared changes. It will be done fromConfigWizard closing + + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + tab->m_presets->discard_current_changes(); + } + load_current_presets(false); + }; + + if (dlg.discard()) + reset_modifications(); + else // save selected changes + { + const auto& preset_names_and_types = dlg.get_names_and_types(); + if (dlg.save_preset()) { + for (const std::pair& nt : preset_names_and_types) + preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + + wxString text = dlg.msg_success_saved_modifications(preset_names_and_types.size()); + if (!is_called_from_configwizard) + text += "\n\n" + _L("For new project all modifications will be reseted"); + + MessageDialog(nullptr, text).ShowModal(); + reset_modifications(); + } + else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) { + // execute this part of code only if not all modifications are keeping to the new project + // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected + for (const std::pair& nt : preset_names_and_types) { + Preset::Type type = nt.second; + Tab* tab = get_tab(type); + std::vector selected_options = dlg.get_selected_options(type); + if (type == Preset::TYPE_PRINTER) { + auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); + if (it != selected_options.end()) { + // erase "extruders_count" option from the list + selected_options.erase(it); + // cache the extruders count + static_cast(tab)->cache_extruder_cnt(); + } + } + tab->cache_config_diff(selected_options); + if (!is_called_from_configwizard) + tab->m_presets->discard_current_changes(); + } + if (is_called_from_configwizard) + *postponed_apply_of_keeped_changes = true; + else + apply_keeped_preset_modifications(); + } + } + } + + return true; +} + +bool GUI_App::can_load_project() +{ + int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified.")); + if (saved_project == wxID_CANCEL || + (plater()->is_project_dirty() && saved_project == wxID_NO && + !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved.")))) + return false; + return true; +} + +bool GUI_App::check_print_host_queue() +{ + wxString dirty; + std::vector> jobs; + // Get ongoing jobs from dialog + mainframe->m_printhost_queue_dlg->get_active_jobs(jobs); + if (jobs.empty()) + return true; + // Show dialog + wxString job_string = wxString(); + for (const auto& job : jobs) { + job_string += format_wxstr(" %1% : %2% \n", job.first, job.second); + } + wxString message; + message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?")); + //wxMessageDialog dialog(mainframe, + MessageDialog dialog(mainframe, + message, + wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")), + wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); + if (dialog.ShowModal() == wxID_YES) + return true; + + // TODO: If already shown, bring forward + mainframe->m_printhost_queue_dlg->Show(); + return false; +} + +bool GUI_App::checked_tab(Tab* tab) +{ + bool ret = true; + if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end()) + ret = false; + return ret; +} + +// Update UI / Tabs to reflect changes in the currently loaded presets +void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) +{ + // check printer_presets for the containing information about "Print Host upload" + // and create physical printer from it, if any exists + if (check_printer_presets_) + check_printer_presets(); + + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + this->plater()->set_printer_technology(printer_technology); + for (Tab *tab : tabs_list) + if (tab->supports_printer_technology(printer_technology)) { + if (tab->type() == Preset::TYPE_PRINTER) { + static_cast(tab)->update_pages(); + // Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change(). + this->plater()->force_print_bed_update(); + } + tab->load_current_preset(); + } +} + +bool GUI_App::OnExceptionInMainLoop() +{ + generic_exception_handle(); + return false; +} + +#ifdef __APPLE__ +// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run +// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App +// to a G-code viewer. +void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames) +{ + size_t num_gcodes = 0; + for (const wxString &filename : fileNames) + if (is_gcode_file(into_u8(filename))) + ++ num_gcodes; + if (fileNames.size() == num_gcodes) { + // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder, + // just G-codes were passed. Switch to G-code viewer mode. + m_app_mode = EAppMode::GCodeViewer; + unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/"); + if(app_config != nullptr) + delete app_config; + app_config = nullptr; + init_app_config(); + } + wxApp::OSXStoreOpenFiles(fileNames); +} +// wxWidgets override to get an event on open files. +void GUI_App::MacOpenFiles(const wxArrayString &fileNames) +{ + std::vector files; + std::vector gcode_files; + std::vector non_gcode_files; + for (const auto& filename : fileNames) { + if (is_gcode_file(into_u8(filename))) + gcode_files.emplace_back(filename); + else { + files.emplace_back(into_u8(filename)); + non_gcode_files.emplace_back(filename); + } + } + if (m_app_mode == EAppMode::GCodeViewer) { + // Running in G-code viewer. + // Load the first G-code into the G-code viewer. + // Or if no G-codes, send other files to slicer. + if (! gcode_files.empty()) { + if (m_post_initialized) + this->plater()->load_gcode(gcode_files.front()); + else + this->init_params->input_files = { into_u8(gcode_files.front()) }; + } + if (!non_gcode_files.empty()) + start_new_slicer(non_gcode_files, true); + } else { + if (! files.empty()) { + if (m_post_initialized) { + wxArrayString input_files; + for (size_t i = 0; i < non_gcode_files.size(); ++i) + input_files.push_back(non_gcode_files[i]); + this->plater()->load_files(input_files); + } else { + for (const auto &f : non_gcode_files) + this->init_params->input_files.emplace_back(into_u8(f)); + } + } + for (const wxString &filename : gcode_files) + start_new_gcodeviewer(&filename); + } +} + +void GUI_App::MacOpenURL(const wxString& url) +{ + if (app_config && app_config->get("downloader_url_registered") != "1") + { + BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; + return; + } + start_download(boost::nowide::narrow(url)); +} + +#endif /* __APPLE */ + +Sidebar& GUI_App::sidebar() +{ + return plater_->sidebar(); +} + +ObjectManipulation* GUI_App::obj_manipul() +{ + // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) + return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr; +} + +ObjectSettings* GUI_App::obj_settings() +{ + return sidebar().obj_settings(); +} + +ObjectList* GUI_App::obj_list() +{ + return sidebar().obj_list(); +} + +ObjectLayers* GUI_App::obj_layers() +{ + return sidebar().obj_layers(); +} + +Plater* GUI_App::plater() +{ + return plater_; +} + +const Plater* GUI_App::plater() const +{ + return plater_; +} + +Model& GUI_App::model() +{ + return plater_->model(); +} +wxBookCtrlBase* GUI_App::tab_panel() const +{ + return mainframe->m_tabpanel; +} + +NotificationManager* GUI_App::notification_manager() +{ + return plater_->get_notification_manager(); +} + +GalleryDialog* GUI_App::gallery_dialog() +{ + return mainframe->gallery_dialog(); +} + +Downloader* GUI_App::downloader() +{ + return m_downloader.get(); +} + +// extruders count from selected printer preset +int GUI_App::extruders_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_selected_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + +// extruders count from edited printer preset +int GUI_App::extruders_edited_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_edited_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + +wxString GUI_App::current_language_code_safe() const +{ + // Translate the language code to a code, for which Prusa Research maintains translations. + const std::map mapping { + { "cs", "cs_CZ", }, + { "sk", "cs_CZ", }, + { "de", "de_DE", }, + { "es", "es_ES", }, + { "fr", "fr_FR", }, + { "it", "it_IT", }, + { "ja", "ja_JP", }, + { "ko", "ko_KR", }, + { "pl", "pl_PL", }, + { "uk", "uk_UA", }, + { "zh", "zh_CN", }, + { "ru", "ru_RU", }, + }; + wxString language_code = this->current_language_code().BeforeFirst('_'); + auto it = mapping.find(language_code); + if (it != mapping.end()) + language_code = it->second; + else + language_code = "en_US"; + return language_code; +} + +void GUI_App::open_web_page_localized(const std::string &http_address) +{ + open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); +} + +// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). +// Because of we can't to print the multi-part objects with SLA technology. +bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) +{ + if (model_has_multi_part_objects(model())) { + show_info(nullptr, + _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } + if (model_has_connectors(model())) { + show_info(nullptr, + _L("SLA technology doesn't support cut with connectors") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } + return true; +} + +bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) +{ + wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + + if (reason == ConfigWizard::RR_USER) { + // Cancel sync before starting wizard to prevent two downloads at same time + preset_updater->cancel_sync(); + preset_updater->update_index_db(); + if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) + return false; + } + + auto wizard = new ConfigWizard(mainframe); + const bool res = wizard->run(reason, start_page); + + if (res) { + load_current_presets(); + + // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() + if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) + may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); + } + + return res; +} + +void GUI_App::show_desktop_integration_dialog() +{ +#ifdef __linux__ + //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + DesktopIntegrationDialog dialog(mainframe); + dialog.ShowModal(); +#endif //__linux__ +} + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG +void GUI_App::gcode_thumbnails_debug() +{ + const std::string BEGIN_MASK = "; thumbnail begin"; + const std::string END_MASK = "; thumbnail end"; + std::string gcode_line; + bool reading_image = false; + unsigned int width = 0; + unsigned int height = 0; + + wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() != wxID_OK) + return; + + std::string in_filename = into_u8(dialog.GetPath()); + std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string(); + + boost::nowide::ifstream in_file(in_filename.c_str()); + std::vector rows; + std::string row; + if (in_file.good()) + { + while (std::getline(in_file, gcode_line)) + { + if (in_file.good()) + { + if (boost::starts_with(gcode_line, BEGIN_MASK)) + { + reading_image = true; + gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); + std::string::size_type x_pos = gcode_line.find('x'); + std::string width_str = gcode_line.substr(0, x_pos); + width = (unsigned int)::atoi(width_str.c_str()); + std::string height_str = gcode_line.substr(x_pos + 1); + height = (unsigned int)::atoi(height_str.c_str()); + row.clear(); + } + else if (reading_image && boost::starts_with(gcode_line, END_MASK)) + { + std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; + boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); + if (out_file.good()) + { + std::string decoded; + decoded.resize(boost::beast::detail::base64::decoded_size(row.size())); + decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first); + + out_file.write(decoded.c_str(), decoded.size()); + out_file.close(); + } + + reading_image = false; + width = 0; + height = 0; + rows.clear(); + } + else if (reading_image) + row += gcode_line.substr(2); + } + } + + in_file.close(); + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + +void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) +{ + if (name.empty()) { return; } + const auto config_key = (boost::format("window_%1%") % name).str(); + + WindowMetrics metrics = WindowMetrics::from_window(window); + app_config->set(config_key, metrics.serialize()); + app_config->save(); +} + +void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized) +{ + if (name.empty()) { return; } + const auto config_key = (boost::format("window_%1%") % name).str(); + + if (! app_config->has(config_key)) { + window->Maximize(default_maximized); + return; + } + + auto metrics = WindowMetrics::deserialize(app_config->get(config_key)); + if (! metrics) { + window->Maximize(default_maximized); + return; + } + + const wxRect& rect = metrics->get_rect(); + + if (app_config->get("restore_win_position") == "1") { + // workaround for crash related to the positioning of the window on secondary monitor + app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str()); + app_config->save(); + window->SetPosition(rect.GetPosition()); + + // workaround for crash related to the positioning of the window on secondary monitor + app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str()); + app_config->save(); + window->SetSize(rect.GetSize()); + + // revert "restore_win_position" value if application wasn't crashed + app_config->set("restore_win_position", "1"); + app_config->save(); + } + else + window->CenterOnScreen(); + + window->Maximize(metrics->get_maximized()); +} + +void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) +{ + /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window); + wxRect display; + if (display_idx == wxNOT_FOUND) { + display = wxDisplay(0u).GetClientArea(); + window->Move(display.GetTopLeft()); + } else { + display = wxDisplay(display_idx).GetClientArea(); + } + + auto metrics = WindowMetrics::from_window(window); + metrics.sanitize_for_display(display); + if (window->GetScreenRect() != metrics.get_rect()) { + window->SetSize(metrics.get_rect()); + } +} + +bool GUI_App::config_wizard_startup() +{ + if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { + run_wizard(ConfigWizard::RR_DATA_EMPTY); + return true; + } else if (get_app_config()->legacy_datadir()) { + // Looks like user has legacy pre-vendorbundle data directory, + // explain what this is and run the wizard + + MsgDataLegacy dlg; + dlg.ShowModal(); + + run_wizard(ConfigWizard::RR_DATA_LEGACY); + return true; + } + return false; +} + +bool GUI_App::check_updates(const bool verbose) +{ + PresetUpdater::UpdateResult updater_result; + try { + preset_updater->update_index_db(); + updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); + if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { + mainframe->Close(); + // Applicaiton is closing. + return false; + } + else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { + m_app_conf_exists = true; + } + else if (verbose && updater_result == PresetUpdater::R_NOOP) { + MsgNoUpdates dlg; + dlg.ShowModal(); + } + } + catch (const std::exception & ex) { + show_error(nullptr, ex.what()); + } + // Applicaiton will continue. + return true; +} + +bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) +{ + bool launch = true; + + // warning dialog containes a "Remember my choice" checkbox + std::string option_key = "suppress_hyperlinks"; + if (force_remember_choice || app_config->get(option_key).empty()) { + if (app_config->get(option_key).empty()) { + RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + dialog.ShowCheckBox(_L("Remember my choice")); + auto answer = dialog.ShowModal(); + launch = answer == wxID_YES; + if (dialog.IsCheckBoxChecked()) { + wxString preferences_item = _L("Suppress to open hyperlink in browser"); + wxString msg = + _L("PrusaSlicer will remember your choice.") + "\n\n" + + _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + + format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); + + MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); + if (msg_dlg.ShowModal() == wxID_CANCEL) + return false; + app_config->set(option_key, answer == wxID_NO ? "1" : "0"); + } + } + if (launch) + launch = app_config->get(option_key) != "1"; + } + // warning dialog doesn't containe a "Remember my choice" checkbox + // and will be shown only when "Suppress to open hyperlink in browser" is ON. + else if (app_config->get(option_key) == "1") { + MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + launch = dialog.ShowModal() == wxID_YES; + } + + return launch && wxLaunchDefaultBrowser(url, flags); +} + +// static method accepting a wxWindow object as first parameter +// void warning_catcher{ +// my($self, $message_dialog) = @_; +// return sub{ +// my $message = shift; +// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ; +// my @params = ($message, 'Warning', wxOK | wxICON_WARNING); +// $message_dialog +// ? $message_dialog->(@params) +// : Wx::MessageDialog->new($self, @params)->ShowModal; +// }; +// } + +// Do we need this function??? +// void GUI_App::notify(message) { +// auto frame = GetTopWindow(); +// // try harder to attract user attention on OS X +// if (!frame->IsActive()) +// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO); +// +// // There used to be notifier using a Growl application for OSX, but Growl is dead. +// // The notifier also supported the Linux X D - bus notifications, but that support was broken. +// //TODO use wxNotificationMessage ? +// } + + +#ifdef __WXMSW__ +void GUI_App::associate_3mf_files() +{ + associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); +} + +void GUI_App::associate_stl_files() +{ + associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); +} + +void GUI_App::associate_gcode_files() +{ + associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); +} +#endif // __WXMSW__ + +void GUI_App::on_version_read(wxCommandEvent& evt) +{ + app_config->set("version_online", into_u8(evt.GetString())); + app_config->save(); + std::string opt = app_config->get("notify_release"); + if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { + return; + } + if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { + return; + } + // notification + /* + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable + , NotificationManager::NotificationLevel::ImportantNotificationLevel + , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) + , _u8L("See Download page.") + , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } + ); + */ + // updater + // read triggered_by_user that was set when calling GUI_App::app_version_check + app_updater(m_app_updater->get_triggered_by_user()); +} + +void GUI_App::app_updater(bool from_user) +{ + DownloadAppData app_data = m_app_updater->get_app_data(); + + if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION))) + { + BOOST_LOG_TRIVIAL(info) << "There is no newer version online."; + MsgNoAppUpdates no_update_dialog; + no_update_dialog.ShowModal(); + return; + + } + + assert(!app_data.url.empty()); + assert(!app_data.target_path.empty()); + + // dialog with new version info + AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); + auto dialog_result = dialog.ShowModal(); + // checkbox "do not show again" + if (dialog.disable_version_check()) { + app_config->set("notify_release", "none"); + } + // Doesn't wish to update + if (dialog_result != wxID_OK) { + return; + } + // dialog with new version download (installer or app dependent on system) including path selection + AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); + dialog_result = dwnld_dlg.ShowModal(); + // Doesn't wish to download + if (dialog_result != wxID_OK) { + return; + } + app_data.target_path =dwnld_dlg.get_download_path(); + + // start download + this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); + app_data.start_after = dwnld_dlg.run_after_download(); + m_app_updater->set_app_data(std::move(app_data)); + m_app_updater->sync_download(); +} + +void GUI_App::app_version_check(bool from_user) +{ + if (from_user) { + if (m_app_updater->get_download_ongoing()) { + MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO); + if (msgdlg.ShowModal() != wxID_YES) + return; + } + } + std::string version_check_url = app_config->version_check_url(); + m_app_updater->sync_version(version_check_url, from_user); +} + +void GUI_App::start_download(std::string url) +{ + if (!plater_) { + BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr."; + return; + } + //lets always init so if the download dest folder was changed, new dest is used + boost::filesystem::path dest_folder(app_config->get("url_downloader_dest")); + if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) { + std::string msg = _utf8("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard."); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + return; + } + m_downloader->init(dest_folder); + m_downloader->start_download(url); +} + +} // GUI +} //Slic3r diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index b76e903cdb..d25f5adfa5 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -798,7 +798,7 @@ void PlaterPresetComboBox::show_edit_menu() wxString PlaterPresetComboBox::get_preset_name(const Preset& preset) { - std::string name = preset.alias.empty() ? preset.name : preset.alias; + std::string name = preset.alias.empty() ? preset.name : (preset.vendor && preset.vendor->templates_profile ? preset.name : preset.alias); return from_u8(name + suffix(preset)); } @@ -909,14 +909,7 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } - const AppConfig* app_config = wxGetApp().app_config; - if (!template_presets.empty() && app_config->get("no_templates") == "0") { - set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { - Append(it->first, *it->second); - validate_selection(it->first == selected_user_preset); - } - } + if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); @@ -926,6 +919,15 @@ void PlaterPresetComboBox::update() } } + const AppConfig* app_config = wxGetApp().app_config; + if (!template_presets.empty() && app_config->get("no_templates") == "0") { + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + Append(it->first, *it->second); + validate_selection(it->first == selected_user_preset); + } + } + if (m_type == Preset::TYPE_PRINTER) { // add Physical printers, if any exists @@ -1122,17 +1124,7 @@ void TabPresetComboBox::update() if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } - const AppConfig* app_config = wxGetApp().app_config; - if (!template_presets.empty() && app_config->get("no_templates") == "0") { - set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { - int item_id = Append(it->first, *it->second.first); - bool is_enabled = it->second.second; - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(it->first == selected); - } - } + if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); @@ -1144,7 +1136,19 @@ void TabPresetComboBox::update() validate_selection(it->first == selected); } } - + + const AppConfig* app_config = wxGetApp().app_config; + if (!template_presets.empty() && app_config->get("no_templates") == "0") { + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } + } + if (m_type == Preset::TYPE_PRINTER) { // add Physical printers, if any exists diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 756fcb43ed..cd3935ea00 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -12,9 +12,11 @@ #include #include #include +#include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/format.hpp" @@ -50,7 +52,7 @@ namespace Slic3r { static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; - +namespace { void copy_file_fix(const fs::path &source, const fs::path &target) { BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target); @@ -67,7 +69,21 @@ void copy_file_fix(const fs::path &source, const fs::path &target) static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; fs::permissions(target, perms); } - +std::string escape_string_url(const std::string& unescaped) +{ + std::string ret_val; + CURL* curl = curl_easy_init(); + if (curl) { + char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size()); + if (decoded) { + ret_val = std::string(decoded); + curl_free(decoded); + } + curl_easy_cleanup(curl); + } + return ret_val; +} +} struct Update { fs::path source; @@ -160,12 +176,17 @@ struct PresetUpdater::priv void set_download_prefs(const AppConfig *app_config); bool get_file(const std::string &url, const fs::path &target_path) const; void prune_tmps() const; - void sync_config(const VendorMap vendors, const std::string& profile_archive_url); + void sync_config(const VendorMap vendors, const std::string& index_archive_url); void check_install_indices() const; Updates get_config_updates(const Semver& old_slic3r_version) const; bool perform_updates(Updates &&updates, bool snapshot = true) const; void set_waiting_updates(Updates u); + // checks existence and downloads resource to cache + void get_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const; + // checks existence and downloads resource to vendor or copy from cache to vendor + void get_or_copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const; + void update_index_db(); }; PresetUpdater::priv::priv() @@ -182,6 +203,11 @@ PresetUpdater::priv::priv() index_db = Index::load_db(); } +void PresetUpdater::priv::update_index_db() +{ + index_db = Index::load_db(); +} + // Pull relevant preferences from AppConfig void PresetUpdater::priv::set_download_prefs(const AppConfig *app_config) { @@ -235,9 +261,91 @@ void PresetUpdater::priv::prune_tmps() const } } +void PresetUpdater::priv::get_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const +{ + if (filename.empty() || vendor.empty()) + return; + + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + + std::string escaped_filename = escape_string_url(filename); + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); + + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in vendor folder. No need to download."; + return; + } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in resources folder. No need to download."; + return; + } + if (fs::exists(file_in_cache)) { // In cache/venodr_name/ dir. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in cache folder. No need to download."; + return; + } + + BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", escaped_filename); // vendor should already be in url + + if (!fs::exists(file_in_cache.parent_path())) + fs::create_directory(file_in_cache.parent_path()); + + get_file(resource_url, file_in_cache); + return; +} + +void PresetUpdater::priv::get_or_copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const +{ + if (filename.empty() || vendor.empty()) + return; + + std::string escaped_filename = escape_string_url(filename); + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); + + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in vendor folder. No need to download."; + return; + } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in resources folder. No need to download."; + return; + } + if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir. + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", escaped_filename); // vendor should already be in url + + if (!fs::exists(file_in_vendor.parent_path())) + fs::create_directory(file_in_vendor.parent_path()); + + get_file(resource_url, file_in_vendor); + return; + } + + if (!fs::exists(file_in_vendor.parent_path())) // create vendor_name dir in vendor + fs::create_directory(file_in_vendor.parent_path()); + + BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor; + copy_file_fix(file_in_cache, file_in_vendor); +} + // Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Both are saved in cache. -void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& profile_archive_url) +void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& index_archive_url) { BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; @@ -246,21 +354,20 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string // Download profiles archive zip // dk: Do we want to return here on error? Or skip archive dwnld and unzip and work with previous run state cache / vendor? I think return. // Any error here also doesnt show any info in UI. Do we want maybe notification? - fs::path archive_path(cache_path / "Archive.zip"); - if (profile_archive_url.empty()) { + fs::path archive_path(cache_path / "vendor_indices.zip"); + if (index_archive_url.empty()) { BOOST_LOG_TRIVIAL(error) << "Downloading profile archive failed - url has no value."; return; } - BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip."; + BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip from " << index_archive_url; //check if idx_url is leading to our site - if (!boost::starts_with(profile_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - !boost::starts_with(profile_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + if (!boost::starts_with(index_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(index_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) { BOOST_LOG_TRIVIAL(error) << "Unsafe url path for vedor profiles archive zip. Download is rejected."; - // TODO: this return must be uncommented when correct address is in use - //return; + return; } - if (!get_file(profile_archive_url, archive_path)) { + if (!get_file(index_archive_url, archive_path)) { BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed."; return; } @@ -268,8 +375,16 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string return; } + enum class VendorStatus + { + IN_ARCHIVE, + IN_CACHE, + NEW_VERSION, + INSTALLED + }; + + std::vector> vendors_with_status; // Unzip archive to cache / vendor - std::vector vendors_only_in_archive; mz_zip_archive archive; mz_zip_zero_struct(&archive); if (!open_zip_reader(&archive, archive_path.string())) { @@ -291,69 +406,59 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string } // create file from buffer fs::path tmp_path(cache_vendor_path / (name + ".tmp")); + if (!fs::exists(tmp_path.parent_path())) { + BOOST_LOG_TRIVIAL(error) << "Failed to unzip file " << name << ". Directories are not supported. Skipping file."; + continue; + } fs::path target_path(cache_vendor_path / name); fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); file.write(buffer.c_str(), buffer.size()); file.close(); - fs::rename(tmp_path, target_path); - - if (name.substr(name.size() - 3) == "ini") - vendors_only_in_archive.push_back(name); + boost::system::error_code ec; + bool exists = fs::exists(tmp_path, ec); + if(!exists || ec) { + BOOST_LOG_TRIVIAL(error) << "Failed to find unzipped file at " << tmp_path << ". Terminating Preset updater synchorinzation." ; + close_zip_reader(&archive); + return; + } + fs::rename(tmp_path, target_path, ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << "Failed to rename unzipped file at " << tmp_path << ". Terminating Preset updater synchorinzation. Error message: " << ec.message(); + close_zip_reader(&archive); + return; + } + // TODO: what if unexpected happens here (folder inside zip) - crash! + + if (name.substr(name.size() - 3) == "idx") + vendors_with_status.emplace_back(name.substr(0, name.size() - 4), VendorStatus::IN_ARCHIVE); // asume for now its only in archive - if not, it will change later. } } } close_zip_reader(&archive); } - auto get_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, - const std::string& url, const fs::path& vendor_path, - const fs::path& rsrc_path, const fs::path& cache_path) - { - if (filename.empty() || vendor.empty()) - return; - - if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) - { - throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); - } - - const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); - const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); - const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); - - if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. - return; - } - if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. - return; - } - - BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; - - const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url - - if (!fs::exists(file_in_cache.parent_path())) - fs::create_directory(file_in_cache.parent_path()); - - self.get_file(resource_url, file_in_cache); - return; - }; - // Update vendor preset bundles if in Vendor // Over all indices from the cache directory: for (auto &index : index_db) { if (cancel) { return; } + auto archive_it = std::find_if(vendors_with_status.begin(), vendors_with_status.end(), + [&index](const std::pair& element) { return element.first == index.vendor(); }); + //assert(archive_it != vendors_with_status.end()); // this would mean there is a index for vendor that is missing in recently downloaded archive const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { // Not installed vendor yet we need to check missing thumbnails (of new printers) BOOST_LOG_TRIVIAL(debug) << "No such vendor: " << index.vendor(); + if (archive_it != vendors_with_status.end()) + archive_it->second = VendorStatus::IN_CACHE; continue; } + if (archive_it != vendors_with_status.end()) + archive_it->second = VendorStatus::INSTALLED; + const VendorProfile &vendor = vendor_it->second; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string(); @@ -368,7 +473,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string continue; } if (new_index.version() < index.version()) { - BOOST_LOG_TRIVIAL(warning) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name); + BOOST_LOG_TRIVIAL(info) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name); continue; } copy_file_fix(idx_path_temp, idx_path); @@ -401,23 +506,28 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string recommended.to_string()); if (vendor.config_version >= recommended) { continue; } - - const auto path_in_archive = cache_vendor_path / (vendor.id + ".ini"); - const auto path_in_cache = cache_path / (vendor.id + ".ini"); - // Check version - if (!boost::filesystem::exists(path_in_archive)) - continue; - // vp is fully loaded to get all resources - auto vp = VendorProfile::from_ini(path_in_archive, true); - if (vp.config_version != recommended) - continue; - copy_file_fix(path_in_archive, path_in_cache); - // vendors that are checked here, doesnt need to be checked again later - const auto archive_it = std::find(vendors_only_in_archive.begin(), vendors_only_in_archive.end(), index.vendor() + ".ini"); - if (archive_it != vendors_only_in_archive.end()) { - vendors_only_in_archive.erase(archive_it); - } + // vendors that are checked here, doesnt need to be checked again later + if (archive_it != vendors_with_status.end()) + archive_it->second = VendorStatus::NEW_VERSION; + + // Download recomended ini to cache + const auto path_in_cache = cache_path / (vendor.id + ".ini"); + BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name; + const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string()); + const auto bundle_path = cache_path / (vendor.id + ".ini"); + if (!get_file(bundle_url, bundle_path)) + continue; + if (cancel) + return; + // vp is fully loaded to get all resources + VendorProfile vp; + try { + vp = VendorProfile::from_ini(bundle_path, true); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.id, bundle_path, e.what()); + continue; + } // check the fresh bundle for missing resources // for that, the ini file must be parsed (done above) for (const auto& model : vp.models) { @@ -425,9 +535,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string if (! res.empty()) { try { - // for debug (wont pass check inside function) - //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; - get_missing_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + get_missing_resource(vp.id, res, vendor.config_update_url); } catch (const std::exception& e) { @@ -441,28 +549,222 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string } } // Download missing thumbnails for not-installed vendors. - for (const std::string& vendor : vendors_only_in_archive) - { - BOOST_LOG_TRIVIAL(error) << vendor; - const auto path_in_archive = cache_vendor_path / vendor; - assert(boost::filesystem::exists(path_in_archive)); - auto vp = VendorProfile::from_ini(path_in_archive, true); - for (const auto& model : vp.models) { - if (!model.thumbnail.empty()) { - try - { - // for debug (wont pass check inside function) - //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; - get_missing_resource(vp.id, model.thumbnail, vp.config_update_url, vendor_path, rsrc_path, cache_path); + //for (const std::string& vendor : vendors_only_in_archive) + for (const std::pair& vendor : vendors_with_status) { + if (vendor.second == VendorStatus::IN_ARCHIVE) { + // index in archive and not in cache and not installed vendor + + const auto idx_path_in_archive = cache_vendor_path / (vendor.first + ".idx"); + const auto ini_path_in_archive = cache_vendor_path / (vendor.first + ".ini"); + if (!fs::exists(idx_path_in_archive)) + continue; + Index index; + try { + index.load(idx_path_in_archive); + } + catch (const std::exception& /* err */) { + BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_archive, vendor.first); + continue; + } + const auto recommended_it = index.recommended(); + if (recommended_it == index.end()) { + BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_archive); + continue; + } + const auto recommended = recommended_it->config_version; + if (!fs::exists(ini_path_in_archive)){ + // Download recommneded to vendor - we do not have any existing ini file so we have to use hardcoded url. + const std::string fixed_url = GUI::wxGetApp().app_config->profile_folder_url(); + const auto bundle_url = format("%1%/%2%/%3%.ini", fixed_url, vendor.first, recommended.to_string()); + if (!get_file(bundle_url, ini_path_in_archive)) + continue; + } else { + // check existing ini version + // then download recommneded to vendor if needed + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_archive, true); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what()); + continue; } - catch (const std::exception& e) - { - BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + if (vp.config_version != recommended) { + const std::string fixed_url = GUI::wxGetApp().app_config->profile_folder_url(); + const auto bundle_url = format("%1%/%2%/%3%.ini", fixed_url, vendor.first, recommended.to_string()); + if (!get_file(bundle_url, ini_path_in_archive)) + continue; } } - if (cancel) - return; + // check missing thumbnails + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_archive, true); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what()); + continue; + } + for (const auto& model : vp.models) { + if (!model.thumbnail.empty()) { + try + { + get_missing_resource(vp.id, model.thumbnail, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + } + } else if (vendor.second == VendorStatus::IN_CACHE) { + // find those where archive index recommends other version than index in cache and get it if not present + const auto idx_path_in_archive = cache_vendor_path / (vendor.first + ".idx"); + const auto ini_path_in_archive = cache_vendor_path / (vendor.first + ".ini"); + const auto idx_path_in_cache = cache_path / (vendor.first + ".idx"); + + if (!fs::exists(idx_path_in_archive) || !fs::exists(idx_path_in_cache)) + continue; + + // Compare index in cache and recetly downloaded one as part of zip archive + Index index_cache; + try { + index_cache.load(idx_path_in_cache); + } + catch (const std::exception& /* err */) { + BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_cache, vendor.first); + continue; + } + const auto recommended_it_cache = index_cache.recommended(); + if (recommended_it_cache == index_cache.end()) { + BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_cache); + continue; + } + const auto recommended_cache = recommended_it_cache->config_version; + + Index index_archive; + try { + index_archive.load(idx_path_in_archive); + } + catch (const std::exception& /* err */) { + BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path_in_archive, vendor.first); + continue; + } + const auto recommended_it_archive = index_archive.recommended(); + if (recommended_it_archive == index_archive.end()) { + BOOST_LOG_TRIVIAL(error) << format("No recommended version for vendor: %1%, invalid index? (%2%)", vendor.first, idx_path_in_archive); + continue; + } + const auto recommended_archive = recommended_it_archive->config_version; + if (recommended_archive <= recommended_cache) { + // There isn't more recent recomended version online. This vendor is also not istalled. + // Thus only .ini is in resources and came with installation. + // And we expect all resources are present. + continue; + } + + // Download new .ini if needed. So next time user runs Wizard, most recent profiles are shown & installed. + if (!fs::exists(ini_path_in_archive) || fs::is_empty(ini_path_in_archive)) { + // download recommneded to vendor + const fs::path ini_path_in_rsrc = rsrc_path / (vendor.first + ".ini"); + if (!fs::exists(ini_path_in_rsrc)) { + // THIS SHOULD NOT HAPPEN + continue; + } + // Get download path from existing ini. + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_rsrc, false); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_rsrc, e.what()); + continue; + } + const auto bundle_url = format("%1%/%2%.ini", vp.config_update_url, recommended_archive.to_string()); + if (!get_file(bundle_url, ini_path_in_archive)) { + BOOST_LOG_TRIVIAL(error) << format("Failed to open vendor .ini file when checking missing resources: %1%", ini_path_in_rsrc); + continue; + } + } else { + // Check existing ini version. + // Then download recommneded to vendor if needed. + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_archive, false); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what()); + continue; + } + if (vp.config_version != recommended_archive) { + const auto bundle_url = format("%1%/%2%.ini", vp.config_update_url, recommended_archive.to_string()); + if (!get_file(bundle_url, ini_path_in_archive)) { + BOOST_LOG_TRIVIAL(error) << format("Failed to open vendor .ini file when checking missing resources: %1%", ini_path_in_archive); + continue; + } + } + } + + if (!fs::exists(ini_path_in_archive)) { + BOOST_LOG_TRIVIAL(error) << "Resources check failed to find ini file for vendor: " << vendor.first; + continue; + } + // check missing thumbnails + VendorProfile vp; + try { + vp = VendorProfile::from_ini(ini_path_in_archive, true); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what()); + continue; + } + for (const auto& model : vp.models) { + if (!model.thumbnail.empty()) { + try + { + get_missing_resource(vp.id, model.thumbnail, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + } + } else if (vendor.second == VendorStatus::INSTALLED || vendor.second == VendorStatus::NEW_VERSION) { + // Installed vendors need to check that no resource is missing. Do this only for files in vendor folder (not in resorces) + // VendorStatus::NEW_VERSION might seem like a mistake here since files are downloaded when preparing update higher in this function. + // But this is a check for ini file in vendor where resources might be still missing since last update. + const auto path_in_vendor = vendor_path / (vendor.first + ".ini"); + if(!fs::exists(path_in_vendor)) + continue; + VendorProfile vp; + try { + vp = VendorProfile::from_ini(path_in_vendor, true); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, path_in_vendor, e.what()); + continue; + } + for (const auto& model : vp.models) { + for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail }) { + if (!model.thumbnail.empty()) { + try + { + get_or_copy_missing_resource(vp.id, res, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + } + } } } } @@ -513,8 +815,14 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version } // Perform a basic load and check the version of the installed preset bundle. - auto vp = VendorProfile::from_ini(bundle_path, false); - + VendorProfile vp; + try { + vp = VendorProfile::from_ini(bundle_path, false); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", idx.vendor(), bundle_path, e.what()); + continue; + } // Getting a recommended version from the latest index, wich may have been downloaded // from the internet, or installed / updated from the installation resources. auto recommended = idx.recommended(); @@ -590,18 +898,13 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version if (new_vp.config_version == recommended->config_version) { // The config bundle from the cache directory matches the recommended version of the index from the cache directory. // This is the newest known recommended config. Use it. - if (PresetUtils::vendor_profile_has_all_resources(new_vp)) { - // All resources for the profile in cache dir are existing (either in resources or data_dir/vendor or waiting to be copied to vendor from cache) - // This final check is not performed for updates from resources dir below. - new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); - // and install the config index from the cache into vendor's directory. - bundle_path_idx_to_install = idx.path(); - found = true; - } else { - // Resource missing - treat as if the INI file was corrupted. - throw Slic3r::CriticalException("Some resources are missing."); + if (!PresetUtils::vendor_profile_has_all_resources(new_vp)) { + BOOST_LOG_TRIVIAL(warning) << "Some resources are missing for update of vedor " << new_vp.id; } - + new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url, current_not_supported); + // and install the config index from the cache into vendor's directory. + bundle_path_idx_to_install = idx.path(); + found = true; } } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(info) << format("Failed to load the config bundle `%1%`: %2%", path_in_cache.string(), ex.what()); @@ -715,7 +1018,7 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size()); - wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles")); + wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles") , 100, nullptr, wxPD_AUTO_HIDE); progress_dialog.Pulse(); for (const auto &update : updates.updates) { @@ -755,50 +1058,16 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_prints) { obsolete_remover("sla_print", name); } for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } - - auto get_and_copy_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, - const fs::path& vendor_path, const fs::path& rsrc_path, - const fs::path& cache_path, const std::string& url) - { - if (filename.empty() || vendor.empty()) - return; - - const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); - const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); - const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); - - if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. - return; - } - if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. - return; - } - if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir. - if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) - { - throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); - } - BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename; - - const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url - - if (!fs::exists(file_in_vendor.parent_path())) - fs::create_directory(file_in_vendor.parent_path()); - - self.get_file(resource_url, file_in_vendor); - return; - } - - if (!fs::exists(file_in_vendor.parent_path())) // create vendor_name dir in vendor - fs::create_directory(file_in_vendor.parent_path()); - - BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor; - copy_file_fix(file_in_cache, file_in_vendor); - }; - + // check if any resorces of installed bundle are missing. If so, new ones should be already downloaded at cache/vendor_id/ - auto vp = VendorProfile::from_ini(update.target, true); + VendorProfile vp; + try { + vp = VendorProfile::from_ini(update.target, true); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", update.target, e.what()); + continue; + } progress_dialog.Update(1, GUI::format_wxstr(_L("Downloading resources for %1%."),vp.id)); progress_dialog.Pulse(); for (const auto& model : vp.models) { @@ -807,17 +1076,14 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons continue; try { - // for debug (wont pass check inside function) - //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; - get_and_copy_missing_resource(vp.id, resource, vendor_path, rsrc_path, cache_path, vp.config_update_url); + get_or_copy_missing_resource(vp.id, resource, vp.config_update_url); } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(error) << "Failed to prepare " << resource << " for " << vp.id << " " << model.id << ": " << e.what(); } } - } - + } } progress_dialog.Destroy(); @@ -825,7 +1091,7 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons return true; } - + void PresetUpdater::priv::set_waiting_updates(Updates u) { waiting_updates = u; @@ -858,11 +1124,11 @@ void PresetUpdater::sync(const PresetBundle *preset_bundle) // Unfortunatelly as of C++11, it needs to be copied again // into the closure (but perhaps the compiler can elide this). VendorMap vendors = preset_bundle->vendors; - std::string profile_archive_url =GUI::wxGetApp().app_config->profile_archive_url(); + std::string index_archive_url = GUI::wxGetApp().app_config->index_archive_url(); - p->thread = std::thread([this, vendors, profile_archive_url]() { + p->thread = std::thread([this, vendors, index_archive_url]() { this->p->prune_tmps(); - this->p->sync_config(std::move(vendors), profile_archive_url); + this->p->sync_config(std::move(vendors), index_archive_url); }); } @@ -1052,16 +1318,40 @@ bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vectorvendor_path / bundle).replace_extension(".ini"); bool is_in_rsrc = fs::exists(path_in_rsrc); - bool is_in_cache_vendor = fs::exists(path_in_cache_vendor); + bool is_in_cache_vendor = fs::exists(path_in_cache_vendor) && !fs::is_empty(path_in_cache_vendor); // find if in cache vendor is newer version than in resources if (is_in_cache_vendor) { - auto version_cache = VendorProfile::from_ini(path_in_cache_vendor, false).config_version; - auto version_rsrc = !is_in_rsrc ? Semver::zero() : VendorProfile::from_ini(path_in_rsrc, false).config_version; + Semver version_cache = Semver::zero(); + try { + auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); + version_cache = vp_cache.config_version; + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", path_in_cache_vendor, e.what()); + // lets use file in resources + if (is_in_rsrc) { + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + } + continue; + } + + Semver version_rsrc = Semver::zero(); + try { + if (is_in_rsrc) { + auto vp = VendorProfile::from_ini(path_in_rsrc, false); + version_rsrc = vp.config_version; + } + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", path_in_rsrc, e.what()); + continue; + } if (!is_in_rsrc || version_cache > version_rsrc) { // in case we are installing from cache / vendor. we should also copy index to cache // This needs to be done now bcs the current one would be missing this version on next start + // dk: Should we copy it to vendor dir too? auto path_idx_cache_vendor(path_in_cache_vendor); path_idx_cache_vendor.replace_extension(".idx"); auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); @@ -1121,4 +1411,9 @@ bool PresetUpdater::version_check_enabled() const return p->enabled_version_check; } +void PresetUpdater::update_index_db() +{ + p->update_index_db(); +} + } diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index 5bcba615bc..85875e5a31 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -53,6 +53,8 @@ public: // Providing old slic3r version upgrade profiles on upgrade of an application even in case // that the config index installed from the Internet is equal to the index contained in the installation package. UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const; + + void update_index_db(); // "Update" a list of bundles from resources or cache/vendor (behaves like an online update). bool install_bundles_rsrc_or_cache_vendor(std::vector bundles, bool snapshot = true) const; From b70571cd79ac74597682203c1aedd30b3b203bf5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 18 Jan 2023 12:20:21 +0100 Subject: [PATCH 83/86] Fixed Layer::sort_perimeters_into_islands() for fuzzy skin Follow-up to 52ea2edf842e81e0f81fcd8303b69d288d903ae3 1) There was a bug in accessing the "perimeter is external" property, ExtrusionCollection returns "mixed", the embedded ExtrusionPath has to be queried directly. 2) The search bounding box has to be extended by the maximum offset introduced by fuzzy skin algorithm. For Arachne the fuzzy skin algorithm observes fuzzy_skin_point_dist. --- src/libslic3r/Layer.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 44fc18f917..9b0004e989 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -577,12 +577,22 @@ void Layer::sort_perimeters_into_islands( } if (! sample_set) { // If there is no infill, take a sample of some inner perimeter. - for (uint32_t iperimeter : extrusions.first) - if (const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter]; ! ee.role().is_external()) { - sample = ee.first_point(); + for (uint32_t iperimeter : extrusions.first) { + const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter]; + if (ee.is_collection()) { + for (const ExtrusionEntity *ee2 : dynamic_cast(ee).entities) + if (! ee2->role().is_external()) { + sample = ee2->first_point(); + sample_set = true; + goto loop_end; + } + } else if (! ee.role().is_external()) { + sample = ee.first_point(); sample_set = true; break; } + } + loop_end: if (! sample_set) { if (! extrusions.second.empty()) { // If there is no inner perimeter, take a sample of some gap fill extrusion. @@ -752,7 +762,9 @@ void Layer::sort_perimeters_into_islands( const PrintRegionConfig ®ion_config = this_layer_region.region().config(); const auto bbox_eps = scaled( EPSILON + print_config.gcode_resolution.value + - (region_config.fuzzy_skin.value == FuzzySkinType::None ? 0. : region_config.fuzzy_skin_thickness.value)); + (region_config.fuzzy_skin.value == FuzzySkinType::None ? 0. : region_config.fuzzy_skin_thickness.value + //FIXME it looks as if Arachne could extend open lines by fuzzy_skin_point_dist, which does not seem right. + + region_config.fuzzy_skin_point_dist.value)); auto point_inside_surface_dist2 = [&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps] (const size_t lslice_idx, const Point &point) { From c3330c119b5c051ad1b67237416267c7422407f1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 18 Jan 2023 13:55:15 +0100 Subject: [PATCH 84/86] Prevent rare support strut and pinhead collisions By sending a ray through the center of each strut and increasing the ray count on the surface of the struts --- src/libslic3r/SLA/SupportTreeUtils.hpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 93f370c325..aebd50950b 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -31,7 +31,7 @@ using Slic3r::Geometry::spheric_to_dir; // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space template class PointRing { - std::array m_phis; + std::array m_phis; // Two vectors that will be perpendicular to each other and to the // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a @@ -51,7 +51,7 @@ class PointRing { public: - PointRing(const Vec3d &n) : m_phis{linspace_array(0., 2 * PI)} + PointRing(const Vec3d &n) : m_phis{linspace_array(0., 2 * PI)} { // We have to address the case when the direction vector v (same as // dir) is coincident with one of the world axes. In this case two of @@ -70,7 +70,10 @@ public: Vec3d get(size_t idx, const Vec3d &src, double r) const { - double phi = m_phis[idx]; + if (idx == 0) + return src; + + double phi = m_phis[idx - 1]; double sinphi = std::sin(phi); double cosphi = std::cos(phi); @@ -137,9 +140,9 @@ struct Beam_ { // Defines a set of rays displaced along a cone's surface {} }; -using Beam = Beam_<8>; +using Beam = Beam_<>; -template +template Hit beam_mesh_hit(Ex policy, const AABBMesh &mesh, const Beam_ &beam, @@ -196,7 +199,10 @@ Hit pinhead_mesh_hit(Ex ex, double width, double sd) { - static const size_t SAMPLES = 8; + // Support tree generation speed depends heavily on this value. 8 is almost + // ok, but to prevent rare cases of collision, 16 is necessary, which makes + // the algorithm run about 60% longer. + static const size_t SAMPLES = 16; // Move away slightly from the touching point to avoid raycasting on the // inner surface of the mesh. From 96762a2119e60aacec45e68fb52c1b9576498849 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 18 Jan 2023 15:29:55 +0100 Subject: [PATCH 85/86] No new version available notification --- src/slic3r/GUI/GUI_App.cpp | 15 ++++++++++++++- src/slic3r/GUI/NotificationManager.cpp | 23 +++++++++++++++++++++++ src/slic3r/GUI/NotificationManager.hpp | 4 ++++ src/slic3r/Utils/AppUpdater.cpp | 5 +++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3e561d04e5..b901e7f10e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1249,7 +1249,7 @@ bool GUI_App::on_init_inner() std::string evt_string = into_u8(evt.GetString()); if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); - this->plater_->get_notification_manager()->push_notification( notif_type + this->plater_->get_notification_manager()->push_version_notification( notif_type , NotificationManager::NotificationLevel::ImportantNotificationLevel , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) , _u8L("See Releases page.") @@ -3300,6 +3300,19 @@ void GUI_App::on_version_read(wxCommandEvent& evt) return; } if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { + if (m_app_updater->get_triggered_by_user()) + { + std::string text = (*Semver::parse(into_u8(evt.GetString())) == Semver()) + ? Slic3r::format(_u8L("Check for application update has failed.")) + : Slic3r::format(_u8L("No new version is available. Latest release version is %1%."), evt.GetString()); + + this->plater_->get_notification_manager()->push_version_notification(NotificationType::NoNewReleaseAvailable + , NotificationManager::NotificationLevel::RegularNotificationLevel + , text + , std::string() + , std::function() + ); + } return; } // notification diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index e6f9d952a6..e8fd68c81f 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -2246,6 +2246,29 @@ void NotificationManager::push_simplify_suggestion_notification(const std::stri notification->object_id = object_id; push_notification_data(std::move(notification), 0); } +void NotificationManager::push_version_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext, std::function callback) +{ + assert (type == NotificationType::NewAlphaAvailable + || type == NotificationType::NewBetaAvailable + || type == NotificationType::NoNewReleaseAvailable); + + for (std::unique_ptr& notification : m_pop_notifications) { + // NoNewReleaseAvailable must not show if alfa / beta is on. + NotificationType nttype = notification->get_type(); + if (type == NotificationType::NoNewReleaseAvailable + && (notification->get_type() == NotificationType::NewAlphaAvailable + || notification->get_type() == NotificationType::NewBetaAvailable)) { + return; + } + // NoNewReleaseAvailable must close if alfa / beta is being push. + if (notification->get_type() == NotificationType::NoNewReleaseAvailable + && (type == NotificationType::NewAlphaAvailable + || type == NotificationType::NewBetaAvailable)) { + notification->close(); + } + } + push_notification(type, level, text, hypertext, callback); +} void NotificationManager::close_notification_of_type(const NotificationType type) { for (std::unique_ptr ¬ification : m_pop_notifications) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 7cd77a3048..b3a39a936f 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -56,6 +56,7 @@ enum class NotificationType // Like NewAppAvailable but with text and link for alpha / bet release NewAlphaAvailable, NewBetaAvailable, + NoNewReleaseAvailable, // Notification on the start of PrusaSlicer, when updates of system profiles are detected. // Contains a hyperlink to execute installation of the new system profiles. PresetUpdateAvailable, @@ -191,6 +192,9 @@ public: // Object warning with ObjectID, closes when object is deleted. ID used is of object not print like in slicing warning. void push_simplify_suggestion_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "", std::function callback = std::function()); + // Could be either NewAlphaAvailable, NewBetaAvailable or NoNewReleaseAvailable - this function only makes sure only 1 is visible. + void push_version_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext, + std::function callback); // Close object warnings, whose ObjectID is not in the list. // living_oids is expected to be sorted. void remove_simplify_suggestion_of_released_objects(const std::vector& living_oids); diff --git a/src/slic3r/Utils/AppUpdater.cpp b/src/slic3r/Utils/AppUpdater.cpp index a17adea8a5..bd71e86ec1 100644 --- a/src/slic3r/Utils/AppUpdater.cpp +++ b/src/slic3r/Utils/AppUpdater.cpp @@ -323,6 +323,11 @@ void AppUpdater::priv::parse_version_string(const std::string& body) return; #endif // 0 BOOST_LOG_TRIVIAL(error) << "Could not find property tree in version file. Checking for application update has failed."; + // Lets send event with current version, this way if user triggered this check, it will notify him about no new version online. + std::string version = Semver().to_string(); + wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE); + evt->SetString(GUI::from_u8(version)); + GUI::wxGetApp().QueueEvent(evt); return; } std::string tree_string = body.substr(start); From bdcb7732026aa2e6d71108434f892ed6f4b5ce3c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 18 Jan 2023 16:01:09 +0100 Subject: [PATCH 86/86] replace triangulation in SupportSpotGenerator with triangle formula and winding number Use the same apporach in computation of polygon area principal components --- src/libslic3r/BridgeDetector.hpp | 7 +- src/libslic3r/PrincipalComponents2D.cpp | 132 ++++++++++++++++-------- src/libslic3r/PrincipalComponents2D.hpp | 9 +- src/libslic3r/SupportSpotsGenerator.cpp | 105 ++++++------------- 4 files changed, 133 insertions(+), 120 deletions(-) diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index b11736417e..bc5da9712f 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -82,12 +82,11 @@ inline std::tuple detect_bridging_direction(const Polygons &to_co if (floating_polylines.empty()) { // consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges - //use 3mm resolution (should be quite fast, and rough estimation should not cause any problems here) - auto [pc1, pc2] = compute_principal_components(overhang_area, 3.0); - if (pc2 == Vec2d::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok + auto [pc1, pc2] = compute_principal_components(overhang_area); + if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok return {Vec2d{1.0,0.0}, 0.0}; } else { - return {pc2.normalized(), 0.0}; + return {pc2.normalized().cast(), 0.0}; } } diff --git a/src/libslic3r/PrincipalComponents2D.cpp b/src/libslic3r/PrincipalComponents2D.cpp index 4b7c3a1da2..7bdf793157 100644 --- a/src/libslic3r/PrincipalComponents2D.cpp +++ b/src/libslic3r/PrincipalComponents2D.cpp @@ -3,53 +3,97 @@ namespace Slic3r { -// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first -std::tuple compute_principal_components(const Polygons &polys, const double unscaled_resolution) + + +// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance +// none of the values is divided/normalized by area. +// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) +// f(x,y) = x^2 for second moment of area +// and f(x,y) = x*y for second moment of area covariance +std::tuple compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c) { - // USING UNSCALED VALUES - const Vec2d pixel_size = Vec2d(unscaled_resolution, unscaled_resolution); - const auto bb = get_extents(polys); - const Vec2i pixel_count = unscaled(bb.size()).cwiseQuotient(pixel_size).cast() + Vec2i::Ones(); + // based on the following guide: + // Denote the vertices of S by a, b, c. Then the map + // g:(u,v)↦a+u(b−a)+v(c−a) , + // which in coordinates appears as + // g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(1) + // obviously maps S′ bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain + // ∫Sf(x,y)d(x,y)=∫S′f(x(u,v),y(u,v))∣∣Jg(u,v)∣∣ d(u,v) . + // In the case at hand the Jacobian determinant is a constant: From (1) we obtain + // Jg(u,v)=det[xuyuxvyv]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) . + // Therefore we can write + // ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(u,v) dv du , + // where f~ denotes the pullback of f to S′: + // f~(u,v):=f(x(u,v),y(u,v)) . + // Don't forget taking the absolute value of Jg! - std::vector lines{}; - for (Line l : to_lines(polys)) { lines.emplace_back(unscaled(l.a), unscaled(l.b)); } - AABBTreeIndirect::Tree<2, double> tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); - auto is_inside = [&](const Vec2d &point) { - size_t nearest_line_index_out = 0; - Vec2d nearest_point_out = Vec2d::Zero(); - auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, point, nearest_line_index_out, nearest_point_out); - if (distance < 0) return false; - const Linef &line = lines[nearest_line_index_out]; - Vec2d v1 = line.b - line.a; - Vec2d v2 = point - line.a; - if ((v1.x() * v2.y()) - (v1.y() * v2.x()) > 0.0) { return true; } - return false; - }; + float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y())); - double pixel_area = pixel_size.x() * pixel_size.y(); - Vec2d centroid_accumulator = Vec2d::Zero(); - Vec2d second_moment_of_area_accumulator = Vec2d::Zero(); - double second_moment_of_area_covariance_accumulator = 0.0; - double area = 0.0; + // coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x) + // coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y) + // second moment of area for x: f(x, y) = x^2; + // f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form) - for (int x = 0; x < pixel_count.x(); x++) { - for (int y = 0; y < pixel_count.y(); y++) { - Vec2d position = unscaled(bb.min) + pixel_size.cwiseProduct(Vec2d{x, y}); - if (is_inside(position)) { - area += pixel_area; - centroid_accumulator += pixel_area * position; - second_moment_of_area_accumulator += pixel_area * position.cwiseProduct(position); - second_moment_of_area_covariance_accumulator += pixel_area * position.x() * position.y(); - } + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + // integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2) + + Vec2f second_moment_of_area_xy = jacobian_determinant_abs * + (a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) + + a.cwiseProduct(b + c)) / + 12.0f; + // second moment of area covariance : f(x, y) = x*y; + // f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form) + //(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2)) + // == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) + + // intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv = + // 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 + // b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - + // 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du = + // 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2) + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) * + (a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() + + b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y()); + + float area = jacobian_determinant_abs * 0.5f; + + Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f; + + return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance}; +}; + +// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first +std::tuple compute_principal_components(const Polygons &polys) +{ + Vec2f centroid_accumulator = Vec2f::Zero(); + Vec2f second_moment_of_area_accumulator = Vec2f::Zero(); + float second_moment_of_area_covariance_accumulator = 0.0f; + float area = 0.0f; + + for (const Polygon &poly : polys) { + Vec2f p0 = unscaled(poly.first_point()).cast(); + for (size_t i = 2; i < poly.points.size(); i++) { + Vec2f p1 = unscaled(poly.points[i - 1]).cast(); + Vec2f p2 = unscaled(poly.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [triangle_area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + area += sign * triangle_area; + centroid_accumulator += sign * first_moment_of_area; + second_moment_of_area_accumulator += sign * second_moment_area; + second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; } } if (area <= 0.0) { - return {Vec2d::Zero(), Vec2d::Zero()}; + return {Vec2f::Zero(), Vec2f::Zero()}; } - Vec2d centroid = centroid_accumulator / area; - Vec2d variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid); + Vec2f centroid = centroid_accumulator / area; + Vec2f variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid); double covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); #if 0 std::cout << "area : " << area << std::endl; @@ -58,7 +102,7 @@ std::tuple compute_principal_components(const Polygons &polys, con std::cout << "covariance : " << covariance << std::endl; #endif if (abs(covariance) < EPSILON) { - std::tuple result{Vec2d{variance.x(), 0.0}, Vec2d{0.0, variance.y()}}; + std::tuple result{Vec2f{variance.x(), 0.0}, Vec2f{0.0, variance.y()}}; if (variance.y() > variance.x()) { return {std::get<1>(result), std::get<0>(result)}; } else @@ -72,12 +116,12 @@ std::tuple compute_principal_components(const Polygons &polys, con // Eigenvalues are solutions to det(C - lI) = 0, where l is the eigenvalue and I unit matrix // Eigenvector for eigenvalue l is any vector v such that Cv = lv - double eigenvalue_a = 0.5 * (variance.x() + variance.y() + - sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance)); - double eigenvalue_b = 0.5 * (variance.x() + variance.y() - - sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance)); - Vec2d eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0}; - Vec2d eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0}; + float eigenvalue_a = 0.5f * (variance.x() + variance.y() + + sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance)); + float eigenvalue_b = 0.5f * (variance.x() + variance.y() - + sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance)); + Vec2f eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0f}; + Vec2f eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0f}; #if 0 std::cout << "eigenvalue_a: " << eigenvalue_a << std::endl; diff --git a/src/libslic3r/PrincipalComponents2D.hpp b/src/libslic3r/PrincipalComponents2D.hpp index 0eccdfcc5e..dc8897a7a7 100644 --- a/src/libslic3r/PrincipalComponents2D.hpp +++ b/src/libslic3r/PrincipalComponents2D.hpp @@ -9,8 +9,15 @@ namespace Slic3r { +// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance +// none of the values is divided/normalized by area. +// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) +// f(x,y) = x^2 for second moment of area +// and f(x,y) = x*y for second moment of area covariance +std::tuple compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c); + // returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first -std::tuple compute_principal_components(const Polygons &polys, const double unscaled_resolution); +std::tuple compute_principal_components(const Polygons &polys); } diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index eaf7dd57d1..e881f7bba1 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -1,5 +1,6 @@ #include "SupportSpotsGenerator.hpp" +#include "BoundingBox.hpp" #include "ExPolygon.hpp" #include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" @@ -7,6 +8,7 @@ #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" +#include "PrincipalComponents2D.hpp" #include "Print.hpp" #include "PrintBase.hpp" #include "Tesselate.hpp" @@ -117,13 +119,14 @@ public: size_t to_cell_index(const Vec3i &cell_coords) const { +#ifdef DETAILED_DEBUG_LOGS assert(cell_coords.x() >= 0); assert(cell_coords.x() < cell_count.x()); assert(cell_coords.y() >= 0); assert(cell_coords.y() < cell_count.y()); assert(cell_coords.z() >= 0); assert(cell_coords.z() < cell_count.z()); - +#endif return cell_coords.z() * cell_count.x() * cell_count.y() + cell_coords.y() * cell_count.x() + cell_coords.x(); } @@ -244,6 +247,7 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit const float flow_width = get_flow_width(layer_region, entity->role()); + // Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, prev_layer_lines, flow_width, params.bridge_distance); @@ -262,6 +266,7 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : ExtrusionLine{}; + // correctify the distance sign using slice polygons float sign = (prev_layer_boundary.distance_from_lines(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; curr_point.distance *= sign; @@ -297,84 +302,39 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit } } -// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance -// none of the values is divided/normalized by area. -// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) -// f(x,y) = x^2 for second moment of area -// and f(x,y) = x*y for second moment of area covariance -std::tuple compute_triangle_moments_of_area(const Vec2f &a, const Vec2f &b, const Vec2f &c) -{ - // based on the following guide: - // Denote the vertices of S by a, b, c. Then the map - // g:(u,v)↦a+u(b−a)+v(c−a) , - // which in coordinates appears as - // g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(1) - // obviously maps S′ bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain - // ∫Sf(x,y)d(x,y)=∫S′f(x(u,v),y(u,v))∣∣Jg(u,v)∣∣ d(u,v) . - // In the case at hand the Jacobian determinant is a constant: From (1) we obtain - // Jg(u,v)=det[xuyuxvyv]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) . - // Therefore we can write - // ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(u,v) dv du , - // where f~ denotes the pullback of f to S′: - // f~(u,v):=f(x(u,v),y(u,v)) . - // Don't forget taking the absolute value of Jg! - - float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y())); - - // coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x) - // coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y) - // second moment of area for x: f(x, y) = x^2; - // f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form) - - // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du - // integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2) - - Vec2f second_moment_of_area_xy = jacobian_determinant_abs * - (a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) + - a.cwiseProduct(b + c)) / - 12.0f; - // second moment of area covariance : f(x, y) = x*y; - // f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form) - //(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2)) - // == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) - - // intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv = - // 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 - // b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - - // 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du = - // 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2) - // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du - float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) * - (a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() + - b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y()); - - float area = jacobian_determinant_abs * 0.5f; - - Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f; - - return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance}; -}; - SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) { SliceConnection connection; const LayerSlice &slice = layer->lslices_ex[slice_idx]; - ExPolygon slice_poly = layer->lslices[slice_idx]; + Polygons slice_polys = to_polygons(layer->lslices[slice_idx]); + BoundingBox slice_bb = get_extents(slice_polys); const Layer *lower_layer = layer->lower_layer; - ExPolygons below_polys{}; - for (const auto &link : slice.overlaps_below) { below_polys.push_back(lower_layer->lslices[link.slice_idx]); } - ExPolygons overlap = intersection_ex({slice_poly}, below_polys); + ExPolygons below{}; + for (const auto &link : slice.overlaps_below) { below.push_back(lower_layer->lslices[link.slice_idx]); } + Polygons below_polys = to_polygons(below); - std::vector triangles = triangulate_expolygons_2f(overlap); - for (size_t idx = 0; idx < triangles.size(); idx += 3) { - auto [area, first_moment_of_area, second_moment_area, - second_moment_of_area_covariance] = compute_triangle_moments_of_area(triangles[idx], triangles[idx + 1], triangles[idx + 2]); - connection.area += area; - connection.centroid_accumulator += Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); - connection.second_moment_of_area_accumulator += second_moment_area; - connection.second_moment_of_area_covariance_accumulator += second_moment_of_area_covariance; + BoundingBox below_bb = get_extents(below_polys); + + Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb), + ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb)); + + for (const Polygon &poly : overlap) { + Vec2f p0 = unscaled(poly.first_point()).cast(); + for (size_t i = 2; i < poly.points.size(); i++) { + Vec2f p1 = unscaled(poly.points[i - 1]).cast(); + Vec2f p2 = unscaled(poly.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + connection.area += sign * area; + connection.centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); + connection.second_moment_of_area_accumulator += sign * second_moment_area; + connection.second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; + } } return connection; @@ -973,6 +933,9 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) std::vector current_layer_lines; for (const LayerRegion *layer_region : l->regions()) { for (const ExtrusionEntity *extrusion : layer_region->perimeters().flatten().entities) { + + if (!extrusion->role().is_external_perimeter()) continue; + Points extrusion_pts; extrusion->collect_points(extrusion_pts); float flow_width = get_flow_width(layer_region, extrusion->role());