From 3fcf194e39e93d36bc164acffbf7f4ad2c67fb6f Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 13 Mar 2018 12:39:57 +0100 Subject: [PATCH 001/145] ConfigWizard: Basic structure / WIP --- lib/Slic3r/GUI.pm | 20 +- lib/Slic3r/GUI/MainFrame.pm | 8 +- resources/icons/printers/MK2S.png | Bin 0 -> 58794 bytes resources/icons/printers/MK2SMM.png | Bin 0 -> 57081 bytes resources/icons/printers/MK3.png | Bin 0 -> 64194 bytes resources/profiles/PrusaResearch.ini | 8 +- xs/CMakeLists.txt | 5 + xs/src/libslic3r/Utils.hpp | 2 + xs/src/slic3r/GUI/AppConfig.cpp | 2 +- xs/src/slic3r/GUI/ConfigWizard.cpp | 433 +++++++++++++++++++++ xs/src/slic3r/GUI/ConfigWizard.hpp | 38 ++ xs/src/slic3r/GUI/ConfigWizard_private.hpp | 162 ++++++++ xs/src/slic3r/GUI/GUI.cpp | 15 + xs/src/slic3r/GUI/GUI.hpp | 4 + xs/src/slic3r/GUI/Preferences.cpp | 1 + xs/src/slic3r/GUI/Preset.hpp | 9 +- xs/src/slic3r/GUI/PresetBundle.cpp | 31 +- xs/src/slic3r/GUI/PresetBundle.hpp | 1 + xs/src/slic3r/Utils/PresetUpdate.cpp | 104 +++++ xs/src/slic3r/Utils/PresetUpdate.hpp | 38 ++ xs/xsp/GUI.xsp | 3 + xs/xsp/Utils_PresetUpdate.xsp | 18 + xs/xsp/my.map | 3 + 23 files changed, 881 insertions(+), 24 deletions(-) create mode 100644 resources/icons/printers/MK2S.png create mode 100644 resources/icons/printers/MK2SMM.png create mode 100644 resources/icons/printers/MK3.png create mode 100644 xs/src/slic3r/GUI/ConfigWizard.cpp create mode 100644 xs/src/slic3r/GUI/ConfigWizard.hpp create mode 100644 xs/src/slic3r/GUI/ConfigWizard_private.hpp create mode 100644 xs/src/slic3r/Utils/PresetUpdate.cpp create mode 100644 xs/src/slic3r/Utils/PresetUpdate.hpp create mode 100644 xs/xsp/Utils_PresetUpdate.xsp diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 4ec388c147..0c5e87429d 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -101,6 +101,8 @@ sub OnInit { $self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->save; + my $slic3r_update_avail = $self->{app_config}->get("version_check") && $self->{app_config}->get("version_online") != $Slic3r::VERSION; + Slic3r::GUI::set_app_config($self->{app_config}); Slic3r::GUI::load_language(); @@ -111,6 +113,7 @@ sub OnInit { warn $@ . "\n"; show_error(undef, $@); } + # TODO: check previously downloaded updates $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); @@ -134,14 +137,19 @@ sub OnInit { $self->{app_config}->save if $self->{app_config}->dirty; }); - if ($run_wizard) { - # On OSX the UI was not initialized correctly if the wizard was called - # before the UI was up and running. - $self->CallAfter(sub { + # On OSX the UI was not initialized correctly if the wizard was called + # before the UI was up and running. + $self->CallAfter(sub { + if ($slic3r_update_avail) { + # TODO + } elsif ($run_wizard) { # Run the config wizard, don't offer the "reset user profile" checkbox. $self->{mainframe}->config_wizard(1); - }); - } + } + + # XXX: recreate_GUI ??? + Slic3r::PresetUpdater::download($self->{app_config}, $self->{preset_bundle}); + }); # The following event is emited by the C++ menu implementation of application language change. EVT_COMMAND($self, -1, $LANGUAGE_CHANGE_EVENT, sub{ diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index b2f51b9e1d..68fc963394 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -95,7 +95,7 @@ sub new { }); $self->update_ui_from_settings; - + return $self; } @@ -643,6 +643,12 @@ sub config_wizard { my ($self, $fresh_start) = @_; # Exit wizard if there are unsaved changes and the user cancels the action. return unless $self->check_unsaved_changes; + + + # TODO: Offer "reset user profile" + Slic3r::GUI::open_config_wizard(); + return; + # Enumerate the profiles bundled with the Slic3r installation under resources/profiles. my $directory = Slic3r::resources_dir() . "/profiles"; my @profiles = (); diff --git a/resources/icons/printers/MK2S.png b/resources/icons/printers/MK2S.png new file mode 100644 index 0000000000000000000000000000000000000000..925447cf201911308d2a4788e6a0f8fd6f9db2c6 GIT binary patch literal 58794 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 DH{Xdt literal 0 HcmV?d00001 diff --git a/resources/icons/printers/MK2SMM.png b/resources/icons/printers/MK2SMM.png new file mode 100644 index 0000000000000000000000000000000000000000..d6ff161259d9752504ad959c87d0e8529ad97541 GIT binary patch literal 57081 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 literal 0 HcmV?d00001 diff --git a/resources/icons/printers/MK3.png b/resources/icons/printers/MK3.png new file mode 100644 index 0000000000000000000000000000000000000000..5279ba01e060d81738565c5fd99c11e06fbef540 GIT binary patch literal 64194 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�WrFjD4RdqUre@ogt*oytQM-njETd@mVx1yo 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

H`@`RyGv2V%cRm zB3AgRoS99FaA*JaJF3;LSMN3m=6GJltr65d8uPZ+g6jrBL}C8c*uw3>bL(4p&%yn* z1L!=|>Nlac8C>4j2 zL@Kf(2V*EpivaRK4Zkrq&<;=-W7uVvy}0;-3pn(tb!=~fSG@d1Ty?`NN4)smEEyf; zU2i#x-(2~h{PLGqKJt=d=iPVZ7eD{`k!h{QOs~7?qyPTyiNC0;6h)m#Ya%7-&UW>7 zJ8nys+fpe*mSwc+O`JD4Z;7IShBz0z|5T)rGHBkp3d+;96ql*WcDD<*Q!(p&@F*O( z!RqQ8b`13V<`5Ii$>@fKi^Jyu0u(cj{&P?K?a&qLKAx=DzQbl#^&pT30Wv>8TLi

#>L=X2CcPG;rmSx$cc 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

mM0xmn2E#!U}l&p`N$PZaZ@8&36buIP20?_pDBU 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 literal 0 HcmV?d00001 diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index fa4d48cdae..0a1d26eaa0 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -7,21 +7,25 @@ name = Prusa Research # This means, the server may force the Slic3r configuration to be downgraded. config_version = 0.1 # Where to get the updates from? -config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch.ini +# TODO: proper URL +# config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch.ini +config_update_url = https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/e9187c3e15ceaf1a90f29b7c43cf3ccc746140f0/PrusaResearch.ini # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. #TODO: One day we may differentiate variants of the nozzles / hot ends, #for example by the melt zone size, or whether the nozzle is hardened. [printer_model:MK3] +name = Original Prusa i3 MK3 variants = 0.4; 0.25; 0.6 [printer_model:MK2S] +name = Original Prusa i3 MK2S variants = 0.4; 0.25; 0.6 [printer_model:MK2SMM] # Printer model name will be shown by the installation wizard. -name = MK2S Multi Material +name = Original Prusa i3 MK2SMM variants = 0.4; 0.6 # All presets starting with asterisk, for example *common*, are intermediate and they will diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index f28db8f92b..e73c8429a5 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -205,12 +205,16 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/BonjourDialog.hpp ${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp ${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp + ${LIBDIR}/slic3r/GUI/ConfigWizard.cpp + ${LIBDIR}/slic3r/GUI/ConfigWizard.hpp ${LIBDIR}/slic3r/Utils/Http.cpp ${LIBDIR}/slic3r/Utils/Http.hpp ${LIBDIR}/slic3r/Utils/OctoPrint.cpp ${LIBDIR}/slic3r/Utils/OctoPrint.hpp ${LIBDIR}/slic3r/Utils/Bonjour.cpp ${LIBDIR}/slic3r/Utils/Bonjour.hpp + ${LIBDIR}/slic3r/Utils/PresetUpdate.cpp + ${LIBDIR}/slic3r/Utils/PresetUpdate.hpp ) add_library(admesh STATIC @@ -356,6 +360,7 @@ set(XS_XSP_FILES ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp ${XSP_DIR}/Utils_OctoPrint.xsp + ${XSP_DIR}/Utils_PresetUpdate.xsp ${XSP_DIR}/XS.xsp ) foreach (file ${XS_XSP_FILES}) diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 27e7fad6b0..7e2fe31800 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -3,6 +3,8 @@ #include +#include "libslic3r.h" + namespace Slic3r { extern void set_logging_level(unsigned int level); diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index e32b645b47..4bdf88dda6 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -42,7 +42,7 @@ void AppConfig::set_defaults() set("no_defaults", "1"); if (get("show_incompatible_presets").empty()) set("show_incompatible_presets", "0"); - // Version check is enabled by default in the config, but it is not implemented yet. + // Version check is enabled by default in the config, but it is not implemented yet. // XXX if (get("version_check").empty()) set("version_check", "1"); // Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers. diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp new file mode 100644 index 0000000000..3cad789b65 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -0,0 +1,433 @@ +#include "ConfigWizard_private.hpp" + +#include // XXX +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Utils.hpp" +#include "PresetBundle.hpp" +#include "GUI.hpp" + +namespace fs = boost::filesystem; + +namespace Slic3r { +namespace GUI { + + +// Wizard page base + +ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname) : + wxPanel(parent), + parent(parent), + shortname(std::move(shortname)), + p_prev(nullptr), + p_next(nullptr) +{ + auto *sizer = new wxBoxSizer(wxVERTICAL); + + auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + auto font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + font.SetWeight(wxFONTWEIGHT_BOLD); + font.SetPointSize(14); + text->SetFont(font); + sizer->Add(text, 0, wxALIGN_LEFT, 0); + sizer->AddSpacer(10); + + content = new wxBoxSizer(wxVERTICAL); + sizer->Add(content, 1); + + SetSizer(sizer); + + this->Hide(); + + Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { + this->Layout(); + event.Skip(); + }); +} + +ConfigWizardPage::~ConfigWizardPage() {} + +ConfigWizardPage* ConfigWizardPage::chain(ConfigWizardPage *page) +{ + if (p_next != nullptr) { p_next->p_prev = nullptr; } + p_next = page; + if (page != nullptr) { + if (page->p_prev != nullptr) { page->p_prev->p_next = nullptr; } + page->p_prev = this; + } + + return page; +} + +void ConfigWizardPage::append_text(wxString text) +{ + auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + widget->Wrap(CONTENT_WIDTH); + widget->SetMinSize(wxSize(CONTENT_WIDTH, -1)); + // content->Add(widget, 1, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10); + content->Add(widget, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10); +} + +void ConfigWizardPage::append_widget(wxWindow *widget, int proportion) +{ + content->Add(widget, proportion, wxEXPAND | wxTOP | wxBOTTOM, 10); +} + +void ConfigWizardPage::append_spacer(int space) +{ + content->AddSpacer(space); +} + +bool ConfigWizardPage::Show(bool show) +{ + if (extra_buttons() != nullptr) { extra_buttons()->Show(show); } + return wxPanel::Show(show); +} + +void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable); } + + +// Wizard pages + +PageWelcome::PageWelcome(ConfigWizard *parent, const PresetBundle &bundle) : + ConfigWizardPage(parent, _(L("Welcome to the Slic3r Configuration assistant")), _(L("Welcome"))), + others_buttons(new wxPanel(parent)), + variants_checked(0) +{ + append_text(_(L("Hello, welcome to Slic3r Prusa Edition! TODO: This text."))); + + const auto &vendors = bundle.vendors; + const auto vendor_prusa = std::find(vendors.cbegin(), vendors.cend(), VendorProfile("PrusaResearch")); + + // TODO: preload checkiness from app config + + if (vendor_prusa != vendors.cend()) { + const auto &models = vendor_prusa->models; + + auto *printer_picker = new wxPanel(this); + auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20); + printer_grid->SetFlexibleDirection(wxVERTICAL); + printer_picker->SetSizer(printer_grid); + + auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + namefont.SetWeight(wxFONTWEIGHT_BOLD); + + for (auto model = models.cbegin(); model != models.cend(); ++model) { + auto *panel = new wxPanel(printer_picker); + auto *sizer = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(sizer); + + auto *title = new wxStaticText(panel, wxID_ANY, model->name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + title->SetFont(namefont); + sizer->Add(title, 0, wxBOTTOM, 3); + + auto bitmap_file = wxString::Format("printers/%s.png", model->id); + wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG); + auto *bitmap_widget = new wxStaticBitmap(panel, wxID_ANY, bitmap); + sizer->Add(bitmap_widget, 0, wxBOTTOM, 3); + + sizer->AddSpacer(20); + + for (const auto &variant : model->variants) { + auto *cbox = new wxCheckBox(panel, wxID_ANY, wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle")))); + sizer->Add(cbox, 0, wxBOTTOM, 3); + cbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { + this->variants_checked += event.IsChecked() ? 1 : -1; + this->on_variant_checked(); + }); + } + + printer_grid->Add(panel); + } + + append_widget(printer_picker); + } + + { + auto *sizer = new wxBoxSizer(wxHORIZONTAL); + auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors"))); + auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup"))); + + sizer->Add(other_vendors); + sizer->AddSpacer(BTN_SPACING); + sizer->Add(custom_setup); + + other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); }); + custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); }); + + others_buttons->SetSizer(sizer); + } +} + +void PageWelcome::on_page_set() +{ + chain(wizard_p()->page_update); + on_variant_checked(); +} + +void PageWelcome::on_variant_checked() +{ + enable_next(variants_checked > 0); +} + +PageUpdate::PageUpdate(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))) +{ + append_text(_(L("TODO: text"))); + auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for Slic3r updates"))); + box_slic3r->SetValue(true); + append_widget(box_slic3r); + + append_text(_(L("TODO: text"))); + auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically"))); + box_presets->SetValue(true); + append_widget(box_presets); +} + +void PageUpdate::presets_update_enable(bool enable) +{ + // TODO +} + +PageVendors::PageVendors(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors"))) +{} + +PageFirmware::PageFirmware(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))) +{} + +PageBedShape::PageBedShape(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Bed Shape and Size")), _(L("Bed Shape"))) +{} + +PageDiameters::PageDiameters(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Filament and Nozzle Diameter")), _(L("Print Diameters"))) +{} + +PageTemperatures::PageTemperatures(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Bed and Extruder Temperature")), _(L("Temperatures"))) +{} + + +// Index + +ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) : + wxPanel(parent), + bg(GUI::from_u8(Slic3r::var("Slic3r_192px_transparent.png")), wxBITMAP_TYPE_PNG), + bullet_black(GUI::from_u8(Slic3r::var("bullet_black.png")), wxBITMAP_TYPE_PNG), + bullet_blue(GUI::from_u8(Slic3r::var("bullet_blue.png")), wxBITMAP_TYPE_PNG), + bullet_white(GUI::from_u8(Slic3r::var("bullet_white.png")), wxBITMAP_TYPE_PNG) +{ + SetMinSize(bg.GetSize()); + Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); + + wxClientDC dc(this); + text_height = dc.GetCharHeight(); +} + +void ConfigWizardIndex::load_items(ConfigWizardPage *firstpage) +{ + items.clear(); + item_active = items.cend(); + + for (auto *page = firstpage; page != nullptr; page = page->page_next()) { + items.emplace_back(page->shortname); + } + + Refresh(); +} + +void ConfigWizardIndex::set_active(ConfigWizardPage *page) +{ + item_active = std::find(items.cbegin(), items.cend(), page->shortname); + Refresh(); +} + +void ConfigWizardIndex::on_paint(wxPaintEvent & evt) +{ + enum { + MARGIN = 10, + SPACING = 5, + }; + + const auto size = GetClientSize(); + const auto h = size.GetHeight(); + const auto w = size.GetWidth(); + if (h == 0 || w == 0) { return; } + + wxPaintDC dc(this); + dc.DrawBitmap(bg, 0, h - bg.GetHeight(), false); + + const auto bullet_w = bullet_black.GetSize().GetWidth(); + const auto bullet_h = bullet_black.GetSize().GetHeight(); + const int yoff_icon = bullet_h < text_height ? (text_height - bullet_h) / 2 : 0; + const int yoff_text = bullet_h > text_height ? (bullet_h - text_height) / 2 : 0; + const int yinc = std::max(bullet_h, text_height) + SPACING; + + unsigned y = 0; + for (auto it = items.cbegin(); it != items.cend(); ++it) { + if (it < item_active) { dc.DrawBitmap(bullet_black, MARGIN, y + yoff_icon, false); } + if (it == item_active) { dc.DrawBitmap(bullet_blue, MARGIN, y + yoff_icon, false); } + if (it > item_active) { dc.DrawBitmap(bullet_white, MARGIN, y + yoff_icon, false); } + dc.DrawText(*it, MARGIN + bullet_w + SPACING, y + yoff_text); + y += yinc; + } +} + + + +// priv + +void ConfigWizard::priv::index_refresh() +{ + index->load_items(page_welcome); +} + +void ConfigWizard::priv::add_page(ConfigWizardPage *page) +{ + topsizer->Add(page, 0, wxEXPAND); + + auto *extra_buttons = page->extra_buttons(); + if (extra_buttons != nullptr) { + btnsizer->Prepend(extra_buttons, 0); + } +} + +void ConfigWizard::priv::set_page(ConfigWizardPage *page) +{ + if (page == nullptr) { return; } + if (page_current != nullptr) { page_current->Hide(); } + page_current = page; + enable_next(true); + + page->on_page_set(); + index->load_items(page_welcome); + index->set_active(page); + page->Show(); + + btn_prev->Enable(page->page_prev() != nullptr); + btn_next->Show(page->page_next() != nullptr); + btn_finish->Show(page->page_next() == nullptr); + + q->Layout(); +} + +void ConfigWizard::priv::enable_next(bool enable) +{ + btn_next->Enable(enable); + btn_finish->Enable(enable); +} + +void ConfigWizard::priv::on_other_vendors() +{ + page_welcome + ->chain(page_vendors) + ->chain(page_update); + set_page(page_vendors); +} + +void ConfigWizard::priv::on_custom_setup() +{ + page_welcome->chain(page_firmware); + page_temps->chain(page_update); + set_page(page_firmware); +} + +// Public + +ConfigWizard::ConfigWizard(wxWindow *parent, const PresetBundle &bundle) : + wxDialog(parent, wxID_ANY, _(L("Configuration Assistant")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + p(new priv(this)) +{ + p->index = new ConfigWizardIndex(this); + + auto *vsizer = new wxBoxSizer(wxVERTICAL); + p->topsizer = new wxBoxSizer(wxHORIZONTAL); + auto *hline = new wxStaticLine(this); + p->btnsizer = new wxBoxSizer(wxHORIZONTAL); + + p->topsizer->Add(p->index, 0, wxEXPAND); + p->topsizer->AddSpacer(INDEX_MARGIN); + + // TODO: btn labels vs default w/ icons ... use arrows from resources? (no apply icon) + // Also: http://docs.wxwidgets.org/3.0/page_stockitems.html + p->btn_prev = new wxButton(this, wxID_BACKWARD, _(L("< &Back"))); + p->btn_next = new wxButton(this, wxID_FORWARD, _(L("&Next >"))); + p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish"))); + p->btn_cancel = new wxButton(this, wxID_CANCEL); + 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); + + p->add_page(p->page_welcome = new PageWelcome(this, bundle)); + p->add_page(p->page_update = new PageUpdate(this)); + p->add_page(p->page_vendors = new PageVendors(this)); + p->add_page(p->page_firmware = new PageFirmware(this)); + p->add_page(p->page_bed = new PageBedShape(this)); + p->add_page(p->page_diams = new PageDiameters(this)); + p->add_page(p->page_temps = new PageTemperatures(this)); + p->index_refresh(); + + p->page_welcome->chain(p->page_update); + p->page_firmware + ->chain(p->page_bed) + ->chain(p->page_diams) + ->chain(p->page_temps); + + vsizer->Add(p->topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); + vsizer->Add(hline, 0, wxEXPAND); + vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); + + p->set_page(p->page_welcome); + SetSizerAndFit(vsizer); + SetMinSize(GetSize()); + + p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); + p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); }); +} + +ConfigWizard::~ConfigWizard() {} + +void ConfigWizard::run(wxWindow *parent) +{ + PresetBundle bundle; + + const auto profiles_dir = fs::path(resources_dir()) / "profiles"; + for (fs::directory_iterator it(profiles_dir); it != fs::directory_iterator(); ++it) { + if (it->path().extension() == ".ini") { + bundle.load_configbundle(it->path().native(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + } + } + + // XXX + for (const auto &vendor : bundle.vendors) { + std::cerr << "vendor: " << vendor.name << std::endl; + std::cerr << " URL: " << vendor.config_update_url << std::endl; + for (const auto &model : vendor.models) { + std::cerr << "\tmodel: " << model.id << " (" << model.name << ")" << std::endl; + for (const auto &variant : model.variants) { + std::cerr << "\t\tvariant: " << variant.name << std::endl; + } + } + } + + ConfigWizard wizard(parent, bundle); + wizard.ShowModal(); +} + + +} +} diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp new file mode 100644 index 0000000000..1b14e29be7 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -0,0 +1,38 @@ +#ifndef slic3r_ConfigWizard_hpp_ +#define slic3r_ConfigWizard_hpp_ + +#include + +#include + +namespace Slic3r { + +class PresetBundle; + +namespace GUI { + + +class ConfigWizard: public wxDialog +{ +public: + ConfigWizard(wxWindow *parent, const PresetBundle &bundle); + ConfigWizard(ConfigWizard &&) = delete; + ConfigWizard(const ConfigWizard &) = delete; + ConfigWizard &operator=(ConfigWizard &&) = delete; + ConfigWizard &operator=(const ConfigWizard &) = delete; + ~ConfigWizard(); + + static void run(wxWindow *parent); +private: + struct priv; + std::unique_ptr p; + + friend class ConfigWizardPage; +}; + + + +} +} + +#endif diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp new file mode 100644 index 0000000000..ba028c0e87 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -0,0 +1,162 @@ +#ifndef slic3r_ConfigWizard_private_hpp_ +#define slic3r_ConfigWizard_private_hpp_ + +#include "ConfigWizard.hpp" + +#include + +#include +#include +#include + + +namespace Slic3r { +namespace GUI { + + +enum { + DIALOG_MARGIN = 15, + INDEX_MARGIN = 40, + BTN_SPACING = 10, +}; + +struct ConfigWizardPage: wxPanel +{ + enum { + CONTENT_WIDTH = 500, + }; + + ConfigWizard *parent; + const wxString shortname; + wxBoxSizer *content; + + ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname); + + virtual ~ConfigWizardPage(); + + ConfigWizardPage *page_prev() const { return p_prev; } + ConfigWizardPage *page_next() const { return p_next; } + ConfigWizardPage* chain(ConfigWizardPage *page); + + void append_text(wxString text); + void append_widget(wxWindow *widget, int proportion = 0); + void append_spacer(int space); + + ConfigWizard::priv *wizard_p() const { return parent->p.get(); } + + virtual bool Show(bool show = true); + virtual bool Hide() { return Show(false); } + virtual wxPanel* extra_buttons() { return nullptr; } + virtual void on_page_set() {} + + void enable_next(bool enable); +private: + ConfigWizardPage *p_prev; + ConfigWizardPage *p_next; +}; + +struct PageWelcome: ConfigWizardPage +{ + wxPanel *others_buttons; + unsigned variants_checked; + + PageWelcome(ConfigWizard *parent, const PresetBundle &bundle); + + virtual wxPanel* extra_buttons() { return others_buttons; } + virtual void on_page_set(); + + void on_variant_checked(); +}; + +struct PageUpdate: ConfigWizardPage +{ + PageUpdate(ConfigWizard *parent); + + void presets_update_enable(bool enable); +}; + +struct PageVendors: ConfigWizardPage +{ + PageVendors(ConfigWizard *parent); +}; + +struct PageFirmware: ConfigWizardPage +{ + PageFirmware(ConfigWizard *parent); +}; + +struct PageBedShape: ConfigWizardPage +{ + PageBedShape(ConfigWizard *parent); +}; + +struct PageDiameters: ConfigWizardPage +{ + PageDiameters(ConfigWizard *parent); +}; + +struct PageTemperatures: ConfigWizardPage +{ + PageTemperatures(ConfigWizard *parent); +}; + + +class ConfigWizardIndex: public wxPanel +{ +public: + ConfigWizardIndex(wxWindow *parent); + + void load_items(ConfigWizardPage *firstpage); + void set_active(ConfigWizardPage *page); +private: + const wxBitmap bg; + const wxBitmap bullet_black; + const wxBitmap bullet_blue; + const wxBitmap bullet_white; + int text_height; + + std::vector items; + std::vector::const_iterator item_active; + + void on_paint(wxPaintEvent & evt); +}; + +struct ConfigWizard::priv +{ + ConfigWizard *q; + wxBoxSizer *topsizer = nullptr; + wxBoxSizer *btnsizer = nullptr; + ConfigWizardPage *page_current = nullptr; + ConfigWizardIndex *index = nullptr; + wxButton *btn_prev = nullptr; + wxButton *btn_next = nullptr; + wxButton *btn_finish = nullptr; + wxButton *btn_cancel = nullptr; + + PageWelcome *page_welcome = nullptr; + PageUpdate *page_update = nullptr; + PageVendors *page_vendors = nullptr; + PageFirmware *page_firmware = nullptr; + PageBedShape *page_bed = nullptr; + PageDiameters *page_diams = nullptr; + PageTemperatures *page_temps = nullptr; + + priv(ConfigWizard *q) : q(q) {} + + void add_page(ConfigWizardPage *page); + void index_refresh(); + void set_page(ConfigWizardPage *page); + void go_prev() { if (page_current != nullptr) { set_page(page_current->page_prev()); } } + void go_next() { if (page_current != nullptr) { set_page(page_current->page_next()); } } + void enable_next(bool enable); + + void on_other_vendors(); + void on_custom_setup(); +}; + + + +} +} + +#endif diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 3eca4e7075..53288067cf 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -44,6 +44,7 @@ #include "TabIface.hpp" #include "AppConfig.hpp" #include "Utils.hpp" +#include "ConfigWizard.hpp" #include "Preferences.hpp" #include "PresetBundle.hpp" @@ -352,6 +353,20 @@ void add_debug_menu(wxMenuBar *menu, int event_language_change) //#endif } +void open_config_wizard() +{ + if (g_wxMainFrame == nullptr) { + throw std::runtime_error("Main frame not set"); + } + + // auto *wizard = new ConfigWizard(static_cast(g_wxMainFrame)); // FIXME: lifetime + + // wizard->run(); + ConfigWizard::run(g_wxMainFrame); + + // show_info(g_wxMainFrame, "After wizard", "After wizard"); +} + void open_preferences_dialog(int event_preferences) { auto dlg = new PreferencesDialog(g_wxMainFrame, event_preferences); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 362b15307b..1f93e18e99 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -73,6 +73,7 @@ void break_to_debugger(); // Passing the wxWidgets GUI classes instantiated by the Perl part to C++. void set_wxapp(wxApp *app); void set_main_frame(wxFrame *main_frame); +// wxFrame* get_main_frame(); void set_tab_panel(wxNotebook *tab_panel); void set_app_config(AppConfig *app_config); void set_preset_bundle(PresetBundle *preset_bundle); @@ -84,6 +85,9 @@ wxColour* get_sys_label_clr(); void add_debug_menu(wxMenuBar *menu, int event_language_change); +// Opens the first-time configuration wizard +void open_config_wizard(); + // Create "Preferences" dialog after selecting menu "Preferences" in Perl part void open_preferences_dialog(int event_preferences); diff --git a/xs/src/slic3r/GUI/Preferences.cpp b/xs/src/slic3r/GUI/Preferences.cpp index 0009b17ecd..6731cd394d 100644 --- a/xs/src/slic3r/GUI/Preferences.cpp +++ b/xs/src/slic3r/GUI/Preferences.cpp @@ -14,6 +14,7 @@ void PreferencesDialog::build() m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; + // TODO // $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( // opt_id = > 'version_check', // type = > 'bool', diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 3634c5dd94..78e15badae 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -40,7 +40,7 @@ public: struct PrinterModel { PrinterModel() {} - PrinterModel(const std::string &name) : name(name) {} + std::string id; std::string name; bool enabled = true; std::vector variants; @@ -51,11 +51,10 @@ public: return nullptr; } const PrinterVariant* variant(const std::string &name) const { return const_cast(this)->variant(name); } - - bool operator< (const PrinterModel &rhs) const { return this->name < rhs.name; } - bool operator==(const PrinterModel &rhs) const { return this->name == rhs.name; } }; - std::set models; + std::vector models; + + VendorProfile(std::string id) : id(std::move(id)) {} size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; } diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 7131bf7716..241f0433ef 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -4,6 +4,7 @@ #include "PresetBundle.hpp" #include "BitmapCache.hpp" +#include #include #include #include @@ -104,6 +105,7 @@ void PresetBundle::setup_directories() std::initializer_list paths = { data_dir, data_dir / "vendor", + data_dir / "cache", #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. data_dir / "presets", @@ -198,6 +200,7 @@ static inline std::string remove_ini_suffix(const std::string &name) // If the "vendor" section is missing, enable all models and variants of the particular vendor. void PresetBundle::load_installed_printers(const AppConfig &config) { + // TODO // m_storage } @@ -667,7 +670,7 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorProfile &vendor_profile) { const std::string printer_model_key = "printer_model:"; - for (auto §ion : tree) + for (auto §ion : tree) { if (section.first == "vendor") { // Load the names of the active presets. for (auto &kvp : section.second) { @@ -682,7 +685,8 @@ static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorP } } else if (boost::starts_with(section.first, printer_model_key)) { VendorProfile::PrinterModel model; - model.name = section.first.substr(printer_model_key.size()); + model.id = section.first.substr(printer_model_key.size()); + model.name = section.second.get("name", model.id); section.second.get("variants", ""); std::vector variants; if (Slic3r::unescape_strings_cstyle(section.second.get("variants", ""), variants)) { @@ -693,9 +697,10 @@ static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorP } else { // Log error? } - if (! model.name.empty() && ! model.variants.empty()) - vendor_profile.models.insert(model); + if (! model.id.empty() && ! model.variants.empty()) + vendor_profile.models.push_back(std::move(model)); } + } } // Load a config bundle file, into presets and store the loaded presets into separate files @@ -719,12 +724,11 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla pt::ptree tree; boost::nowide::ifstream ifs(path); pt::read_ini(ifs, tree); - // Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. - flatten_configbundle_hierarchy(tree); const VendorProfile *vendor_profile = nullptr; - if (flags & LOAD_CFGBNDLE_SYSTEM) { - VendorProfile vp; + if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { + boost::filesystem::path fspath(path); + VendorProfile vp(fspath.stem().native()); load_vendor_profile(tree, vp); if (vp.name.empty()) throw std::runtime_error(std::string("Vendor Config Bundle is not valid: Missing vendor name key.")); @@ -732,6 +736,13 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla return 0; vendor_profile = &(*this->vendors.insert(vp).first); } + + if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) { + return 0; + } + + // 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. + flatten_configbundle_hierarchy(tree); // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. std::vector loaded_prints; @@ -803,7 +814,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla section.first << "\" defines no printer variant, it will be ignored."; continue; } - auto it_model = vendor_profile->models.find(VendorProfile::PrinterModel(printer_model)); + auto it_model = std::find_if(vendor_profile->models.cbegin(), vendor_profile->models.cend(), + [&](const VendorProfile::PrinterModel &m) { return m.id == printer_model; } + ); if (it_model == vendor_profile->models.end()) { BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << section.first << "\" defines invalid printer model \"" << printer_model << "\", it will be ignored."; diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index f481ba374f..4949e0e037 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -81,6 +81,7 @@ public: LOAD_CFGBNDLE_RESET_USER_PROFILE = 2, // Load a system config bundle. LOAD_CFGBNDLE_SYSTEM = 4, + LOAD_CFGBUNDLE_VENDOR_ONLY = 8, }; // Load the config bundle, store it to the user profile directory by default. size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); diff --git a/xs/src/slic3r/Utils/PresetUpdate.cpp b/xs/src/slic3r/Utils/PresetUpdate.cpp new file mode 100644 index 0000000000..0e4c62af9a --- /dev/null +++ b/xs/src/slic3r/Utils/PresetUpdate.cpp @@ -0,0 +1,104 @@ +#include "PresetUpdate.hpp" + +#include // XXX +#include +#include +#include + +#include "libslic3r/Utils.hpp" +#include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/Utils/Http.hpp" + +namespace fs = boost::filesystem; + + +namespace Slic3r { + + +struct PresetUpdater::priv +{ + PresetBundle *bundle; + fs::path cache_path; + std::thread thread; + + priv(PresetBundle *bundle); + + void download(); +}; + + +PresetUpdater::priv::priv(PresetBundle *bundle) : + bundle(bundle), + cache_path(fs::path(Slic3r::data_dir()) / "cache") +{} + +void PresetUpdater::priv::download() +{ + std::cerr << "PresetUpdater::priv::download()" << std::endl; + + std::cerr << "Bundle vendors: " << bundle->vendors.size() << std::endl; + for (const auto &vendor : bundle->vendors) { + std::cerr << "vendor: " << vendor.name << std::endl; + std::cerr << " URL: " << vendor.config_update_url << std::endl; + + // TODO: Proper caching + + auto target_path = cache_path / vendor.id; + target_path += ".ini"; + std::cerr << "target_path: " << target_path << std::endl; + + Http::get(vendor.config_update_url) + .on_complete([&](std::string body, unsigned http_status) { + std::cerr << "Got ini: " << http_status << ", body: " << body.size() << std::endl; + fs::fstream file(target_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(body.c_str(), body.size()); + }) + .on_error([](std::string body, std::string error, unsigned http_status) { + // TODO: what about errors? + std::cerr << "Error: " << http_status << ", " << error << std::endl; + }) + .perform_sync(); + } +} + +PresetUpdater::PresetUpdater(PresetBundle *preset_bundle) : p(new priv(preset_bundle)) {} + + +// Public + +PresetUpdater::~PresetUpdater() +{ + if (p && p->thread.joinable()) { + p->thread.detach(); + } +} + +void PresetUpdater::download(AppConfig *app_config, PresetBundle *preset_bundle) +{ + std::cerr << "PresetUpdater::download()" << std::endl; + + auto self = std::make_shared(preset_bundle); + auto thread = std::thread([self](){ + self->p->download(); + }); + self->p->thread = std::move(thread); +} + + +// TODO: remove +namespace Utils { + +void preset_update_check() +{ + std::cerr << "preset_update_check()" << std::endl; + + // TODO: + // 1. Get a version tag or the whole bundle from the web + // 2. Store into temporary location (?) + // 3. ??? + // 4. Profit! +} + +} + +} diff --git a/xs/src/slic3r/Utils/PresetUpdate.hpp b/xs/src/slic3r/Utils/PresetUpdate.hpp new file mode 100644 index 0000000000..4311340973 --- /dev/null +++ b/xs/src/slic3r/Utils/PresetUpdate.hpp @@ -0,0 +1,38 @@ +#ifndef slic3r_PresetUpdate_hpp_ +#define slic3r_PresetUpdate_hpp_ + +#include + +namespace Slic3r { + + +class AppConfig; +class PresetBundle; + +class PresetUpdater +{ +public: + PresetUpdater(PresetBundle *preset_bundle); + PresetUpdater(PresetUpdater &&) = delete; + PresetUpdater(const PresetUpdater &) = delete; + PresetUpdater &operator=(PresetUpdater &&) = delete; + PresetUpdater &operator=(const PresetUpdater &) = delete; + ~PresetUpdater(); + + static void download(AppConfig *app_config, PresetBundle *preset_bundle); +private: + struct priv; + std::unique_ptr p; +}; + + +// TODO: Remove +namespace Utils { + +void preset_update_check(); + +} + +} + +#endif diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 1376ff164b..5115eda646 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -54,6 +54,9 @@ int combochecklist_get_flags(SV *ui) void set_app_config(AppConfig *app_config) %code%{ Slic3r::GUI::set_app_config(app_config); %}; +void open_config_wizard() + %code%{ Slic3r::GUI::open_config_wizard(); %}; + void open_preferences_dialog(int preferences_event) %code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %}; diff --git a/xs/xsp/Utils_PresetUpdate.xsp b/xs/xsp/Utils_PresetUpdate.xsp new file mode 100644 index 0000000000..3596b7c86b --- /dev/null +++ b/xs/xsp/Utils_PresetUpdate.xsp @@ -0,0 +1,18 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "slic3r/Utils/PresetUpdate.hpp" +%} + +%name{Slic3r::PresetUpdater} class PresetUpdater { + static void download(PresetBundle *preset_bundle); +}; + + +# TODO: remove: + +%package{Slic3r::Utils}; + +void preset_update_check() + %code%{ Slic3r::Utils::preset_update_check(); %}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 87a8d8d866..d817af0529 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -235,6 +235,9 @@ PresetHints* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T TabIface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T +# TODO: remove: +# ConfigWizard* O_OBJECT_SLIC3R +# Ref O_OBJECT_SLIC3R_T OctoPrint* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T From 57e47a3296c94e9ed879e5bf281aef69f1942409 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 28 Mar 2018 11:36:36 +0200 Subject: [PATCH 002/145] AppConfig: Support for vendor / model / variant enable state --- lib/Slic3r/GUI.pm | 24 ++- xs/CMakeLists.txt | 6 +- xs/src/perlglue.cpp | 1 + xs/src/slic3r/GUI/AppConfig.cpp | 75 +++++++++- xs/src/slic3r/GUI/AppConfig.hpp | 16 ++ xs/src/slic3r/Utils/PresetUpdate.cpp | 104 ------------- xs/src/slic3r/Utils/PresetUpdater.cpp | 137 ++++++++++++++++++ .../{PresetUpdate.hpp => PresetUpdater.hpp} | 12 +- xs/xsp/GUI_AppConfig.xsp | 1 + xs/xsp/Utils_PresetUpdate.xsp | 18 --- xs/xsp/Utils_PresetUpdater.xsp | 11 ++ xs/xsp/my.map | 4 + 12 files changed, 267 insertions(+), 142 deletions(-) delete mode 100644 xs/src/slic3r/Utils/PresetUpdate.cpp create mode 100644 xs/src/slic3r/Utils/PresetUpdater.cpp rename xs/src/slic3r/Utils/{PresetUpdate.hpp => PresetUpdater.hpp} (71%) delete mode 100644 xs/xsp/Utils_PresetUpdate.xsp create mode 100644 xs/xsp/Utils_PresetUpdater.xsp diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 0c5e87429d..495dd9ecc7 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -70,6 +70,8 @@ our $grey = Wx::Colour->new(200,200,200); our $LANGUAGE_CHANGE_EVENT = Wx::NewEventType; # 2) To inform about a change of Preferences. our $PREFERENCES_EVENT = Wx::NewEventType; +# To inform AppConfig about Slic3r version available online +our $VERSION_ONLINE_EVENT = Wx::NewEventType; sub OnInit { my ($self) = @_; @@ -101,7 +103,9 @@ sub OnInit { $self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->save; - my $slic3r_update_avail = $self->{app_config}->get("version_check") && $self->{app_config}->get("version_online") != $Slic3r::VERSION; + # my $version_check = $self->{app_config}->get('version_check'); + $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT, $self->{app_config}); + my $slic3r_update = $self->{app_config}->slic3r_update_avail; Slic3r::GUI::set_app_config($self->{app_config}); Slic3r::GUI::load_language(); @@ -140,15 +144,17 @@ sub OnInit { # On OSX the UI was not initialized correctly if the wizard was called # before the UI was up and running. $self->CallAfter(sub { - if ($slic3r_update_avail) { + # XXX: recreate_GUI ??? + if ($slic3r_update) { # TODO - } elsif ($run_wizard) { + } + # XXX: ? + if ($run_wizard) { # Run the config wizard, don't offer the "reset user profile" checkbox. $self->{mainframe}->config_wizard(1); } - # XXX: recreate_GUI ??? - Slic3r::PresetUpdater::download($self->{app_config}, $self->{preset_bundle}); + $self->{preset_updater}->download($self->{preset_bundle}); }); # The following event is emited by the C++ menu implementation of application language change. @@ -161,6 +167,14 @@ sub OnInit { $self->update_ui_from_settings; }); + # The following event is emited by PresetUpdater (C++) + EVT_COMMAND($self, -1, $VERSION_ONLINE_EVENT, sub { + my ($self, $event) = @_; + my $version = $event->GetString; + $self->{app_config}->set('version_online', $version); + $self->{app_config}->save; + }); + return 1; } diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index e73c8429a5..3ce499d2b6 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -213,8 +213,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/Utils/OctoPrint.hpp ${LIBDIR}/slic3r/Utils/Bonjour.cpp ${LIBDIR}/slic3r/Utils/Bonjour.hpp - ${LIBDIR}/slic3r/Utils/PresetUpdate.cpp - ${LIBDIR}/slic3r/Utils/PresetUpdate.hpp + ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp + ${LIBDIR}/slic3r/Utils/PresetUpdater.hpp ) add_library(admesh STATIC @@ -360,7 +360,7 @@ set(XS_XSP_FILES ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp ${XSP_DIR}/Utils_OctoPrint.xsp - ${XSP_DIR}/Utils_PresetUpdate.xsp + ${XSP_DIR}/Utils_PresetUpdater.xsp ${XSP_DIR}/XS.xsp ) foreach (file ${XS_XSP_FILES}) diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index d7c9a590a0..9706ced2cd 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -64,6 +64,7 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); REGISTER_CLASS(PresetHints, "GUI::PresetHints"); REGISTER_CLASS(TabIface, "GUI::Tab"); +REGISTER_CLASS(PresetUpdater, "PresetUpdater"); REGISTER_CLASS(OctoPrint, "OctoPrint"); SV* ConfigBase__as_hash(ConfigBase* THIS) diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 4bdf88dda6..e951beb440 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -1,5 +1,3 @@ -#include - #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Utils.hpp" #include "AppConfig.hpp" @@ -9,15 +7,20 @@ #include #include #include +#include #include #include #include #include #include +#include namespace Slic3r { +static const std::string VENDOR_PREFIX = "vendor:"; +static const std::string MODEL_PREFIX = "model:"; + void AppConfig::reset() { m_storage.clear(); @@ -45,6 +48,8 @@ void AppConfig::set_defaults() // Version check is enabled by default in the config, but it is not implemented yet. // XXX if (get("version_check").empty()) set("version_check", "1"); + if (get("preset_update").empty()) + set("preset_update", "1"); // Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers. // https://github.com/prusa3d/Slic3r/issues/233 if (get("use_legacy_opengl").empty()) @@ -67,6 +72,19 @@ void AppConfig::load() if (! data.empty()) // If there is a non-empty data, then it must be a top-level (without a section) config entry. m_storage[""][section.first] = data; + } else if (boost::starts_with(section.first, VENDOR_PREFIX)) { + // This is a vendor section listing enabled model / variants + const auto vendor_name = section.first.substr(VENDOR_PREFIX.size()); + auto &vendor = m_vendors[vendor_name]; + for (const auto &kvp : section.second) { + if (! boost::starts_with(kvp.first, MODEL_PREFIX)) { continue; } + const auto model_name = kvp.first.substr(MODEL_PREFIX.size()); + std::vector variants; + if (! unescape_strings_cstyle(kvp.second.data(), variants)) { continue; } + for (const auto &variant : variants) { + vendor[model_name].insert(variant); + } + } } else { // This must be a section name. Read the entries of a section. std::map &storage = m_storage[section.first]; @@ -96,10 +114,53 @@ void AppConfig::save() for (const std::pair &kvp : category.second) c << kvp.first << " = " << kvp.second << std::endl; } + // Write vendor sections + for (const auto &vendor : m_vendors) { + size_t size_sum = 0; + for (const auto &model : vendor.second) { size_sum += model.second.size(); } + if (size_sum == 0) { continue; } + + c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl; + + for (const auto &model : vendor.second) { + if (model.second.size() == 0) { continue; } + const std::vector variants(model.second.begin(), model.second.end()); + const auto escaped = escape_strings_cstyle(variants); + c << MODEL_PREFIX << model.first << " = " << escaped << std::endl; + } + } c.close(); m_dirty = false; } +bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const +{ + std::cerr << "AppConfig::get_variant(" << vendor << ", " << model << ", " << variant << ") "; + + const auto it_v = m_vendors.find(vendor); + if (it_v == m_vendors.end()) { return false; } + const auto it_m = it_v->second.find(model); + return it_m == it_v->second.end() ? false : it_m->second.find(variant) != it_m->second.end(); +} + +void AppConfig::set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable) +{ + if (enable) { + if (get_variant(vendor, model, variant)) { return; } + m_vendors[vendor][model].insert(variant); + } else { + auto it_v = m_vendors.find(vendor); + if (it_v == m_vendors.end()) { return; } + auto it_m = it_v->second.find(model); + if (it_m == it_v->second.end()) { return; } + auto it_var = it_m->second.find(variant); + if (it_var == it_m->second.end()) { return; } + it_m->second.erase(it_var); + } + // If we got here, there was an update + m_dirty = true; +} + std::string AppConfig::get_last_dir() const { const auto it = m_storage.find("recent"); @@ -156,6 +217,16 @@ void AppConfig::reset_selections() } } +bool AppConfig::version_check_enabled() const +{ + return get("version_check") == "1"; +} + +bool AppConfig::slic3r_update_avail() const +{ + return version_check_enabled() && get("version_online") != SLIC3R_VERSION; +} + std::string AppConfig::config_path() { return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index 9b1d5a7128..4ccd51304f 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -1,9 +1,12 @@ #ifndef slic3r_AppConfig_hpp_ #define slic3r_AppConfig_hpp_ +#include #include #include +#include "libslic3r/Config.hpp" + namespace Slic3r { class AppConfig @@ -65,6 +68,13 @@ public: void clear_section(const std::string §ion) { m_storage[section].clear(); } + // TODO: remove / upgrade + // ConfigOptionStrings get_strings(const std::string §ion, const std::string &key) const; + // void set_strings(const std::string §ion, const std::string &key, const ConfigOptionStrings &value); + // TODO: + bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const; + void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable); + // return recent/skein_directory or recent/config_directory or empty string. std::string get_last_dir() const; void update_config_dir(const std::string &dir); @@ -78,6 +88,10 @@ public: // the first non-default preset when called. void reset_selections(); + // Whether the Slic3r version available online differs from this one + bool version_check_enabled() const; + bool slic3r_update_avail() const; + // Get the default config path from Slic3r::data_dir(). static std::string config_path(); @@ -87,6 +101,8 @@ public: private: // Map of section, name -> value std::map> m_storage; + // Map of enabled vendors / models / variants + std::map>> m_vendors; // Has any value been modified since the config.ini has been last saved or loaded? bool m_dirty; }; diff --git a/xs/src/slic3r/Utils/PresetUpdate.cpp b/xs/src/slic3r/Utils/PresetUpdate.cpp deleted file mode 100644 index 0e4c62af9a..0000000000 --- a/xs/src/slic3r/Utils/PresetUpdate.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "PresetUpdate.hpp" - -#include // XXX -#include -#include -#include - -#include "libslic3r/Utils.hpp" -#include "slic3r/GUI/PresetBundle.hpp" -#include "slic3r/Utils/Http.hpp" - -namespace fs = boost::filesystem; - - -namespace Slic3r { - - -struct PresetUpdater::priv -{ - PresetBundle *bundle; - fs::path cache_path; - std::thread thread; - - priv(PresetBundle *bundle); - - void download(); -}; - - -PresetUpdater::priv::priv(PresetBundle *bundle) : - bundle(bundle), - cache_path(fs::path(Slic3r::data_dir()) / "cache") -{} - -void PresetUpdater::priv::download() -{ - std::cerr << "PresetUpdater::priv::download()" << std::endl; - - std::cerr << "Bundle vendors: " << bundle->vendors.size() << std::endl; - for (const auto &vendor : bundle->vendors) { - std::cerr << "vendor: " << vendor.name << std::endl; - std::cerr << " URL: " << vendor.config_update_url << std::endl; - - // TODO: Proper caching - - auto target_path = cache_path / vendor.id; - target_path += ".ini"; - std::cerr << "target_path: " << target_path << std::endl; - - Http::get(vendor.config_update_url) - .on_complete([&](std::string body, unsigned http_status) { - std::cerr << "Got ini: " << http_status << ", body: " << body.size() << std::endl; - fs::fstream file(target_path, std::ios::out | std::ios::binary | std::ios::trunc); - file.write(body.c_str(), body.size()); - }) - .on_error([](std::string body, std::string error, unsigned http_status) { - // TODO: what about errors? - std::cerr << "Error: " << http_status << ", " << error << std::endl; - }) - .perform_sync(); - } -} - -PresetUpdater::PresetUpdater(PresetBundle *preset_bundle) : p(new priv(preset_bundle)) {} - - -// Public - -PresetUpdater::~PresetUpdater() -{ - if (p && p->thread.joinable()) { - p->thread.detach(); - } -} - -void PresetUpdater::download(AppConfig *app_config, PresetBundle *preset_bundle) -{ - std::cerr << "PresetUpdater::download()" << std::endl; - - auto self = std::make_shared(preset_bundle); - auto thread = std::thread([self](){ - self->p->download(); - }); - self->p->thread = std::move(thread); -} - - -// TODO: remove -namespace Utils { - -void preset_update_check() -{ - std::cerr << "preset_update_check()" << std::endl; - - // TODO: - // 1. Get a version tag or the whole bundle from the web - // 2. Store into temporary location (?) - // 3. ??? - // 4. Profit! -} - -} - -} diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp new file mode 100644 index 0000000000..11064d2f0b --- /dev/null +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -0,0 +1,137 @@ +#include "PresetUpdater.hpp" + +#include // XXX +#include +#include +#include +#include + +#include +#include + +#include "libslic3r/Utils.hpp" +#include "slic3r/GUI/GUI.hpp" +// #include "slic3r/GUI/AppConfig.hpp" +#include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/Utils/Http.hpp" + +namespace fs = boost::filesystem; + + +namespace Slic3r { + + +// TODO: proper URL +static const std::string SLIC3R_VERSION_URL = "https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/e9187c3e15ceaf1a90f29b7c43cf3ccc746140f0/slic3rPE.version"; +enum { + SLIC3R_VERSION_BODY_MAX = 256, +}; + +struct PresetUpdater::priv +{ + int version_online_event; + bool version_check; + bool preset_update; + + fs::path cache_path; + bool cancel; + std::thread thread; + + priv(int event); + + void download(const std::set vendors) const; +}; + +PresetUpdater::priv::priv(int event) : + version_online_event(event), + version_check(false), + preset_update(false), + cache_path(fs::path(Slic3r::data_dir()) / "cache"), + cancel(false) +{} + +void PresetUpdater::priv::download(const std::set vendors) const +{ + std::cerr << "PresetUpdater::priv::download()" << std::endl; + + if (!version_check) { return; } + + // Download current Slic3r version + Http::get(SLIC3R_VERSION_URL) + .size_limit(SLIC3R_VERSION_BODY_MAX) + .on_progress([this](Http::Progress, bool &cancel) { + cancel = this->cancel; + }) + .on_complete([&](std::string body, unsigned http_status) { + boost::trim(body); + std::cerr << "Got version: " << http_status << ", body: \"" << body << '"' << std::endl; + wxCommandEvent* evt = new wxCommandEvent(version_online_event); + evt->SetString(body); + GUI::get_app()->QueueEvent(evt); + }) + .perform_sync(); + + if (!preset_update) { return; } + + // Donwload vendor preset bundles + std::cerr << "Bundle vendors: " << vendors.size() << std::endl; + for (const auto &vendor : vendors) { + std::cerr << "vendor: " << vendor.name << std::endl; + std::cerr << " URL: " << vendor.config_update_url << std::endl; + + if (cancel) { return; } + + // TODO: Proper caching + + auto target_path = cache_path / vendor.id; + target_path += ".ini"; + std::cerr << "target_path: " << target_path << std::endl; + + Http::get(vendor.config_update_url) + .on_progress([this](Http::Progress, bool &cancel) { + cancel = this->cancel; + }) + .on_complete([&](std::string body, unsigned http_status) { + std::cerr << "Got ini: " << http_status << ", body: " << body.size() << std::endl; + fs::fstream file(target_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(body.c_str(), body.size()); + }) + .perform_sync(); + } +} + +PresetUpdater::PresetUpdater(int version_online_event, AppConfig *app_config) : + p(new priv(version_online_event)) +{ + p->preset_update = app_config->get("preset_update") == "1"; + // preset_update implies version_check: + p->version_check = p->preset_update || app_config->get("version_check") == "1"; +} + + +// Public + +PresetUpdater::~PresetUpdater() +{ + if (p && p->thread.joinable()) { + p->cancel = true; + p->thread.join(); + } +} + +void PresetUpdater::download(PresetBundle *preset_bundle) +{ + std::cerr << "PresetUpdater::download()" << std::endl; + + // Copy the whole vendors data for use in the background thread + // Unfortunatelly as of C++11, it needs to be copied again + // into the closure (but perhaps the compiler can elide this). + std::set vendors = preset_bundle->vendors; + + p->thread = std::move(std::thread([this, vendors]() { + this->p->download(std::move(vendors)); + })); +} + + +} diff --git a/xs/src/slic3r/Utils/PresetUpdate.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp similarity index 71% rename from xs/src/slic3r/Utils/PresetUpdate.hpp rename to xs/src/slic3r/Utils/PresetUpdater.hpp index 4311340973..aafe9569b7 100644 --- a/xs/src/slic3r/Utils/PresetUpdate.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -12,27 +12,19 @@ class PresetBundle; class PresetUpdater { public: - PresetUpdater(PresetBundle *preset_bundle); + PresetUpdater(int version_online_event, AppConfig *app_config); PresetUpdater(PresetUpdater &&) = delete; PresetUpdater(const PresetUpdater &) = delete; PresetUpdater &operator=(PresetUpdater &&) = delete; PresetUpdater &operator=(const PresetUpdater &) = delete; ~PresetUpdater(); - static void download(AppConfig *app_config, PresetBundle *preset_bundle); + void download(PresetBundle *preset_bundle); private: struct priv; std::unique_ptr p; }; -// TODO: Remove -namespace Utils { - -void preset_update_check(); - } - -} - #endif diff --git a/xs/xsp/GUI_AppConfig.xsp b/xs/xsp/GUI_AppConfig.xsp index 08a88883db..de0e5a22bc 100644 --- a/xs/xsp/GUI_AppConfig.xsp +++ b/xs/xsp/GUI_AppConfig.xsp @@ -43,4 +43,5 @@ void update_last_output_dir(char *dir); void reset_selections(); + bool slic3r_update_avail() const; }; diff --git a/xs/xsp/Utils_PresetUpdate.xsp b/xs/xsp/Utils_PresetUpdate.xsp deleted file mode 100644 index 3596b7c86b..0000000000 --- a/xs/xsp/Utils_PresetUpdate.xsp +++ /dev/null @@ -1,18 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/Utils/PresetUpdate.hpp" -%} - -%name{Slic3r::PresetUpdater} class PresetUpdater { - static void download(PresetBundle *preset_bundle); -}; - - -# TODO: remove: - -%package{Slic3r::Utils}; - -void preset_update_check() - %code%{ Slic3r::Utils::preset_update_check(); %}; diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp new file mode 100644 index 0000000000..666379f02e --- /dev/null +++ b/xs/xsp/Utils_PresetUpdater.xsp @@ -0,0 +1,11 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "slic3r/Utils/PresetUpdater.hpp" +%} + +%name{Slic3r::PresetUpdater} class PresetUpdater { + PresetUpdater(int version_online_event, AppConfig *app_config); + void download(PresetBundle* preset_bundle); +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index d817af0529..9c0f60c1dc 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -239,6 +239,10 @@ Ref O_OBJECT_SLIC3R_T # ConfigWizard* O_OBJECT_SLIC3R # Ref O_OBJECT_SLIC3R_T +PresetUpdater* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + OctoPrint* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T From e53949f2c85e06adaf7be9a96873844a8862eab6 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 29 Mar 2018 17:54:43 +0200 Subject: [PATCH 003/145] Apply printer model / variant preferences when loading presets --- lib/Slic3r/GUI.pm | 2 +- lib/Slic3r/GUI/MainFrame.pm | 6 +- resources/profiles/PrusaResearch.ini | 2 + xs/src/slic3r/GUI/AppConfig.cpp | 8 +- xs/src/slic3r/GUI/AppConfig.hpp | 5 +- xs/src/slic3r/GUI/ConfigWizard.cpp | 154 ++++++++++++++++----- xs/src/slic3r/GUI/ConfigWizard.hpp | 5 +- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 30 +++- xs/src/slic3r/GUI/GUI.cpp | 9 +- xs/src/slic3r/GUI/GUI.hpp | 2 +- xs/src/slic3r/GUI/Preset.cpp | 11 ++ xs/src/slic3r/GUI/Preset.hpp | 18 ++- xs/src/slic3r/GUI/PresetBundle.cpp | 20 ++- xs/src/slic3r/GUI/PresetBundle.hpp | 2 + xs/src/slic3r/Utils/PresetUpdater.cpp | 2 +- xs/xsp/GUI.xsp | 4 +- xs/xsp/GUI_Preset.xsp | 2 +- 17 files changed, 218 insertions(+), 64 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 495dd9ecc7..ed2f6dfc7d 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -154,7 +154,7 @@ sub OnInit { $self->{mainframe}->config_wizard(1); } - $self->{preset_updater}->download($self->{preset_bundle}); + # $self->{preset_updater}->download($self->{preset_bundle}); }); # The following event is emited by the C++ menu implementation of application language change. diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 68fc963394..4307375c81 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -646,7 +646,11 @@ sub config_wizard { # TODO: Offer "reset user profile" - Slic3r::GUI::open_config_wizard(); + Slic3r::GUI::open_config_wizard(wxTheApp->{preset_bundle}); + # Load the currently selected preset into the GUI, update the preset selection box. + foreach my $tab (values %{$self->{options_tabs}}) { # XXX: only if not cancelled? + $tab->load_current_preset; + } return; # Enumerate the profiles bundled with the Slic3r installation under resources/profiles. diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 0a1d26eaa0..6dc29a96e9 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -999,6 +999,7 @@ default_print_profile = 0.15mm OPTIMAL MK3 [printer:Original Prusa i3 MK3 0.25 nozzle] inherits = *common* nozzle_diameter = 0.25 +printer_variant = 0.25 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 @@ -1009,6 +1010,7 @@ default_print_profile = 0.10mm DETAIL MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] inherits = *common* nozzle_diameter = 0.6 +printer_variant = 0.6 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index e951beb440..10a586e27c 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -135,7 +135,7 @@ void AppConfig::save() bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const { - std::cerr << "AppConfig::get_variant(" << vendor << ", " << model << ", " << variant << ") "; + // std::cerr << "AppConfig::get_variant(" << vendor << ", " << model << ", " << variant << ") " << std::endl; const auto it_v = m_vendors.find(vendor); if (it_v == m_vendors.end()) { return false; } @@ -161,6 +161,12 @@ void AppConfig::set_variant(const std::string &vendor, const std::string &model, m_dirty = true; } +void AppConfig::set_vendors(const AppConfig &from) +{ + m_vendors = from.m_vendors; + m_dirty = true; +} + std::string AppConfig::get_last_dir() const { const auto it = m_storage.find("recent"); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index 4ccd51304f..e43ff51bf6 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -68,12 +68,9 @@ public: void clear_section(const std::string §ion) { m_storage[section].clear(); } - // TODO: remove / upgrade - // ConfigOptionStrings get_strings(const std::string §ion, const std::string &key) const; - // void set_strings(const std::string §ion, const std::string &key, const ConfigOptionStrings &value); - // TODO: bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const; void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable); + void set_vendors(const AppConfig &from); // return recent/skein_directory or recent/config_directory or empty string. std::string get_last_dir() const; diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 3cad789b65..556b91a462 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -74,13 +74,17 @@ void ConfigWizardPage::append_text(wxString text) auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); widget->Wrap(CONTENT_WIDTH); widget->SetMinSize(wxSize(CONTENT_WIDTH, -1)); - // content->Add(widget, 1, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10); content->Add(widget, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10); } -void ConfigWizardPage::append_widget(wxWindow *widget, int proportion) +void ConfigWizardPage::append_widget(wxWindow *widget, int proportion, int flag, int border) { - content->Add(widget, proportion, wxEXPAND | wxTOP | wxBOTTOM, 10); + content->Add(widget, proportion, flag, border); +} + +void ConfigWizardPage::append_sizer(wxSizer *sizer, int proportion) +{ + content->Add(sizer, proportion, wxEXPAND | wxTOP | wxBOTTOM, 10); } void ConfigWizardPage::append_spacer(int space) @@ -99,17 +103,19 @@ void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable) // Wizard pages -PageWelcome::PageWelcome(ConfigWizard *parent, const PresetBundle &bundle) : +// PageWelcome::PageWelcome(ConfigWizard *parent, const PresetBundle &bundle) : +PageWelcome::PageWelcome(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Welcome to the Slic3r Configuration assistant")), _(L("Welcome"))), others_buttons(new wxPanel(parent)), variants_checked(0) { append_text(_(L("Hello, welcome to Slic3r Prusa Edition! TODO: This text."))); + const PresetBundle &bundle = wizard_p()->bundle_vendors; const auto &vendors = bundle.vendors; const auto vendor_prusa = std::find(vendors.cbegin(), vendors.cend(), VendorProfile("PrusaResearch")); - // TODO: preload checkiness from app config + const AppConfig &appconfig_vendors = wizard_p()->appconfig_vendors; if (vendor_prusa != vendors.cend()) { const auto &models = vendor_prusa->models; @@ -138,12 +144,20 @@ PageWelcome::PageWelcome(ConfigWizard *parent, const PresetBundle &bundle) : sizer->AddSpacer(20); + std::string model_id = model->id; + for (const auto &variant : model->variants) { + std::string variant_name = variant.name; auto *cbox = new wxCheckBox(panel, wxID_ANY, wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle")))); + bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant_name); + variants_checked += enabled; + cbox->SetValue(enabled); sizer->Add(cbox, 0, wxBOTTOM, 3); - cbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { + cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { this->variants_checked += event.IsChecked() ? 1 : -1; this->on_variant_checked(); + AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; + appconfig_vendors.set_variant("PrusaResearch", model_id, variant_name, event.IsChecked()); }); } @@ -156,6 +170,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent, const PresetBundle &bundle) : { auto *sizer = new wxBoxSizer(wxHORIZONTAL); auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors"))); + other_vendors->Disable(); auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup"))); sizer->Add(other_vendors); @@ -181,17 +196,24 @@ void PageWelcome::on_variant_checked() } PageUpdate::PageUpdate(ConfigWizard *parent) : - ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))) + ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))), + version_check(true), + preset_update(true) { - append_text(_(L("TODO: text"))); - auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for Slic3r updates"))); - box_slic3r->SetValue(true); - append_widget(box_slic3r); + const AppConfig *app_config = GUI::get_app_config(); + append_text(_(L("TODO: text"))); + auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for Slic3r updates"))); + box_slic3r->SetValue(app_config->get("version_check") == "1"); + append_widget(box_slic3r); + append_text(_(L("TODO: text"))); auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically"))); - box_presets->SetValue(true); + box_presets->SetValue(app_config->get("preset_update") == "1"); append_widget(box_presets); + + 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(); }); } void PageUpdate::presets_update_enable(bool enable) @@ -201,7 +223,42 @@ void PageUpdate::presets_update_enable(bool enable) PageVendors::PageVendors(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors"))) -{} +{ + enum { + INDENT_SPACING = 30, + VERTICAL_SPACING = 10, + }; + + append_text(_(L("Other vendors! TODO: This text."))); + + const PresetBundle &bundle = wizard_p()->bundle_vendors; + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + for (const auto &vendor : bundle.vendors) { + if (vendor.id == "PrusaResearch") { continue; } + + auto *label_vendor = new wxStaticText(this, wxID_ANY, vendor.name); + label_vendor->SetFont(boldfont); + append_thing(label_vendor, 0, 0, 0); + + for (const auto &model : vendor.models) { + auto *label_model = new wxStaticText(this, wxID_ANY, model.name); + label_model->SetFont(boldfont); + append_thing(label_model, 0, wxLEFT, INDENT_SPACING); + + for (const auto &variant : model.variants) { + auto *cbox = new wxCheckBox(this, wxID_ANY, variant.name); + append_thing(cbox, 0, wxEXPAND | wxLEFT, 2 * INDENT_SPACING); + cbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { + // TODO + }); + } + } + + append_spacer(VERTICAL_SPACING); + } +} PageFirmware::PageFirmware(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))) @@ -289,6 +346,31 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) // priv +void ConfigWizard::priv::load_vendors() +{ + const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor"; + // const auto profiles_dir = fs::path(resources_dir()) / "profiles"; + for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) { + if (it->path().extension() == ".ini") { + bundle_vendors.load_configbundle(it->path().native(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + } + } + + // XXX + // for (const auto &vendor : bundle_vendors.vendors) { + // std::cerr << "vendor: " << vendor.name << std::endl; + // std::cerr << " URL: " << vendor.config_update_url << std::endl; + // for (const auto &model : vendor.models) { + // std::cerr << "\tmodel: " << model.id << " (" << model.name << ")" << std::endl; + // for (const auto &variant : model.variants) { + // std::cerr << "\t\tvariant: " << variant.name << std::endl; + // } + // } + // } + + appconfig_vendors.set_vendors(*GUI::get_app_config()); +} + void ConfigWizard::priv::index_refresh() { index->load_items(page_welcome); @@ -344,12 +426,31 @@ void ConfigWizard::priv::on_custom_setup() set_page(page_firmware); } +void ConfigWizard::priv::on_finish() +{ + const bool is_custom_setup = page_welcome->page_next() == page_firmware; + + if (! is_custom_setup) { + AppConfig *app_config = GUI::get_app_config(); + app_config->set_vendors(appconfig_vendors); + + app_config->set("version_check", page_update->version_check ? "1" : "0"); + app_config->set("preset_update", page_update->preset_update ? "1" : "0"); + } else { + // TODO + } + + q->EndModal(wxID_OK); +} + // Public -ConfigWizard::ConfigWizard(wxWindow *parent, const PresetBundle &bundle) : +ConfigWizard::ConfigWizard(wxWindow *parent) : wxDialog(parent, wxID_ANY, _(L("Configuration Assistant")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), p(new priv(this)) { + p->load_vendors(); + p->index = new ConfigWizardIndex(this); auto *vsizer = new wxBoxSizer(wxVERTICAL); @@ -372,7 +473,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent, const PresetBundle &bundle) : p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); - p->add_page(p->page_welcome = new PageWelcome(this, bundle)); + p->add_page(p->page_welcome = new PageWelcome(this)); p->add_page(p->page_update = new PageUpdate(this)); p->add_page(p->page_vendors = new PageVendors(this)); p->add_page(p->page_firmware = new PageFirmware(this)); @@ -397,35 +498,24 @@ ConfigWizard::ConfigWizard(wxWindow *parent, const PresetBundle &bundle) : p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); }); + p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->on_finish(); }); } ConfigWizard::~ConfigWizard() {} -void ConfigWizard::run(wxWindow *parent) +void ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) { - PresetBundle bundle; - const auto profiles_dir = fs::path(resources_dir()) / "profiles"; for (fs::directory_iterator it(profiles_dir); it != fs::directory_iterator(); ++it) { if (it->path().extension() == ".ini") { - bundle.load_configbundle(it->path().native(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + PresetBundle::install_vendor_configbundle(it->path()); } } - // XXX - for (const auto &vendor : bundle.vendors) { - std::cerr << "vendor: " << vendor.name << std::endl; - std::cerr << " URL: " << vendor.config_update_url << std::endl; - for (const auto &model : vendor.models) { - std::cerr << "\tmodel: " << model.id << " (" << model.name << ")" << std::endl; - for (const auto &variant : model.variants) { - std::cerr << "\t\tvariant: " << variant.name << std::endl; - } - } + ConfigWizard wizard(parent); + if (wizard.ShowModal() == wxID_OK) { + preset_bundle->load_presets(*GUI::get_app_config()); } - - ConfigWizard wizard(parent, bundle); - wizard.ShowModal(); } diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp index 1b14e29be7..a06388396c 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -15,14 +15,15 @@ namespace GUI { class ConfigWizard: public wxDialog { public: - ConfigWizard(wxWindow *parent, const PresetBundle &bundle); + // ConfigWizard(wxWindow *parent, const PresetBundle &bundle); + ConfigWizard(wxWindow *parent); ConfigWizard(ConfigWizard &&) = delete; ConfigWizard(const ConfigWizard &) = delete; ConfigWizard &operator=(ConfigWizard &&) = delete; ConfigWizard &operator=(const ConfigWizard &) = delete; ~ConfigWizard(); - static void run(wxWindow *parent); + static void run(wxWindow *parent, PresetBundle *preset_bundle); private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index ba028c0e87..ab291d40f8 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -9,6 +9,9 @@ #include #include +#include "AppConfig.hpp" +#include "PresetBundle.hpp" + namespace Slic3r { namespace GUI { @@ -34,14 +37,23 @@ struct ConfigWizardPage: wxPanel virtual ~ConfigWizardPage(); - ConfigWizardPage *page_prev() const { return p_prev; } - ConfigWizardPage *page_next() const { return p_next; } + ConfigWizardPage* page_prev() const { return p_prev; } + ConfigWizardPage* page_next() const { return p_next; } ConfigWizardPage* chain(ConfigWizardPage *page); void append_text(wxString text); - void append_widget(wxWindow *widget, int proportion = 0); + void append_widget(wxWindow *widget, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10); + + template + void append_thing(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10) + { + content->Add(thing, proportion, flag, border); + } + + // TODO: remove: + void append_sizer(wxSizer *sizer, int proportion = 0); void append_spacer(int space); - + ConfigWizard::priv *wizard_p() const { return parent->p.get(); } virtual bool Show(bool show = true); @@ -60,7 +72,7 @@ struct PageWelcome: ConfigWizardPage wxPanel *others_buttons; unsigned variants_checked; - PageWelcome(ConfigWizard *parent, const PresetBundle &bundle); + PageWelcome(ConfigWizard *parent); virtual wxPanel* extra_buttons() { return others_buttons; } virtual void on_page_set(); @@ -70,6 +82,9 @@ struct PageWelcome: ConfigWizardPage struct PageUpdate: ConfigWizardPage { + bool version_check; + bool preset_update; + PageUpdate(ConfigWizard *parent); void presets_update_enable(bool enable); @@ -124,6 +139,9 @@ private: struct ConfigWizard::priv { ConfigWizard *q; + AppConfig appconfig_vendors; + PresetBundle bundle_vendors; + wxBoxSizer *topsizer = nullptr; wxBoxSizer *btnsizer = nullptr; ConfigWizardPage *page_current = nullptr; @@ -143,6 +161,7 @@ struct ConfigWizard::priv priv(ConfigWizard *q) : q(q) {} + void load_vendors(); void add_page(ConfigWizardPage *page); void index_refresh(); void set_page(ConfigWizardPage *page); @@ -152,6 +171,7 @@ struct ConfigWizard::priv void on_other_vendors(); void on_custom_setup(); + void on_finish(); }; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 53288067cf..80e2322875 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -353,18 +353,13 @@ void add_debug_menu(wxMenuBar *menu, int event_language_change) //#endif } -void open_config_wizard() +void open_config_wizard(PresetBundle *preset_bundle) { if (g_wxMainFrame == nullptr) { throw std::runtime_error("Main frame not set"); } - // auto *wizard = new ConfigWizard(static_cast(g_wxMainFrame)); // FIXME: lifetime - - // wizard->run(); - ConfigWizard::run(g_wxMainFrame); - - // show_info(g_wxMainFrame, "After wizard", "After wizard"); + ConfigWizard::run(g_wxMainFrame, preset_bundle); } void open_preferences_dialog(int event_preferences) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 1f93e18e99..98a1240919 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -86,7 +86,7 @@ wxColour* get_sys_label_clr(); void add_debug_menu(wxMenuBar *menu, int event_language_change); // Opens the first-time configuration wizard -void open_config_wizard(); +void open_config_wizard(PresetBundle *preset_bundle); // Create "Preferences" dialog after selecting menu "Preferences" in Perl part void open_preferences_dialog(int event_preferences); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index c437f8b414..40afca144b 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -2,6 +2,7 @@ #include #include "Preset.hpp" +#include "AppConfig.hpp" #include #include @@ -175,6 +176,16 @@ bool Preset::update_compatible_with_printer(const Preset &active_printer, const return this->is_compatible = is_compatible_with_printer(active_printer, extra_config); } +void Preset::set_visible_from_appconfig(const AppConfig &app_config) +{ + if (vendor == nullptr) { return; } + const std::string &model = config.opt_string("printer_model"); + const std::string &variant = config.opt_string("printer_variant"); + if (model.empty() || variant.empty()) { return; } + is_visible = app_config.get_variant(vendor->id, model, variant); + std::cerr << vendor->id << " / " << model << " / " << variant << ": visible: " << is_visible << std::endl; +} + const std::vector& Preset::print_options() { static std::vector s_opts { diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 78e15badae..075eed9afd 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -13,6 +13,8 @@ class wxItemContainer; namespace Slic3r { +class AppConfig; + enum ConfigFileType { CONFIG_FILE_TYPE_UNKNOWN, @@ -35,14 +37,14 @@ public: PrinterVariant() {} PrinterVariant(const std::string &name) : name(name) {} std::string name; - bool enabled = true; + // bool enabled = true; // TODO: remove these? }; struct PrinterModel { PrinterModel() {} std::string id; std::string name; - bool enabled = true; + // bool enabled = true; std::vector variants; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) @@ -85,7 +87,7 @@ public: bool is_external = false; // System preset is read-only. bool is_system = false; - // Preset is visible, if it is compatible with the active Printer. + // Preset is visible, if it is compatible with the active Printer. TODO: fix // Also the "default" preset is only visible, if it is the only preset in the list. bool is_visible = true; // Has this preset been modified? @@ -131,6 +133,9 @@ public: // Mark this preset as compatible if it is compatible with active_printer. bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config); + // Set is_visible according to application config + void set_visible_from_appconfig(const AppConfig &app_config); + // Resize the extruder specific fields, initialize them with the content of the 1st extruder. void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); } @@ -162,6 +167,13 @@ public: PresetCollection(Preset::Type type, const std::vector &keys); ~PresetCollection(); + typedef std::deque::iterator Iterator; + typedef std::deque::const_iterator ConstIterator; + Iterator begin() { return m_presets.begin() + 1; } + ConstIterator begin() const { return m_presets.begin() + 1; } + Iterator end() { return m_presets.end(); } + ConstIterator end() const { return m_presets.end(); } + void reset(bool delete_files); Preset::Type type() const { return m_type; } diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 241f0433ef..8645a4f2d3 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -4,6 +4,7 @@ #include "PresetBundle.hpp" #include "BitmapCache.hpp" +#include // XXX #include #include #include @@ -105,7 +106,7 @@ void PresetBundle::setup_directories() std::initializer_list paths = { data_dir, data_dir / "vendor", - data_dir / "cache", + data_dir / "cache", // TODO: rename as vendor-cache? (Check usage elsewhere!) #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. data_dir / "presets", @@ -200,8 +201,12 @@ static inline std::string remove_ini_suffix(const std::string &name) // If the "vendor" section is missing, enable all models and variants of the particular vendor. void PresetBundle::load_installed_printers(const AppConfig &config) { - // TODO - // m_storage + std::cerr << "load_installed_printers()" << std::endl; + + for (auto &preset : printers) { + std::cerr << "preset: printer: " << preset.name << std::endl; + preset.set_visible_from_appconfig(config); + } } // Load selections (current print, current filaments, current printer) from config.ini @@ -221,6 +226,10 @@ void PresetBundle::load_selections(const AppConfig &config) break; this->set_filament_preset(i, remove_ini_suffix(config.get("presets", name))); } + + // Update visibility of presets based on application vendor / model / variant configuration. + this->load_installed_printers(config); + // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, // as the application may have been closed with an active "external" preset, which does not @@ -708,6 +717,11 @@ static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorP void PresetBundle::install_vendor_configbundle(const std::string &src_path0) { boost::filesystem::path src_path(src_path0); + install_vendor_configbundle(src_path); +} + +void PresetBundle::install_vendor_configbundle(const boost::filesystem::path &src_path) +{ boost::filesystem::copy_file(src_path, (boost::filesystem::path(data_dir()) / "vendor" / src_path.filename()).make_preferred(), boost::filesystem::copy_option::overwrite_if_exists); } diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index 4949e0e037..4189e6c46f 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -5,6 +5,7 @@ #include "Preset.hpp" #include +#include namespace Slic3r { @@ -88,6 +89,7 @@ public: // Install the Vendor specific config bundle into user's directory. void install_vendor_configbundle(const std::string &src_path); + static void install_vendor_configbundle(const boost::filesystem::path &src_path); // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 11064d2f0b..040d326b5c 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -11,9 +11,9 @@ #include "libslic3r/Utils.hpp" #include "slic3r/GUI/GUI.hpp" -// #include "slic3r/GUI/AppConfig.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/Utils/Http.hpp" +#include "slic3r/Utils/Semver.hpp" namespace fs = boost::filesystem; diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 5115eda646..ad7f69a2d7 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -54,8 +54,8 @@ int combochecklist_get_flags(SV *ui) void set_app_config(AppConfig *app_config) %code%{ Slic3r::GUI::set_app_config(app_config); %}; -void open_config_wizard() - %code%{ Slic3r::GUI::open_config_wizard(); %}; +void open_config_wizard(PresetBundle *preset_bundle) + %code%{ Slic3r::GUI::open_config_wizard(preset_bundle); %}; void open_preferences_dialog(int preferences_event) %code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %}; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 84efdde53f..1187a1cf56 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -147,7 +147,7 @@ PresetCollection::arrayref() void install_vendor_configbundle(const char *path) %code%{ try { - THIS->install_vendor_configbundle(path); + THIS->install_vendor_configbundle(std::string(path)); } catch (std::exception& e) { croak("Installing a vendor config bundle %s failed:\n%s\n", path, e.what()); } From 8422cf93c0e550e75a40522c56b9570c72d32169 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 5 Apr 2018 16:10:44 +0200 Subject: [PATCH 004/145] ConfigWizard: Finalize custom setup --- xs/src/libslic3r/Config.hpp | 1 + xs/src/libslic3r/PrintConfig.cpp | 2 +- xs/src/slic3r/GUI/ConfigWizard.cpp | 246 +++++++++++++++------ xs/src/slic3r/GUI/ConfigWizard_private.hpp | 40 +++- 4 files changed, 212 insertions(+), 77 deletions(-) diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 6eb307c5ce..06db9efef2 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -659,6 +659,7 @@ public: ConfigOptionPoints() : ConfigOptionVector() {} explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector(n, value) {} explicit ConfigOptionPoints(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} + explicit ConfigOptionPoints(const std::vector &values) : ConfigOptionVector(values) {} static ConfigOptionType static_type() { return coPoints; } ConfigOptionType type() const override { return static_type(); } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 657e5a4520..5795f044bc 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1620,7 +1620,7 @@ PrintConfigDef::PrintConfigDef() "temperature control commands in the output."); def->cli = "temperature=i@"; def->full_label = L("Temperature"); - def->max = 0; + def->min = 0; def->max = max_temp; def->default_value = new ConfigOptionInts { 200 }; diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 556b91a462..edf958bd8f 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -74,17 +74,7 @@ void ConfigWizardPage::append_text(wxString text) auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); widget->Wrap(CONTENT_WIDTH); widget->SetMinSize(wxSize(CONTENT_WIDTH, -1)); - content->Add(widget, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10); -} - -void ConfigWizardPage::append_widget(wxWindow *widget, int proportion, int flag, int border) -{ - content->Add(widget, proportion, flag, border); -} - -void ConfigWizardPage::append_sizer(wxSizer *sizer, int proportion) -{ - content->Add(sizer, proportion, wxEXPAND | wxTOP | wxBOTTOM, 10); + append(widget); } void ConfigWizardPage::append_spacer(int space) @@ -103,7 +93,6 @@ void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable) // Wizard pages -// PageWelcome::PageWelcome(ConfigWizard *parent, const PresetBundle &bundle) : PageWelcome::PageWelcome(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Welcome to the Slic3r Configuration assistant")), _(L("Welcome"))), others_buttons(new wxPanel(parent)), @@ -164,13 +153,13 @@ PageWelcome::PageWelcome(ConfigWizard *parent) : printer_grid->Add(panel); } - append_widget(printer_picker); + append(printer_picker); } { auto *sizer = new wxBoxSizer(wxHORIZONTAL); auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors"))); - other_vendors->Disable(); + // other_vendors->Disable(); // XXX auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup"))); sizer->Add(other_vendors); @@ -201,36 +190,24 @@ PageUpdate::PageUpdate(ConfigWizard *parent) : preset_update(true) { const AppConfig *app_config = GUI::get_app_config(); - + append_text(_(L("TODO: text"))); auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for Slic3r updates"))); box_slic3r->SetValue(app_config->get("version_check") == "1"); - append_widget(box_slic3r); + append(box_slic3r); append_text(_(L("TODO: text"))); auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically"))); box_presets->SetValue(app_config->get("preset_update") == "1"); - append_widget(box_presets); + append(box_presets); 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(); }); } -void PageUpdate::presets_update_enable(bool enable) -{ - // TODO -} - PageVendors::PageVendors(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors"))) { - enum { - INDENT_SPACING = 30, - VERTICAL_SPACING = 10, - }; - - append_text(_(L("Other vendors! TODO: This text."))); - const PresetBundle &bundle = wizard_p()->bundle_vendors; auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); boldfont.SetWeight(wxFONTWEIGHT_BOLD); @@ -240,16 +217,16 @@ PageVendors::PageVendors(ConfigWizard *parent) : auto *label_vendor = new wxStaticText(this, wxID_ANY, vendor.name); label_vendor->SetFont(boldfont); - append_thing(label_vendor, 0, 0, 0); + append(label_vendor, 0, 0, 0); for (const auto &model : vendor.models) { auto *label_model = new wxStaticText(this, wxID_ANY, model.name); label_model->SetFont(boldfont); - append_thing(label_model, 0, wxLEFT, INDENT_SPACING); + append(label_model, 0, wxLEFT, INDENT_SPACING); for (const auto &variant : model.variants) { auto *cbox = new wxCheckBox(this, wxID_ANY, variant.name); - append_thing(cbox, 0, wxEXPAND | wxLEFT, 2 * INDENT_SPACING); + append(cbox, 0, wxEXPAND | wxLEFT, 2 * INDENT_SPACING); cbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { // TODO }); @@ -260,21 +237,169 @@ PageVendors::PageVendors(ConfigWizard *parent) : } } + PageFirmware::PageFirmware(ConfigWizard *parent) : - ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))) -{} + ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))), + gcode_opt(print_config_def.options["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); + const auto &enum_values = gcode_opt.enum_values; + auto needle = enum_values.cend(); + if (gcode_opt.default_value != nullptr) { + 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) +{ + ConfigOptionEnum opt; + + auto sel = gcode_picker->GetSelection(); + if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) { + config.set_key_value("gcode_flavor", &opt); + } +} PageBedShape::PageBedShape(ConfigWizard *parent) : - ConfigWizardPage(parent, _(L("Bed Shape and Size")), _(L("Bed Shape"))) -{} + ConfigWizardPage(parent, _(L("Bed Shape and Size")), _(L("Bed Shape"))), + 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")); + append(shape_panel); +} + +void PageBedShape::apply_custom_config(DynamicPrintConfig &config) +{ + const auto points(shape_panel->GetValue()); + auto *opt = new ConfigOptionPoints(points); + config.set_key_value("bed_shape", opt); +} PageDiameters::PageDiameters(ConfigWizard *parent) : - ConfigWizardPage(parent, _(L("Filament and Nozzle Diameter")), _(L("Print Diameters"))) -{} + ConfigWizardPage(parent, _(L("Filament and Nozzle Diameters")), _(L("Print Diameters"))), + spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)), + spin_filam(new wxSpinCtrlDouble(this, wxID_ANY)) +{ + spin_nozzle->SetDigits(2); + spin_nozzle->SetIncrement(0.1); + const auto &def_nozzle = print_config_def.options["nozzle_diameter"]; + auto *default_nozzle = dynamic_cast(def_nozzle.default_value); + spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + + spin_filam->SetDigits(2); + spin_filam->SetIncrement(0.25); + const auto &def_filam = print_config_def.options["filament_diameter"]; + auto *default_filam = dynamic_cast(def_filam.default_value); + spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + + 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(spin_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(spin_filam); + sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_filam); +} + +void PageDiameters::apply_custom_config(DynamicPrintConfig &config) +{ + auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue()); + config.set_key_value("nozzle_diameter", opt_nozzle); + auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue()); + config.set_key_value("filament_diameter", opt_filam); +} PageTemperatures::PageTemperatures(ConfigWizard *parent) : - ConfigWizardPage(parent, _(L("Bed and Extruder Temperature")), _(L("Temperatures"))) -{} + ConfigWizardPage(parent, _(L("Extruder and Bed Temperatures")), _(L("Temperatures"))), + spin_extr(new wxSpinCtrl(this, wxID_ANY)), + spin_bed(new wxSpinCtrl(this, wxID_ANY)) +{ + spin_extr->SetIncrement(5); + const auto &def_extr = print_config_def.options["temperature"]; + spin_extr->SetRange(def_extr.min, def_extr.max); + auto *default_extr = dynamic_cast(def_extr.default_value); + spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); + + spin_bed->SetIncrement(5); + const auto &def_bed = print_config_def.options["bed_temperature"]; + spin_bed->SetRange(def_bed.min, def_bed.max); + auto *default_bed = dynamic_cast(def_bed.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 @@ -349,25 +474,12 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) void ConfigWizard::priv::load_vendors() { const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor"; - // const auto profiles_dir = fs::path(resources_dir()) / "profiles"; for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) { if (it->path().extension() == ".ini") { bundle_vendors.load_configbundle(it->path().native(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); } } - // XXX - // for (const auto &vendor : bundle_vendors.vendors) { - // std::cerr << "vendor: " << vendor.name << std::endl; - // std::cerr << " URL: " << vendor.config_update_url << std::endl; - // for (const auto &model : vendor.models) { - // std::cerr << "\tmodel: " << model.id << " (" << model.name << ")" << std::endl; - // for (const auto &variant : model.variants) { - // std::cerr << "\t\tvariant: " << variant.name << std::endl; - // } - // } - // } - appconfig_vendors.set_vendors(*GUI::get_app_config()); } @@ -426,21 +538,22 @@ void ConfigWizard::priv::on_custom_setup() set_page(page_firmware); } -void ConfigWizard::priv::on_finish() +void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle) { const bool is_custom_setup = page_welcome->page_next() == page_firmware; if (! is_custom_setup) { - AppConfig *app_config = GUI::get_app_config(); app_config->set_vendors(appconfig_vendors); - app_config->set("version_check", page_update->version_check ? "1" : "0"); app_config->set("preset_update", page_update->preset_update ? "1" : "0"); + app_config->reset_selections(); // XXX: only on "fresh start"? + preset_bundle->load_presets(*app_config); } else { - // TODO + for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) { + page->apply_custom_config(custom_config); + } + preset_bundle->load_config("My Settings", custom_config); } - - q->EndModal(wxID_OK); } // Public @@ -450,6 +563,10 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : p(new priv(this)) { p->load_vendors(); + std::unique_ptr custom_config_defaults(DynamicPrintConfig::new_from_defaults_keys({ + "gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + })); + p->custom_config.apply(*custom_config_defaults); p->index = new ConfigWizardIndex(this); @@ -461,10 +578,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : p->topsizer->Add(p->index, 0, wxEXPAND); p->topsizer->AddSpacer(INDEX_MARGIN); - // TODO: btn labels vs default w/ icons ... use arrows from resources? (no apply icon) - // Also: http://docs.wxwidgets.org/3.0/page_stockitems.html - p->btn_prev = new wxButton(this, wxID_BACKWARD, _(L("< &Back"))); - p->btn_next = new wxButton(this, wxID_FORWARD, _(L("&Next >"))); + p->btn_prev = new wxButton(this, wxID_BACKWARD); + p->btn_next = new wxButton(this, wxID_FORWARD); p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish"))); p->btn_cancel = new wxButton(this, wxID_CANCEL); p->btnsizer->AddStretchSpacer(); @@ -498,7 +613,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); }); - p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->on_finish(); }); + // p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->on_finish(); }); + p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->EndModal(wxID_OK); }); } ConfigWizard::~ConfigWizard() {} @@ -514,7 +630,7 @@ void ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) ConfigWizard wizard(parent); if (wizard.ShowModal() == wxID_OK) { - preset_bundle->load_presets(*GUI::get_app_config()); + wizard.p->apply_config(GUI::get_app_config(), preset_bundle); } } diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index ab291d40f8..1d9519bc3f 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -8,9 +8,13 @@ #include #include #include +#include +#include +#include "libslic3r/PrintConfig.hpp" #include "AppConfig.hpp" #include "PresetBundle.hpp" +#include "BedShapeDialog.hpp" namespace Slic3r { @@ -21,6 +25,8 @@ enum { DIALOG_MARGIN = 15, INDEX_MARGIN = 40, BTN_SPACING = 10, + INDENT_SPACING = 30, + VERTICAL_SPACING = 10, }; struct ConfigWizardPage: wxPanel @@ -41,17 +47,13 @@ struct ConfigWizardPage: wxPanel ConfigWizardPage* page_next() const { return p_next; } ConfigWizardPage* chain(ConfigWizardPage *page); - void append_text(wxString text); - void append_widget(wxWindow *widget, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10); - template - void append_thing(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10) + void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10) { content->Add(thing, proportion, flag, border); } - // TODO: remove: - void append_sizer(wxSizer *sizer, int proportion = 0); + void append_text(wxString text); void append_spacer(int space); ConfigWizard::priv *wizard_p() const { return parent->p.get(); } @@ -60,6 +62,7 @@ struct ConfigWizardPage: wxPanel virtual bool Hide() { return Show(false); } virtual wxPanel* extra_buttons() { return nullptr; } virtual void on_page_set() {} + virtual void apply_custom_config(DynamicPrintConfig &config) {} void enable_next(bool enable); private: @@ -84,10 +87,8 @@ struct PageUpdate: ConfigWizardPage { bool version_check; bool preset_update; - - PageUpdate(ConfigWizard *parent); - void presets_update_enable(bool enable); + PageUpdate(ConfigWizard *parent); }; struct PageVendors: ConfigWizardPage @@ -97,22 +98,37 @@ struct PageVendors: ConfigWizardPage struct PageFirmware: ConfigWizardPage { + const ConfigOptionDef &gcode_opt; + wxChoice *gcode_picker; + PageFirmware(ConfigWizard *parent); + virtual void apply_custom_config(DynamicPrintConfig &config); }; struct PageBedShape: ConfigWizardPage { + BedShapePanel *shape_panel; + PageBedShape(ConfigWizard *parent); + virtual void apply_custom_config(DynamicPrintConfig &config); }; struct PageDiameters: ConfigWizardPage { + wxSpinCtrlDouble *spin_nozzle; + wxSpinCtrlDouble *spin_filam; + PageDiameters(ConfigWizard *parent); + virtual void apply_custom_config(DynamicPrintConfig &config); }; struct PageTemperatures: ConfigWizardPage { + wxSpinCtrl *spin_extr; + wxSpinCtrl *spin_bed; + PageTemperatures(ConfigWizard *parent); + virtual void apply_custom_config(DynamicPrintConfig &config); }; @@ -133,7 +149,7 @@ private: std::vector items; std::vector::const_iterator item_active; - void on_paint(wxPaintEvent & evt); + void on_paint(wxPaintEvent &evt); }; struct ConfigWizard::priv @@ -141,6 +157,7 @@ struct ConfigWizard::priv ConfigWizard *q; AppConfig appconfig_vendors; PresetBundle bundle_vendors; + DynamicPrintConfig custom_config; wxBoxSizer *topsizer = nullptr; wxBoxSizer *btnsizer = nullptr; @@ -171,7 +188,8 @@ struct ConfigWizard::priv void on_other_vendors(); void on_custom_setup(); - void on_finish(); + + void apply_config(AppConfig *app_config, PresetBundle *preset_bundle); }; From d1c1dcbe8f380c12120228c7318f243633b27490 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 5 Apr 2018 18:30:03 +0200 Subject: [PATCH 005/145] ConfigWizard: Factor out a PrinterPicker widget, finalize other vendors page --- .../{MK2S.png => PrusaResearch_MK2S.png} | Bin .../{MK2SMM.png => PrusaResearch_MK2SMM.png} | Bin .../{MK3.png => PrusaResearch_MK3.png} | Bin xs/src/slic3r/GUI/ConfigWizard.cpp | 220 ++++++++++++------ xs/src/slic3r/GUI/ConfigWizard_private.hpp | 16 +- 5 files changed, 159 insertions(+), 77 deletions(-) rename resources/icons/printers/{MK2S.png => PrusaResearch_MK2S.png} (100%) rename resources/icons/printers/{MK2SMM.png => PrusaResearch_MK2SMM.png} (100%) rename resources/icons/printers/{MK3.png => PrusaResearch_MK3.png} (100%) diff --git a/resources/icons/printers/MK2S.png b/resources/icons/printers/PrusaResearch_MK2S.png similarity index 100% rename from resources/icons/printers/MK2S.png rename to resources/icons/printers/PrusaResearch_MK2S.png diff --git a/resources/icons/printers/MK2SMM.png b/resources/icons/printers/PrusaResearch_MK2SMM.png similarity index 100% rename from resources/icons/printers/MK2SMM.png rename to resources/icons/printers/PrusaResearch_MK2SMM.png diff --git a/resources/icons/printers/MK3.png b/resources/icons/printers/PrusaResearch_MK3.png similarity index 100% rename from resources/icons/printers/MK3.png rename to resources/icons/printers/PrusaResearch_MK3.png diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index edf958bd8f..914ebb9a19 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -23,6 +23,86 @@ namespace Slic3r { namespace GUI { +// FIXME: scrolling + + +// 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); + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors) : + wxPanel(parent), + variants_checked(0) +{ + const auto vendor_id = vendor.id; + const auto &models = vendor.models; + + auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20); + printer_grid->SetFlexibleDirection(wxVERTICAL); + SetSizer(printer_grid); + + auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + namefont.SetWeight(wxFONTWEIGHT_BOLD); + + for (auto model = models.cbegin(); model != models.cend(); ++model) { + auto *panel = new wxPanel(this); + auto *sizer = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(sizer); + + auto *title = new wxStaticText(panel, wxID_ANY, model->name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + title->SetFont(namefont); + sizer->Add(title, 0, wxBOTTOM, 3); + + auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model->id); + wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG); + auto *bitmap_widget = new wxStaticBitmap(panel, wxID_ANY, bitmap); + sizer->Add(bitmap_widget, 0, wxBOTTOM, 3); + + sizer->AddSpacer(20); + + const auto model_id = model->id; + + for (const auto &variant : model->variants) { + const auto variant_name = variant.name; + auto *cbox = new wxCheckBox(panel, wxID_ANY, wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle")))); + bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant_name); + variants_checked += enabled; + cbox->SetValue(enabled); + sizer->Add(cbox, 0, wxBOTTOM, 3); + cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { + this->variants_checked += event.IsChecked() ? 1 : -1; + PrinterPickerEvent evt(EVT_PRINTER_PICK, this->GetId(), std::move(vendor_id), std::move(model_id), std::move(variant_name), event.IsChecked()); + this->AddPendingEvent(evt); + }); + } + + printer_grid->Add(panel); + } + +} + + // Wizard page base ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname) : @@ -95,8 +175,8 @@ void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable) PageWelcome::PageWelcome(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Welcome to the Slic3r Configuration assistant")), _(L("Welcome"))), - others_buttons(new wxPanel(parent)), - variants_checked(0) + printer_picker(nullptr), + others_buttons(new wxPanel(parent)) { append_text(_(L("Hello, welcome to Slic3r Prusa Edition! TODO: This text."))); @@ -104,73 +184,34 @@ PageWelcome::PageWelcome(ConfigWizard *parent) : const auto &vendors = bundle.vendors; const auto vendor_prusa = std::find(vendors.cbegin(), vendors.cend(), VendorProfile("PrusaResearch")); - const AppConfig &appconfig_vendors = wizard_p()->appconfig_vendors; - if (vendor_prusa != vendors.cend()) { const auto &models = vendor_prusa->models; - auto *printer_picker = new wxPanel(this); - auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20); - printer_grid->SetFlexibleDirection(wxVERTICAL); - printer_picker->SetSizer(printer_grid); + AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; - auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - namefont.SetWeight(wxFONTWEIGHT_BOLD); - - for (auto model = models.cbegin(); model != models.cend(); ++model) { - auto *panel = new wxPanel(printer_picker); - auto *sizer = new wxBoxSizer(wxVERTICAL); - panel->SetSizer(sizer); - - auto *title = new wxStaticText(panel, wxID_ANY, model->name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - title->SetFont(namefont); - sizer->Add(title, 0, wxBOTTOM, 3); - - auto bitmap_file = wxString::Format("printers/%s.png", model->id); - wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG); - auto *bitmap_widget = new wxStaticBitmap(panel, wxID_ANY, bitmap); - sizer->Add(bitmap_widget, 0, wxBOTTOM, 3); - - sizer->AddSpacer(20); - - std::string model_id = model->id; - - for (const auto &variant : model->variants) { - std::string variant_name = variant.name; - auto *cbox = new wxCheckBox(panel, wxID_ANY, wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle")))); - bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant_name); - variants_checked += enabled; - cbox->SetValue(enabled); - sizer->Add(cbox, 0, wxBOTTOM, 3); - cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { - this->variants_checked += event.IsChecked() ? 1 : -1; - this->on_variant_checked(); - AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; - appconfig_vendors.set_variant("PrusaResearch", model_id, variant_name, event.IsChecked()); - }); - } - - printer_grid->Add(panel); - } + printer_picker = new PrinterPicker(this, *vendor_prusa, appconfig_vendors); + printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { + appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + this->on_variant_checked(); + }); append(printer_picker); } - { - auto *sizer = new wxBoxSizer(wxHORIZONTAL); - auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors"))); - // other_vendors->Disable(); // XXX - auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup"))); + const size_t num_other_vendors = vendors.size() - (vendor_prusa != vendors.cend()); + auto *sizer = new wxBoxSizer(wxHORIZONTAL); + auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors"))); + other_vendors->Enable(num_other_vendors > 0); + auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup"))); - sizer->Add(other_vendors); - sizer->AddSpacer(BTN_SPACING); - sizer->Add(custom_setup); + sizer->Add(other_vendors); + sizer->AddSpacer(BTN_SPACING); + sizer->Add(custom_setup); - other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); }); - custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); }); + other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); }); + custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); }); - others_buttons->SetSizer(sizer); - } + others_buttons->SetSizer(sizer); } void PageWelcome::on_page_set() @@ -181,7 +222,7 @@ void PageWelcome::on_page_set() void PageWelcome::on_variant_checked() { - enable_next(variants_checked > 0); + enable_next(printer_picker != nullptr ? printer_picker->variants_checked > 0 : false); } PageUpdate::PageUpdate(ConfigWizard *parent) : @@ -208,35 +249,63 @@ PageUpdate::PageUpdate(ConfigWizard *parent) : PageVendors::PageVendors(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors"))) { + append_text(_(L("Pick another vendor supported by Slic3r PE:"))); + const PresetBundle &bundle = wizard_p()->bundle_vendors; auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); boldfont.SetWeight(wxFONTWEIGHT_BOLD); + AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; + wxArrayString choices_vendors; + for (const auto &vendor : bundle.vendors) { if (vendor.id == "PrusaResearch") { continue; } - auto *label_vendor = new wxStaticText(this, wxID_ANY, vendor.name); - label_vendor->SetFont(boldfont); - append(label_vendor, 0, 0, 0); + auto *picker = new PrinterPicker(this, vendor, appconfig_vendors); + picker->Hide(); + pickers.push_back(picker); + choices_vendors.Add(vendor.name); - for (const auto &model : vendor.models) { - auto *label_model = new wxStaticText(this, wxID_ANY, model.name); - label_model->SetFont(boldfont); - append(label_model, 0, wxLEFT, INDENT_SPACING); + picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { + appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + this->on_variant_checked(); + }); + } - for (const auto &variant : model.variants) { - auto *cbox = new wxCheckBox(this, wxID_ANY, variant.name); - append(cbox, 0, wxEXPAND | wxLEFT, 2 * INDENT_SPACING); - cbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { - // TODO - }); - } - } + auto *vendor_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices_vendors); + if (choices_vendors.GetCount() > 0) { + vendor_picker->SetSelection(0); + on_vendor_pick(0); + } - append_spacer(VERTICAL_SPACING); + vendor_picker->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) { + this->on_vendor_pick(evt.GetInt()); + }); + + append(vendor_picker); + for (PrinterPicker *picker : pickers) { this->append(picker); } +} + +void PageVendors::on_page_set() +{ + on_variant_checked(); +} + +void PageVendors::on_vendor_pick(size_t i) +{ + for (PrinterPicker *picker : pickers) { picker->Hide(); } + if (i < pickers.size()) { + pickers[i]->Show(); + Layout(); } } +void PageVendors::on_variant_checked() +{ + size_t variants_checked = 0; + for (const PrinterPicker *picker : pickers) { variants_checked += picker->variants_checked; } + enable_next(variants_checked > 0); +} PageFirmware::PageFirmware(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))), @@ -613,7 +682,6 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); }); - // p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->on_finish(); }); p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->EndModal(wxID_OK); }); } diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index 1d9519bc3f..9f9395975b 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -29,6 +29,13 @@ enum { VERTICAL_SPACING = 10, }; +struct PrinterPicker: wxPanel +{ + unsigned variants_checked; + + PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors); +}; + struct ConfigWizardPage: wxPanel { enum { @@ -72,8 +79,8 @@ private: struct PageWelcome: ConfigWizardPage { + PrinterPicker *printer_picker; wxPanel *others_buttons; - unsigned variants_checked; PageWelcome(ConfigWizard *parent); @@ -93,7 +100,14 @@ struct PageUpdate: ConfigWizardPage struct PageVendors: ConfigWizardPage { + std::vector pickers; + PageVendors(ConfigWizard *parent); + + virtual void on_page_set(); + + void on_vendor_pick(size_t i); + void on_variant_checked(); }; struct PageFirmware: ConfigWizardPage From 9dcec6662e788a8ae0f04043581a1a3c44d36006 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 6 Apr 2018 12:15:28 +0200 Subject: [PATCH 006/145] ConfigWizard: Other vendor sample data, minor fixes --- resources/icons/printers/BarBaz_M1.png | Bin 0 -> 10369 bytes resources/icons/printers/BarBaz_M2.png | Bin 0 -> 10369 bytes resources/icons/printers/BarBaz_M3.png | Bin 0 -> 10369 bytes resources/icons/printers/Foobar_M1.png | Bin 0 -> 9323 bytes resources/icons/printers/Foobar_M2.png | Bin 0 -> 9323 bytes resources/icons/printers/Foobar_M3.png | Bin 0 -> 9323 bytes resources/profiles/BarBaz.ini | 985 +++++++++++++++++++++ resources/profiles/Foobar.ini | 985 +++++++++++++++++++++ xs/src/slic3r/GUI/AppConfig.cpp | 2 - xs/src/slic3r/GUI/AppConfig.hpp | 10 +- xs/src/slic3r/GUI/ConfigWizard.cpp | 21 +- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 9 +- xs/xsp/my.map | 3 - 13 files changed, 1990 insertions(+), 25 deletions(-) create mode 100644 resources/icons/printers/BarBaz_M1.png create mode 100644 resources/icons/printers/BarBaz_M2.png create mode 100644 resources/icons/printers/BarBaz_M3.png create mode 100644 resources/icons/printers/Foobar_M1.png create mode 100644 resources/icons/printers/Foobar_M2.png create mode 100644 resources/icons/printers/Foobar_M3.png create mode 100644 resources/profiles/BarBaz.ini create mode 100644 resources/profiles/Foobar.ini diff --git a/resources/icons/printers/BarBaz_M1.png b/resources/icons/printers/BarBaz_M1.png new file mode 100644 index 0000000000000000000000000000000000000000..5924cc88b9ac373d5dc802ac06b82d1ad9d5015c GIT binary patch literal 10369 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 CbaD>> literal 0 HcmV?d00001 diff --git a/resources/icons/printers/Foobar_M2.png b/resources/icons/printers/Foobar_M2.png new file mode 100644 index 0000000000000000000000000000000000000000..61a76a63d1d3b39f402aea981f7adc11e346b389 GIT binary patch literal 9323 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 CbaD>> literal 0 HcmV?d00001 diff --git a/resources/icons/printers/Foobar_M3.png b/resources/icons/printers/Foobar_M3.png new file mode 100644 index 0000000000000000000000000000000000000000..61a76a63d1d3b39f402aea981f7adc11e346b389 GIT binary patch literal 9323 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 CbaD>> literal 0 HcmV?d00001 diff --git a/resources/profiles/BarBaz.ini b/resources/profiles/BarBaz.ini new file mode 100644 index 0000000000..83bb156844 --- /dev/null +++ b/resources/profiles/BarBaz.ini @@ -0,0 +1,985 @@ +# Print profiles for the Prusa Research printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Bar Baz +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the Slic3r configuration to be downgraded. +config_version = 0.1 +# Where to get the updates from? +config_update_url = https://example.com + +# The printer models will be shown by the Configuration Wizard in this order, +# also the first model installed & the first nozzle installed will be activated after install. +#TODO: One day we may differentiate variants of the nozzles / hot ends, +#for example by the melt zone size, or whether the nozzle is hardened. +[printer_model:M1] +name = Bar Baz Model 1 +variants = 0.4; 0.25; 0.6 + +[printer_model:M2] +name = Bar Baz Model 2 +variants = 0.4; 0.25; 0.6 + +[printer_model:M3] +# Printer model name will be shown by the installation wizard. +name = Bar Baz Model 3 +variants = 0.4; 0.6 + +# All presets starting with asterisk, for example *common*, are intermediate and they will +# not make it into the user interface. + +# Common print preset, mostly derived from MK2 single material with a 0.4mm nozzle. +# All other print presets will derive from the *common* print preset. +[print:*common*] +avoid_crossing_perimeters = 0 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeters_first = 0 +external_perimeter_extrusion_width = 0.45 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +interface_shells = 0 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +overhangs = 0 +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +perimeters = 2 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirts = 1 +skirt_distance = 2 +skirt_height = 3 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_extruder = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_enforce_layers = 0 +support_material_contact_distance = 0.15 +support_material_interface_contact_loops = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 40 +travel_speed = 180 +wipe_tower = 0 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +# Print parameters common to a 0.25mm diameter nozzle. +[print:*0.25nozzle*] +external_perimeter_extrusion_width = 0.25 +extrusion_width = 0.25 +first_layer_extrusion_width = 0.25 +infill_extrusion_width = 0.25 +perimeter_extrusion_width = 0.25 +solid_infill_extrusion_width = 0.25 +top_infill_extrusion_width = 0.25 +support_material_extrusion_width = 0.18 +support_material_interface_layers = 0 +support_material_interface_spacing = 0.15 +support_material_spacing = 1 +support_material_xy_spacing = 150% + +# Print parameters common to a 0.6mm diameter nozzle. +[print:*0.6nozzle*] +external_perimeter_extrusion_width = 0.61 +extrusion_width = 0.67 +first_layer_extrusion_width = 0.65 +infill_extrusion_width = 0.7 +perimeter_extrusion_width = 0.65 +solid_infill_extrusion_width = 0.65 +top_infill_extrusion_width = 0.6 + +[print:*soluble_support*] +overhangs = 1 +skirts = 0 +support_material = 1 +support_material_contact_distance = 0 +support_material_extruder = 4 +support_material_extrusion_width = 0.45 +support_material_interface_extruder = 4 +support_material_interface_spacing = 0.1 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 1 +wipe_tower = 1 + +[print:*0.05mm*] +inherits = *common* +bottom_solid_layers = 10 +bridge_acceleration = 300 +bridge_flow_ratio = 0.7 +default_acceleration = 500 +external_perimeter_speed = 20 +fill_density = 20% +first_layer_acceleration = 500 +gap_fill_speed = 20 +infill_acceleration = 800 +infill_speed = 30 +max_print_speed = 80 +small_perimeter_speed = 15 +solid_infill_speed = 30 +support_material_extrusion_width = 0.3 +support_material_spacing = 1.5 +layer_height = 0.05 +perimeter_acceleration = 300 +perimeter_speed = 30 +perimeters = 3 +support_material_speed = 30 +top_solid_infill_speed = 20 +top_solid_layers = 15 + +[print:0.05mm ULTRADETAIL] +inherits = *0.05mm* +infill_extrusion_width = 0.5 + +[print:0.05mm ULTRADETAIL MK3] +inherits = *0.05mm* +fill_pattern = grid +top_infill_extrusion_width = 0.4 + +[print:0.05mm ULTRADETAIL 0.25 nozzle] +inherits = *0.05mm* +external_perimeter_extrusion_width = 0 +extrusion_width = 0.28 +fill_density = 20% +first_layer_extrusion_width = 0.3 +infill_extrusion_width = 0 +infill_speed = 20 +max_print_speed = 100 +perimeter_extrusion_width = 0 +perimeter_speed = 20 +small_perimeter_speed = 10 +solid_infill_extrusion_width = 0 +solid_infill_speed = 20 +support_material_speed = 20 +top_infill_extrusion_width = 0 + +[print:0.05mm ULTRADETAIL 0.25 nozzle MK3] +inherits = *0.05mm*; *0.25nozzle* +fill_pattern = grid +top_infill_extrusion_width = 0.4 + +[print:*0.10mm*] +inherits = *common* +bottom_solid_layers = 7 +bridge_flow_ratio = 0.7 +layer_height = 0.1 +perimeter_acceleration = 800 +top_solid_layers = 9 + +[print:0.10mm DETAIL] +inherits = *0.10mm* +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +perimeter_speed = 50 +solid_infill_speed = 50 + +[print:0.10mm DETAIL MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:0.10mm DETAIL 0.25 nozzle] +inherits = *0.10mm* +bridge_acceleration = 600 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 10 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +[print:0.10mm DETAIL 0.25 nozzle MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:0.10mm DETAIL 0.6 nozzle MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:*0.15mm*] +inherits = *common* +bottom_solid_layers = 5 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.15 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 7 + +[print:0.15mm 100mms Linear Advance] +inherits = *0.15mm* +bridge_flow_ratio = 0.95 +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.15mm OPTIMAL] +inherits = *0.15mm* +top_infill_extrusion_width = 0.45 + +[print:0.15mm OPTIMAL 0.25 nozzle] +inherits = *0.15mm*; *0.25nozzle* +bridge_acceleration = 600 +bridge_flow_ratio = 0.7 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 10 +solid_infill_speed = 40 +support_material_extrusion_width = 0.2 +top_solid_infill_speed = 30 + +[print:0.15mm OPTIMAL 0.6 nozzle] +inherits = *0.15mm*; *0.6nozzle* + +[print:0.15mm OPTIMAL MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.15mm OPTIMAL SOLUBLE FULL] +inherits = *0.15mm*; *soluble_support* +external_perimeter_speed = 25 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +wipe_tower = 1 + +[print:0.15mm OPTIMAL SOLUBLE INTERFACE] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.15mm OPTIMAL 0.25 nozzle MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 +[print:*0.20mm*] +inherits = *common* +bottom_solid_layers = 4 +bridge_flow_ratio = 0.95 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.2 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 5 + +[print:0.15mm OPTIMAL 0.6 nozzle MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.20mm 100mms Linear Advance] +inherits = *0.20mm* +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.20mm FAST MK3] +inherits = *0.20mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.20mm NORMAL] +inherits = *0.20mm* + +[print:0.20mm NORMAL 0.6 nozzle] +inherits = *0.20mm*; *0.6nozzle* + +[print:0.20mm NORMAL SOLUBLE FULL] +inherits = *0.20mm*; *soluble_support* +external_perimeter_speed = 30 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +[print:0.20mm NORMAL SOLUBLE INTERFACE] +inherits = 0.20mm NORMAL SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.20mm FAST 0.6 nozzle MK3] +inherits = *0.20mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:*0.35mm*] +inherits = *common* +bottom_solid_layers = 3 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 40 +first_layer_extrusion_width = 0.75 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.35 +perimeter_acceleration = 800 +perimeter_extrusion_width = 0.65 +perimeter_speed = 50 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 60 +top_solid_infill_speed = 50 +top_solid_layers = 4 + +[print:0.35mm FAST] +inherits = *0.35mm* +bridge_flow_ratio = 0.95 +first_layer_extrusion_width = 0.42 +perimeter_extrusion_width = 0.43 +solid_infill_extrusion_width = 0.7 +top_infill_extrusion_width = 0.43 + +[print:0.35mm FAST 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle* + +[print:0.35mm FAST sol full 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle*; *soluble_support* +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 30 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +perimeter_speed = 40 +support_material_extrusion_width = 0.55 +support_material_interface_layers = 3 +support_material_xy_spacing = 120% +top_infill_extrusion_width = 0.57 + +[print:0.35mm FAST sol int 0.6 nozzle] +inherits = 0.35mm FAST sol full 0.6 nozzle +support_material_extruder = 0 +support_material_interface_layers = 2 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% + +[filament:*common*] +cooling = 1 +compatible_printers = +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 = 5 +slowdown_below_layer_time = 20 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; 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 = #FF3232 +filament_max_volumetric_speed = 15 +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_max_volumetric_speed = 8 +filament_type = PET +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_max_volumetric_speed = 11 +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #00CA0A +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 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 240 + +[filament:ColorFabb Brass Bronze] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 + +[filament:ColorFabb HT] +inherits = *PET* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +first_layer_bed_temperature = 105 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* + +[filament:ColorFabb Woodfil] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 200 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 200 + +[filament:ColorFabb XT] +inherits = *PET* +filament_type = PLA +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 1 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +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* +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:E3D Edge] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:E3D PC-ABS] +inherits = *ABS* +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum ABS] +inherits = *ABS* +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +fan_always_on = 1 +first_layer_temperature = 265 +temperature = 265 + +[filament:Fillamentum CPE HG100 HM100] +inherits = *PET* +filament_notes = "CPE HG100 , CPE HM100" +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +max_fan_speed = 50 +min_fan_speed = 50 +temperature = 275 + +[filament:Fillamentum Timberfil] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 190 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 190 + +[filament:Generic ABS] +inherits = *ABS* +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" + +[filament:Generic PET] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:Generic PLA] +inherits = *PLA* +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:Polymaker PC-Max] +inherits = *ABS* +bed_temperature = 115 +filament_colour = #3A80CA +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 + +[filament:Primavalue PVA] +inherits = *PLA* +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 195 + +[filament:Prusa ABS] +inherits = *ABS* +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" + +[filament:Prusa HIPS] +inherits = *ABS* +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 0.9 +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 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[filament:Prusa PET] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:Prusa PLA] +inherits = *PLA* +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:SemiFlex or Flexfill 98A] +inherits = *FLEX* + +[filament:Taulman Bridge] +inherits = *common* +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_max_volumetric_speed = 10 +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 60 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 250 + +[filament:Taulman T-Glase] +inherits = *PET* +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 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" + +[filament:Verbatim BVOH] +inherits = *common* +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_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_soluble = 1 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +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_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" +filament_type = PLA +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[printer:*common*] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 0 +end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = marlin +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n +printer_settings_id = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 0.8 +retract_length_toolchange = 4 +retract_lift = 0.6 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 35 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 +printer_model = M2 +printer_variant = 0.4 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusa PLA + +[printer:*multimaterial*] +inherits = *common* +deretract_speed = 50 +retract_before_travel = 3 +retract_before_wipe = 60% +retract_layer_change = 0 +retract_length = 4 +retract_lift = 0.6 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 80 +single_extruder_multi_material = 1 +printer_model = M3 + +[printer:*mm-single*] +inherits = *multimaterial* +end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 + +[printer:*mm-multi*] +inherits = *multimaterial* +end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors +extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 +nozzle_diameter = 0.4,0.4,0.4,0.4 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +variable_layer_height = 0 + +[printer:Original Prusa i3 MK2] +inherits = *common* + +[printer:Original Prusa i3 MK2 0.25 nozzle] +inherits = *common* +max_layer_height = 0.1 +min_layer_height = 0.05 +nozzle_diameter = 0.25 +retract_length = 1 +retract_speed = 50 +variable_layer_height = 0 +printer_variant = 0.25 +default_print_profile = 0.10mm DETAIL 0.25 nozzle + +[printer:Original Prusa i3 MK2 0.6 nozzle] +inherits = *common* +max_layer_height = 0.35 +min_layer_height = 0.1 +nozzle_diameter = 0.6 +printer_variant = 0.6 + +[printer:Original Prusa i3 MK2 MM Single Mode] +inherits = *mm-single* + +[printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] +inherits = *mm-single* +nozzle_diameter = 0.6 +printer_variant = 0.6 + +[printer:Original Prusa i3 MK2 MultiMaterial] +inherits = *mm-multi* +nozzle_diameter = 0.4,0.4,0.4,0.4 + +[printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] +inherits = *mm-multi* +nozzle_diameter = 0.6,0.6,0.6,0.6 +printer_variant = 0.6 + +[printer:Original Prusa i3 MK3] +inherits = *common* +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.15mm OPTIMAL MK3 + +[printer:Original Prusa i3 MK3 0.25 nozzle] +inherits = *common* +nozzle_diameter = 0.25 +printer_variant = 0.25 +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.10mm DETAIL MK3 + +[printer:Original Prusa i3 MK3 0.6 nozzle] +inherits = *common* +nozzle_diameter = 0.6 +printer_variant = 0.6 +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.15mm OPTIMAL MK3 diff --git a/resources/profiles/Foobar.ini b/resources/profiles/Foobar.ini new file mode 100644 index 0000000000..21681398aa --- /dev/null +++ b/resources/profiles/Foobar.ini @@ -0,0 +1,985 @@ +# Print profiles for the Prusa Research printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Foo Bar +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the Slic3r configuration to be downgraded. +config_version = 0.1 +# Where to get the updates from? +config_update_url = https://example.com + +# The printer models will be shown by the Configuration Wizard in this order, +# also the first model installed & the first nozzle installed will be activated after install. +#TODO: One day we may differentiate variants of the nozzles / hot ends, +#for example by the melt zone size, or whether the nozzle is hardened. +[printer_model:M1] +name = Foo Bar Model 1 +variants = 0.4; 0.25; 0.6 + +[printer_model:M2] +name = Foo Bar Model 2 +variants = 0.4; 0.25; 0.6 + +[printer_model:M3] +# Printer model name will be shown by the installation wizard. +name = Foo Bar Model 3 +variants = 0.4; 0.6 + +# All presets starting with asterisk, for example *common*, are intermediate and they will +# not make it into the user interface. + +# Common print preset, mostly derived from MK2 single material with a 0.4mm nozzle. +# All other print presets will derive from the *common* print preset. +[print:*common*] +avoid_crossing_perimeters = 0 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeters_first = 0 +external_perimeter_extrusion_width = 0.45 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +interface_shells = 0 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +overhangs = 0 +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +perimeters = 2 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirts = 1 +skirt_distance = 2 +skirt_height = 3 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_extruder = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_enforce_layers = 0 +support_material_contact_distance = 0.15 +support_material_interface_contact_loops = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 40 +travel_speed = 180 +wipe_tower = 0 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +# Print parameters common to a 0.25mm diameter nozzle. +[print:*0.25nozzle*] +external_perimeter_extrusion_width = 0.25 +extrusion_width = 0.25 +first_layer_extrusion_width = 0.25 +infill_extrusion_width = 0.25 +perimeter_extrusion_width = 0.25 +solid_infill_extrusion_width = 0.25 +top_infill_extrusion_width = 0.25 +support_material_extrusion_width = 0.18 +support_material_interface_layers = 0 +support_material_interface_spacing = 0.15 +support_material_spacing = 1 +support_material_xy_spacing = 150% + +# Print parameters common to a 0.6mm diameter nozzle. +[print:*0.6nozzle*] +external_perimeter_extrusion_width = 0.61 +extrusion_width = 0.67 +first_layer_extrusion_width = 0.65 +infill_extrusion_width = 0.7 +perimeter_extrusion_width = 0.65 +solid_infill_extrusion_width = 0.65 +top_infill_extrusion_width = 0.6 + +[print:*soluble_support*] +overhangs = 1 +skirts = 0 +support_material = 1 +support_material_contact_distance = 0 +support_material_extruder = 4 +support_material_extrusion_width = 0.45 +support_material_interface_extruder = 4 +support_material_interface_spacing = 0.1 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 1 +wipe_tower = 1 + +[print:*0.05mm*] +inherits = *common* +bottom_solid_layers = 10 +bridge_acceleration = 300 +bridge_flow_ratio = 0.7 +default_acceleration = 500 +external_perimeter_speed = 20 +fill_density = 20% +first_layer_acceleration = 500 +gap_fill_speed = 20 +infill_acceleration = 800 +infill_speed = 30 +max_print_speed = 80 +small_perimeter_speed = 15 +solid_infill_speed = 30 +support_material_extrusion_width = 0.3 +support_material_spacing = 1.5 +layer_height = 0.05 +perimeter_acceleration = 300 +perimeter_speed = 30 +perimeters = 3 +support_material_speed = 30 +top_solid_infill_speed = 20 +top_solid_layers = 15 + +[print:0.05mm ULTRADETAIL] +inherits = *0.05mm* +infill_extrusion_width = 0.5 + +[print:0.05mm ULTRADETAIL MK3] +inherits = *0.05mm* +fill_pattern = grid +top_infill_extrusion_width = 0.4 + +[print:0.05mm ULTRADETAIL 0.25 nozzle] +inherits = *0.05mm* +external_perimeter_extrusion_width = 0 +extrusion_width = 0.28 +fill_density = 20% +first_layer_extrusion_width = 0.3 +infill_extrusion_width = 0 +infill_speed = 20 +max_print_speed = 100 +perimeter_extrusion_width = 0 +perimeter_speed = 20 +small_perimeter_speed = 10 +solid_infill_extrusion_width = 0 +solid_infill_speed = 20 +support_material_speed = 20 +top_infill_extrusion_width = 0 + +[print:0.05mm ULTRADETAIL 0.25 nozzle MK3] +inherits = *0.05mm*; *0.25nozzle* +fill_pattern = grid +top_infill_extrusion_width = 0.4 + +[print:*0.10mm*] +inherits = *common* +bottom_solid_layers = 7 +bridge_flow_ratio = 0.7 +layer_height = 0.1 +perimeter_acceleration = 800 +top_solid_layers = 9 + +[print:0.10mm DETAIL] +inherits = *0.10mm* +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +perimeter_speed = 50 +solid_infill_speed = 50 + +[print:0.10mm DETAIL MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:0.10mm DETAIL 0.25 nozzle] +inherits = *0.10mm* +bridge_acceleration = 600 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 10 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +[print:0.10mm DETAIL 0.25 nozzle MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:0.10mm DETAIL 0.6 nozzle MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:*0.15mm*] +inherits = *common* +bottom_solid_layers = 5 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.15 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 7 + +[print:0.15mm 100mms Linear Advance] +inherits = *0.15mm* +bridge_flow_ratio = 0.95 +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.15mm OPTIMAL] +inherits = *0.15mm* +top_infill_extrusion_width = 0.45 + +[print:0.15mm OPTIMAL 0.25 nozzle] +inherits = *0.15mm*; *0.25nozzle* +bridge_acceleration = 600 +bridge_flow_ratio = 0.7 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 10 +solid_infill_speed = 40 +support_material_extrusion_width = 0.2 +top_solid_infill_speed = 30 + +[print:0.15mm OPTIMAL 0.6 nozzle] +inherits = *0.15mm*; *0.6nozzle* + +[print:0.15mm OPTIMAL MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.15mm OPTIMAL SOLUBLE FULL] +inherits = *0.15mm*; *soluble_support* +external_perimeter_speed = 25 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +wipe_tower = 1 + +[print:0.15mm OPTIMAL SOLUBLE INTERFACE] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.15mm OPTIMAL 0.25 nozzle MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 +[print:*0.20mm*] +inherits = *common* +bottom_solid_layers = 4 +bridge_flow_ratio = 0.95 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.2 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 5 + +[print:0.15mm OPTIMAL 0.6 nozzle MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.20mm 100mms Linear Advance] +inherits = *0.20mm* +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.20mm FAST MK3] +inherits = *0.20mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.20mm NORMAL] +inherits = *0.20mm* + +[print:0.20mm NORMAL 0.6 nozzle] +inherits = *0.20mm*; *0.6nozzle* + +[print:0.20mm NORMAL SOLUBLE FULL] +inherits = *0.20mm*; *soluble_support* +external_perimeter_speed = 30 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +[print:0.20mm NORMAL SOLUBLE INTERFACE] +inherits = 0.20mm NORMAL SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.20mm FAST 0.6 nozzle MK3] +inherits = *0.20mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:*0.35mm*] +inherits = *common* +bottom_solid_layers = 3 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 40 +first_layer_extrusion_width = 0.75 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.35 +perimeter_acceleration = 800 +perimeter_extrusion_width = 0.65 +perimeter_speed = 50 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 60 +top_solid_infill_speed = 50 +top_solid_layers = 4 + +[print:0.35mm FAST] +inherits = *0.35mm* +bridge_flow_ratio = 0.95 +first_layer_extrusion_width = 0.42 +perimeter_extrusion_width = 0.43 +solid_infill_extrusion_width = 0.7 +top_infill_extrusion_width = 0.43 + +[print:0.35mm FAST 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle* + +[print:0.35mm FAST sol full 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle*; *soluble_support* +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 30 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +perimeter_speed = 40 +support_material_extrusion_width = 0.55 +support_material_interface_layers = 3 +support_material_xy_spacing = 120% +top_infill_extrusion_width = 0.57 + +[print:0.35mm FAST sol int 0.6 nozzle] +inherits = 0.35mm FAST sol full 0.6 nozzle +support_material_extruder = 0 +support_material_interface_layers = 2 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% + +[filament:*common*] +cooling = 1 +compatible_printers = +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 = 5 +slowdown_below_layer_time = 20 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; 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 = #FF3232 +filament_max_volumetric_speed = 15 +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_max_volumetric_speed = 8 +filament_type = PET +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_max_volumetric_speed = 11 +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #00CA0A +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 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 240 + +[filament:ColorFabb Brass Bronze] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 + +[filament:ColorFabb HT] +inherits = *PET* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +first_layer_bed_temperature = 105 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* + +[filament:ColorFabb Woodfil] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 200 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 200 + +[filament:ColorFabb XT] +inherits = *PET* +filament_type = PLA +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 1 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +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* +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:E3D Edge] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:E3D PC-ABS] +inherits = *ABS* +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum ABS] +inherits = *ABS* +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +fan_always_on = 1 +first_layer_temperature = 265 +temperature = 265 + +[filament:Fillamentum CPE HG100 HM100] +inherits = *PET* +filament_notes = "CPE HG100 , CPE HM100" +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +max_fan_speed = 50 +min_fan_speed = 50 +temperature = 275 + +[filament:Fillamentum Timberfil] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 190 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 190 + +[filament:Generic ABS] +inherits = *ABS* +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" + +[filament:Generic PET] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:Generic PLA] +inherits = *PLA* +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:Polymaker PC-Max] +inherits = *ABS* +bed_temperature = 115 +filament_colour = #3A80CA +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 + +[filament:Primavalue PVA] +inherits = *PLA* +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 195 + +[filament:Prusa ABS] +inherits = *ABS* +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" + +[filament:Prusa HIPS] +inherits = *ABS* +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 0.9 +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 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[filament:Prusa PET] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:Prusa PLA] +inherits = *PLA* +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:SemiFlex or Flexfill 98A] +inherits = *FLEX* + +[filament:Taulman Bridge] +inherits = *common* +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_max_volumetric_speed = 10 +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 60 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 250 + +[filament:Taulman T-Glase] +inherits = *PET* +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 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" + +[filament:Verbatim BVOH] +inherits = *common* +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_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_soluble = 1 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +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_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" +filament_type = PLA +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[printer:*common*] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 0 +end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = marlin +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n +printer_settings_id = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 0.8 +retract_length_toolchange = 4 +retract_lift = 0.6 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 35 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 +printer_model = M2 +printer_variant = 0.4 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusa PLA + +[printer:*multimaterial*] +inherits = *common* +deretract_speed = 50 +retract_before_travel = 3 +retract_before_wipe = 60% +retract_layer_change = 0 +retract_length = 4 +retract_lift = 0.6 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 80 +single_extruder_multi_material = 1 +printer_model = M3 + +[printer:*mm-single*] +inherits = *multimaterial* +end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 + +[printer:*mm-multi*] +inherits = *multimaterial* +end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors +extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 +nozzle_diameter = 0.4,0.4,0.4,0.4 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +variable_layer_height = 0 + +[printer:Original Prusa i3 MK2] +inherits = *common* + +[printer:Original Prusa i3 MK2 0.25 nozzle] +inherits = *common* +max_layer_height = 0.1 +min_layer_height = 0.05 +nozzle_diameter = 0.25 +retract_length = 1 +retract_speed = 50 +variable_layer_height = 0 +printer_variant = 0.25 +default_print_profile = 0.10mm DETAIL 0.25 nozzle + +[printer:Original Prusa i3 MK2 0.6 nozzle] +inherits = *common* +max_layer_height = 0.35 +min_layer_height = 0.1 +nozzle_diameter = 0.6 +printer_variant = 0.6 + +[printer:Original Prusa i3 MK2 MM Single Mode] +inherits = *mm-single* + +[printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] +inherits = *mm-single* +nozzle_diameter = 0.6 +printer_variant = 0.6 + +[printer:Original Prusa i3 MK2 MultiMaterial] +inherits = *mm-multi* +nozzle_diameter = 0.4,0.4,0.4,0.4 + +[printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] +inherits = *mm-multi* +nozzle_diameter = 0.6,0.6,0.6,0.6 +printer_variant = 0.6 + +[printer:Original Prusa i3 MK3] +inherits = *common* +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.15mm OPTIMAL MK3 + +[printer:Original Prusa i3 MK3 0.25 nozzle] +inherits = *common* +nozzle_diameter = 0.25 +printer_variant = 0.25 +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.10mm DETAIL MK3 + +[printer:Original Prusa i3 MK3 0.6 nozzle] +inherits = *common* +nozzle_diameter = 0.6 +printer_variant = 0.6 +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.15mm OPTIMAL MK3 diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 10a586e27c..9e5ce5f1bc 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -135,8 +135,6 @@ void AppConfig::save() bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const { - // std::cerr << "AppConfig::get_variant(" << vendor << ", " << model << ", " << variant << ") " << std::endl; - const auto it_v = m_vendors.find(vendor); if (it_v == m_vendors.end()) { return false; } const auto it_m = it_v->second.find(model); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index e43ff51bf6..7aac95fd6b 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -68,9 +68,9 @@ public: void clear_section(const std::string §ion) { m_storage[section].clear(); } - bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const; - void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable); - void set_vendors(const AppConfig &from); + bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const; + void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable); + void set_vendors(const AppConfig &from); // return recent/skein_directory or recent/config_directory or empty string. std::string get_last_dir() const; @@ -86,8 +86,8 @@ public: void reset_selections(); // Whether the Slic3r version available online differs from this one - bool version_check_enabled() const; - bool slic3r_update_avail() const; + bool version_check_enabled() const; + bool slic3r_update_avail() const; // Get the default config path from Slic3r::data_dir(). static std::string config_path(); diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 914ebb9a19..ce51d76411 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -1,6 +1,5 @@ #include "ConfigWizard_private.hpp" -#include // XXX #include #include #include @@ -23,9 +22,6 @@ namespace Slic3r { namespace GUI { -// FIXME: scrolling - - // Printer model picker GUI control struct PrinterPickerEvent : public wxEvent @@ -296,7 +292,7 @@ void PageVendors::on_vendor_pick(size_t i) for (PrinterPicker *picker : pickers) { picker->Hide(); } if (i < pickers.size()) { pickers[i]->Show(); - Layout(); + wizard_p()->layout_fit(); } } @@ -352,7 +348,7 @@ PageBedShape::PageBedShape(ConfigWizard *parent) : { append_text(_(L("Set the shape of your printer's bed."))); - shape_panel->build_panel(wizard_p()->custom_config.option("bed_shape")); + shape_panel->build_panel(wizard_p()->custom_config->option("bed_shape")); append(shape_panel); } @@ -583,7 +579,13 @@ void ConfigWizard::priv::set_page(ConfigWizardPage *page) btn_next->Show(page->page_next() != nullptr); btn_finish->Show(page->page_next() == nullptr); + layout_fit(); +} + +void ConfigWizard::priv::layout_fit() +{ q->Layout(); + q->Fit(); } void ConfigWizard::priv::enable_next(bool enable) @@ -619,9 +621,9 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese preset_bundle->load_presets(*app_config); } else { for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) { - page->apply_custom_config(custom_config); + page->apply_custom_config(*custom_config); } - preset_bundle->load_config("My Settings", custom_config); + preset_bundle->load_config("My Settings", *custom_config); } } @@ -632,10 +634,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : p(new priv(this)) { p->load_vendors(); - std::unique_ptr custom_config_defaults(DynamicPrintConfig::new_from_defaults_keys({ + p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ "gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", })); - p->custom_config.apply(*custom_config_defaults); p->index = new ConfigWizardIndex(this); diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index 9f9395975b..d32a609be6 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -22,6 +22,8 @@ namespace GUI { enum { + CONTENT_WIDTH = 500, + DIALOG_MARGIN = 15, INDEX_MARGIN = 40, BTN_SPACING = 10, @@ -38,10 +40,6 @@ struct PrinterPicker: wxPanel struct ConfigWizardPage: wxPanel { - enum { - CONTENT_WIDTH = 500, - }; - ConfigWizard *parent; const wxString shortname; wxBoxSizer *content; @@ -171,7 +169,7 @@ struct ConfigWizard::priv ConfigWizard *q; AppConfig appconfig_vendors; PresetBundle bundle_vendors; - DynamicPrintConfig custom_config; + std::unique_ptr custom_config; wxBoxSizer *topsizer = nullptr; wxBoxSizer *btnsizer = nullptr; @@ -196,6 +194,7 @@ struct ConfigWizard::priv void add_page(ConfigWizardPage *page); void index_refresh(); void set_page(ConfigWizardPage *page); + void layout_fit(); void go_prev() { if (page_current != nullptr) { set_page(page_current->page_prev()); } } void go_next() { if (page_current != nullptr) { set_page(page_current->page_next()); } } void enable_next(bool enable); diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 9c0f60c1dc..c1ca58827b 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -235,9 +235,6 @@ PresetHints* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T TabIface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -# TODO: remove: -# ConfigWizard* O_OBJECT_SLIC3R -# Ref O_OBJECT_SLIC3R_T PresetUpdater* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T From 90a8ef8e9f208dce144cea4252173079fd0f830c Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 6 Apr 2018 13:18:12 +0200 Subject: [PATCH 007/145] Cleanup --- lib/Slic3r/GUI/MainFrame.pm | 44 +--------------------- resources/profiles/PrusaResearch.ini | 5 --- xs/src/slic3r/GUI/ConfigWizard.cpp | 15 +++++--- xs/src/slic3r/GUI/ConfigWizard.hpp | 3 +- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 4 +- xs/src/slic3r/GUI/GUI.cpp | 4 +- xs/src/slic3r/GUI/GUI.hpp | 5 +-- xs/src/slic3r/GUI/Preset.cpp | 1 - xs/src/slic3r/GUI/Preset.hpp | 5 +-- xs/src/slic3r/GUI/PresetBundle.cpp | 6 +-- xs/xsp/GUI.xsp | 4 +- 11 files changed, 23 insertions(+), 73 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 4307375c81..402cb80519 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -644,48 +644,8 @@ sub config_wizard { # Exit wizard if there are unsaved changes and the user cancels the action. return unless $self->check_unsaved_changes; - - # TODO: Offer "reset user profile" - Slic3r::GUI::open_config_wizard(wxTheApp->{preset_bundle}); - # Load the currently selected preset into the GUI, update the preset selection box. - foreach my $tab (values %{$self->{options_tabs}}) { # XXX: only if not cancelled? - $tab->load_current_preset; - } - return; - - # Enumerate the profiles bundled with the Slic3r installation under resources/profiles. - my $directory = Slic3r::resources_dir() . "/profiles"; - my @profiles = (); - if (opendir(DIR, Slic3r::encode_path($directory))) { - while (my $file = readdir(DIR)) { - if ($file =~ /\.ini$/) { - $file =~ s/\.ini$//; - push @profiles, Slic3r::decode_path($file); - } - } - closedir(DIR); - } - # Open the wizard. - if (my $result = Slic3r::GUI::ConfigWizard->new($self, \@profiles, $fresh_start)->run) { - eval { - if ($result->{reset_user_profile}) { - wxTheApp->{preset_bundle}->reset(1); - } - if (defined $result->{config}) { - # Load and save the settings into print, filament and printer presets. - wxTheApp->{preset_bundle}->load_config('My Settings', $result->{config}); - } else { - # Wizard returned a name of a preset bundle bundled with the installation. Unpack it. - wxTheApp->{preset_bundle}->install_vendor_configbundle($directory . '/' . $result->{preset_name} . '.ini'); - # Reset the print / filament / printer selections, so that following line will select some sensible defaults. - if ($fresh_start) { - wxTheApp->{app_config}->reset_selections; - } - # Reload all presets after the vendor config bundle has been installed. - wxTheApp->{preset_bundle}->load_presets(wxTheApp->{app_config}); - } - }; - Slic3r::GUI::catch_error($self) and return; + # TODO: Offer "reset user profile" ??? + if (Slic3r::GUI::open_config_wizard(wxTheApp->{preset_bundle})) { # Load the currently selected preset into the GUI, update the preset selection box. foreach my $tab (values %{$self->{options_tabs}}) { $tab->load_current_preset; diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 6dc29a96e9..b654b4039f 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -1017,8 +1017,3 @@ retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 - -[presets] -print = 0.15mm OPTIMAL MK3 -printer = Original Prusa i3 MK3 -filament = Prusa PLA diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index ce51d76411..032b5e903b 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -412,16 +412,16 @@ void PageDiameters::apply_custom_config(DynamicPrintConfig &config) PageTemperatures::PageTemperatures(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Extruder and Bed Temperatures")), _(L("Temperatures"))), - spin_extr(new wxSpinCtrl(this, wxID_ANY)), - spin_bed(new wxSpinCtrl(this, wxID_ANY)) + spin_extr(new wxSpinCtrlDouble(this, wxID_ANY)), + spin_bed(new wxSpinCtrlDouble(this, wxID_ANY)) { - spin_extr->SetIncrement(5); + spin_extr->SetIncrement(5.0); const auto &def_extr = print_config_def.options["temperature"]; spin_extr->SetRange(def_extr.min, def_extr.max); auto *default_extr = dynamic_cast(def_extr.default_value); spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); - spin_bed->SetIncrement(5); + spin_bed->SetIncrement(5.0); const auto &def_bed = print_config_def.options["bed_temperature"]; spin_bed->SetRange(def_bed.min, def_bed.max); auto *default_bed = dynamic_cast(def_bed.default_value); @@ -541,7 +541,7 @@ void ConfigWizard::priv::load_vendors() const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor"; for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) { if (it->path().extension() == ".ini") { - bundle_vendors.load_configbundle(it->path().native(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + bundle_vendors.load_configbundle(it->path().string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); } } @@ -688,7 +688,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : ConfigWizard::~ConfigWizard() {} -void ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) +bool ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) { const auto profiles_dir = fs::path(resources_dir()) / "profiles"; for (fs::directory_iterator it(profiles_dir); it != fs::directory_iterator(); ++it) { @@ -700,6 +700,9 @@ void ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) ConfigWizard wizard(parent); if (wizard.ShowModal() == wxID_OK) { wizard.p->apply_config(GUI::get_app_config(), preset_bundle); + return true; + } else { + return false; } } diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp index a06388396c..40ecf09a1e 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -15,7 +15,6 @@ namespace GUI { class ConfigWizard: public wxDialog { public: - // ConfigWizard(wxWindow *parent, const PresetBundle &bundle); ConfigWizard(wxWindow *parent); ConfigWizard(ConfigWizard &&) = delete; ConfigWizard(const ConfigWizard &) = delete; @@ -23,7 +22,7 @@ public: ConfigWizard &operator=(const ConfigWizard &) = delete; ~ConfigWizard(); - static void run(wxWindow *parent, PresetBundle *preset_bundle); + static bool run(wxWindow *parent, PresetBundle *preset_bundle); private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index d32a609be6..652328aaad 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -136,8 +136,8 @@ struct PageDiameters: ConfigWizardPage struct PageTemperatures: ConfigWizardPage { - wxSpinCtrl *spin_extr; - wxSpinCtrl *spin_bed; + wxSpinCtrlDouble *spin_extr; + wxSpinCtrlDouble *spin_bed; PageTemperatures(ConfigWizard *parent); virtual void apply_custom_config(DynamicPrintConfig &config); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 80e2322875..310860e536 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -353,13 +353,13 @@ void add_debug_menu(wxMenuBar *menu, int event_language_change) //#endif } -void open_config_wizard(PresetBundle *preset_bundle) +bool open_config_wizard(PresetBundle *preset_bundle) { if (g_wxMainFrame == nullptr) { throw std::runtime_error("Main frame not set"); } - ConfigWizard::run(g_wxMainFrame, preset_bundle); + return ConfigWizard::run(g_wxMainFrame, preset_bundle); } void open_preferences_dialog(int event_preferences) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 98a1240919..321c97d367 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -73,7 +73,6 @@ void break_to_debugger(); // Passing the wxWidgets GUI classes instantiated by the Perl part to C++. void set_wxapp(wxApp *app); void set_main_frame(wxFrame *main_frame); -// wxFrame* get_main_frame(); void set_tab_panel(wxNotebook *tab_panel); void set_app_config(AppConfig *app_config); void set_preset_bundle(PresetBundle *preset_bundle); @@ -85,8 +84,8 @@ wxColour* get_sys_label_clr(); void add_debug_menu(wxMenuBar *menu, int event_language_change); -// Opens the first-time configuration wizard -void open_config_wizard(PresetBundle *preset_bundle); +// Opens the first-time configuration wizard, returns true if wizard is finished & accepted. +bool open_config_wizard(PresetBundle *preset_bundle); // Create "Preferences" dialog after selecting menu "Preferences" in Perl part void open_preferences_dialog(int event_preferences); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 40afca144b..66836074e9 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -183,7 +183,6 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) const std::string &variant = config.opt_string("printer_variant"); if (model.empty() || variant.empty()) { return; } is_visible = app_config.get_variant(vendor->id, model, variant); - std::cerr << vendor->id << " / " << model << " / " << variant << ": visible: " << is_visible << std::endl; } const std::vector& Preset::print_options() diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 075eed9afd..4f734b85e3 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -37,14 +37,12 @@ public: PrinterVariant() {} PrinterVariant(const std::string &name) : name(name) {} std::string name; - // bool enabled = true; // TODO: remove these? }; struct PrinterModel { PrinterModel() {} std::string id; std::string name; - // bool enabled = true; std::vector variants; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) @@ -87,7 +85,8 @@ public: bool is_external = false; // System preset is read-only. bool is_system = false; - // Preset is visible, if it is compatible with the active Printer. TODO: fix + // Preset is visible, if it is associated with a printer model / variant that is enabled in the AppConfig + // or if it has no printer model / variant association. // Also the "default" preset is only visible, if it is the only preset in the list. bool is_visible = true; // Has this preset been modified? diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 8645a4f2d3..bd9e35ca80 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -4,7 +4,6 @@ #include "PresetBundle.hpp" #include "BitmapCache.hpp" -#include // XXX #include #include #include @@ -201,10 +200,7 @@ static inline std::string remove_ini_suffix(const std::string &name) // If the "vendor" section is missing, enable all models and variants of the particular vendor. void PresetBundle::load_installed_printers(const AppConfig &config) { - std::cerr << "load_installed_printers()" << std::endl; - for (auto &preset : printers) { - std::cerr << "preset: printer: " << preset.name << std::endl; preset.set_visible_from_appconfig(config); } } @@ -742,7 +738,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla const VendorProfile *vendor_profile = nullptr; if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { boost::filesystem::path fspath(path); - VendorProfile vp(fspath.stem().native()); + VendorProfile vp(fspath.stem().string()); load_vendor_profile(tree, vp); if (vp.name.empty()) throw std::runtime_error(std::string("Vendor Config Bundle is not valid: Missing vendor name key.")); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index ad7f69a2d7..fcf465a290 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -54,8 +54,8 @@ int combochecklist_get_flags(SV *ui) void set_app_config(AppConfig *app_config) %code%{ Slic3r::GUI::set_app_config(app_config); %}; -void open_config_wizard(PresetBundle *preset_bundle) - %code%{ Slic3r::GUI::open_config_wizard(preset_bundle); %}; +bool open_config_wizard(PresetBundle *preset_bundle) + %code%{ RETVAL=Slic3r::GUI::open_config_wizard(preset_bundle); %}; void open_preferences_dialog(int preferences_event) %code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %}; From 57f6601c9d6b506ebc3aacdb580c59d3c90633d2 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 9 Apr 2018 10:41:34 +0200 Subject: [PATCH 008/145] ConfigWizard: Fix logo rendering --- xs/src/slic3r/GUI/ConfigWizard.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 032b5e903b..f13448c374 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -477,10 +477,22 @@ ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) : bullet_white(GUI::from_u8(Slic3r::var("bullet_white.png")), wxBITMAP_TYPE_PNG) { SetMinSize(bg.GetSize()); - Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); wxClientDC dc(this); text_height = dc.GetCharHeight(); + + // Add logo bitmap. + // This could be done in on_paint() along with the index labels, but I've found it tricky + // to get the bitmap rendered well on all platforms with transparent background. + // In some cases it didn't work at all. And so wxStaticBitmap is used here instead, + // because it has all the platform quirks figured out. + auto *sizer = new wxBoxSizer(wxVERTICAL); + auto *logo = new wxStaticBitmap(this, wxID_ANY, bg); + sizer->AddStretchSpacer(); + sizer->Add(logo); + SetSizer(sizer); + + Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); } void ConfigWizardIndex::load_items(ConfigWizardPage *firstpage) @@ -509,12 +521,9 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) }; const auto size = GetClientSize(); - const auto h = size.GetHeight(); - const auto w = size.GetWidth(); - if (h == 0 || w == 0) { return; } + if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } wxPaintDC dc(this); - dc.DrawBitmap(bg, 0, h - bg.GetHeight(), false); const auto bullet_w = bullet_black.GetSize().GetWidth(); const auto bullet_h = bullet_black.GetSize().GetHeight(); From b8a06d728ad523b5fa1542840c02e9dc67e0403e Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 9 Apr 2018 16:24:34 +0200 Subject: [PATCH 009/145] Fixes in 2DBed --- xs/src/slic3r/GUI/2DBed.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/xs/src/slic3r/GUI/2DBed.cpp b/xs/src/slic3r/GUI/2DBed.cpp index c5d68400da..6d788cf340 100644 --- a/xs/src/slic3r/GUI/2DBed.cpp +++ b/xs/src/slic3r/GUI/2DBed.cpp @@ -1,4 +1,4 @@ -#include "2DBed.hpp"; +#include "2DBed.hpp" #include #include "BoundingBox.hpp" @@ -66,7 +66,7 @@ void Bed_2D::repaint() shift.y - (cbb.max.y - GetSize().GetHeight())); // draw bed fill - dc.SetBrush(*new wxBrush(*new wxColour(255, 255, 255), wxSOLID)); + dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxSOLID)); wxPointList pt_list; for (auto pt: m_bed_shape) { @@ -87,7 +87,7 @@ void Bed_2D::repaint() } polylines = intersection_pl(polylines, bed_polygon); - dc.SetPen(*new wxPen(*new wxColour(230, 230, 230), 1, wxSOLID)); + dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxSOLID)); for (auto pl : polylines) { for (size_t i = 0; i < pl.points.size()-1; i++){ @@ -98,8 +98,8 @@ void Bed_2D::repaint() } // draw bed contour - dc.SetPen(*new wxPen(*new wxColour(0, 0, 0), 1, wxSOLID)); - dc.SetBrush(*new wxBrush(*new wxColour(0, 0, 0), wxTRANSPARENT)); + dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID)); + dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxTRANSPARENT)); dc.DrawPolygon(&pt_list, 0, 0); auto origin_px = to_pixels(Pointf(0, 0)); @@ -108,7 +108,7 @@ void Bed_2D::repaint() auto axes_len = 50; auto arrow_len = 6; auto arrow_angle = Geometry::deg2rad(45.0); - dc.SetPen(*new wxPen(*new wxColour(255, 0, 0), 2, wxSOLID)); // red + dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxSOLID)); // red auto x_end = Pointf(origin_px.x + axes_len, origin_px.y); dc.DrawLine(wxPoint(origin_px.x, origin_px.y), wxPoint(x_end.x, x_end.y)); for (auto angle : { -arrow_angle, arrow_angle }){ @@ -118,7 +118,7 @@ void Bed_2D::repaint() dc.DrawLine(wxPoint(x_end.x, x_end.y), wxPoint(end.x, end.y)); } - dc.SetPen(*new wxPen(*new wxColour(0, 255, 0), 2, wxSOLID)); // green + dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxSOLID)); // green auto y_end = Pointf(origin_px.x, origin_px.y - axes_len); dc.DrawLine(wxPoint(origin_px.x, origin_px.y), wxPoint(y_end.x, y_end.y)); for (auto angle : { -arrow_angle, arrow_angle }) { @@ -129,19 +129,23 @@ void Bed_2D::repaint() } // draw origin - dc.SetPen(*new wxPen(*new wxColour(0, 0, 0), 1, wxSOLID)); - dc.SetBrush(*new wxBrush(*new wxColour(0, 0, 0), wxSOLID)); + dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID)); + dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxSOLID)); dc.DrawCircle(origin_px.x, origin_px.y, 3); - dc.SetTextForeground(*new wxColour(0, 0, 0)); - dc.SetFont(*new wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL)); - dc.DrawText("(0,0)", origin_px.x + 1, origin_px.y + 2); + static const auto origin_label = wxString("(0,0)"); + dc.SetTextForeground(wxColour(0, 0, 0)); + dc.SetFont(wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL)); + auto extent = dc.GetTextExtent(origin_label); + const auto origin_label_x = origin_px.x <= cw / 2 ? origin_px.x + 1 : origin_px.x - 1 - extent.GetWidth(); + const auto origin_label_y = origin_px.y <= ch / 2 ? origin_px.y + 1 : origin_px.y - 1 - extent.GetHeight(); + dc.DrawText(origin_label, origin_label_x, origin_label_y); // draw current position if (m_pos!= Pointf(0, 0)) { auto pos_px = to_pixels(m_pos); - dc.SetPen(*new wxPen(*new wxColour(200, 0, 0), 2, wxSOLID)); - dc.SetBrush(*new wxBrush(*new wxColour(200, 0, 0), wxTRANSPARENT)); + dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxSOLID)); + dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxTRANSPARENT)); dc.DrawCircle(pos_px.x, pos_px.y, 5); dc.DrawLine(pos_px.x - 15, pos_px.y, pos_px.x + 15, pos_px.y); From 26511deec0f4b86ee1f7597281f4553fbe91bca2 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 9 Apr 2018 16:39:50 +0200 Subject: [PATCH 010/145] Add '-alpha' suffix to data directory for now --- xs/src/libslic3r/utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 34b9eaa9f1..14736468aa 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -119,7 +119,7 @@ static std::string g_data_dir; void set_data_dir(const std::string &dir) { - g_data_dir = dir; + g_data_dir = dir + "-alpha"; // FIXME: Resolve backcompat problems } const std::string& data_dir() From 32c4cddb91f91a35dd468c3f394911a0b7ad952b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 9 Apr 2018 17:03:37 +0200 Subject: [PATCH 011/145] Ported the AboutDialog to C++, thanks @alexrj for the work. New "configuration" menu over the snapshots, user preferences etc. --- lib/Slic3r/GUI.pm | 8 -- lib/Slic3r/GUI/AboutDialog.pm | 122 -------------------------- lib/Slic3r/GUI/MainFrame.pm | 14 +-- xs/CMakeLists.txt | 6 +- xs/lib/Slic3r/XS.pm | 2 +- xs/src/libslic3r/FileParserError.hpp | 4 + xs/src/slic3r/Config/Snapshot.cpp | 36 ++++++++ xs/src/slic3r/Config/Snapshot.hpp | 7 ++ xs/src/slic3r/Config/Version.cpp | 47 +++++++++- xs/src/slic3r/Config/Version.hpp | 12 ++- xs/src/slic3r/GUI/AboutDialog.cpp | 125 +++++++++++++++++++++++++++ xs/src/slic3r/GUI/AboutDialog.hpp | 36 ++++++++ xs/src/slic3r/GUI/GUI.cpp | 74 +++++++++++----- xs/src/slic3r/GUI/GUI.hpp | 13 +-- xs/xsp/GUI.xsp | 12 +-- 15 files changed, 336 insertions(+), 182 deletions(-) delete mode 100644 lib/Slic3r/GUI/AboutDialog.pm create mode 100644 xs/src/slic3r/GUI/AboutDialog.cpp create mode 100644 xs/src/slic3r/GUI/AboutDialog.hpp diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 4ec388c147..88e4745d1e 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -7,7 +7,6 @@ use File::Basename qw(basename); use FindBin; use List::Util qw(first); use Slic3r::GUI::2DBed; -use Slic3r::GUI::AboutDialog; use Slic3r::GUI::BedShapeDialog; use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::Controller; @@ -191,13 +190,6 @@ sub recreate_GUI{ } } -sub about { - my ($self) = @_; - my $about = Slic3r::GUI::AboutDialog->new(undef); - $about->ShowModal; - $about->Destroy; -} - sub system_info { my ($self) = @_; my $slic3r_info = Slic3r::slic3r_info(format => 'html'); diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm deleted file mode 100644 index 0879ea35b5..0000000000 --- a/lib/Slic3r/GUI/AboutDialog.pm +++ /dev/null @@ -1,122 +0,0 @@ -package Slic3r::GUI::AboutDialog; -use strict; -use warnings; -use utf8; - -use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id); -use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON); -use Wx::Print; -use Wx::Html; -use base 'Wx::Dialog'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 340], wxCAPTION); - - $self->SetBackgroundColour(Wx::wxWHITE); - my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); - $self->SetSizer($hsizer); - - # logo - my $logo = Slic3r::GUI::AboutDialog::Logo->new($self, -1, wxDefaultPosition, wxDefaultSize); - $logo->SetBackgroundColour(Wx::wxWHITE); - $hsizer->Add($logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30); - - my $vsizer = Wx::BoxSizer->new(wxVERTICAL); - $hsizer->Add($vsizer, 1, wxEXPAND, 0); - - # title - my $title = Wx::StaticText->new($self, -1, $Slic3r::FORK_NAME, wxDefaultPosition, wxDefaultSize); - my $title_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - $title_font->SetWeight(wxFONTWEIGHT_BOLD); - $title_font->SetFamily(wxFONTFAMILY_ROMAN); - $title_font->SetPointSize(24); - $title->SetFont($title_font); - $vsizer->Add($title, 0, wxALIGN_LEFT | wxTOP, 30); - - # version - my $version = Wx::StaticText->new($self, -1, "Version $Slic3r::VERSION", wxDefaultPosition, wxDefaultSize); - my $version_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - $version_font->SetPointSize(&Wx::wxMSW ? 9 : 11); - $version->SetFont($version_font); - $vsizer->Add($version, 0, wxALIGN_LEFT | wxBOTTOM, 10); - - # text - my $text = - '' . - '' . - '' . - 'Copyright © 2016 Vojtech Bubnik, Prusa Research.
' . - 'Copyright © 2011-2016 Alessandro Ranellucci.
' . - 'Slic3r is licensed under the ' . - 'GNU Affero General Public License, version 3.' . - '


' . - 'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake and numerous others. ' . - 'Manual by Gary Hodgson. Inspired by the RepRap community.
' . - 'Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. ' . - '
' . - '' . - ''; - my $html = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); - my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - my $size = &Wx::wxMSW ? 8 : 10; - $html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size, $size, $size, $size, $size, $size, $size]); - $html->SetBorders(2); - $html->SetPage($text); - $vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20); - EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked); - - my $buttons = $self->CreateStdDialogButtonSizer(wxOK); - $self->SetEscapeId(wxID_CLOSE); - EVT_BUTTON($self, wxID_CLOSE, sub { - $self->EndModal(wxID_CLOSE); - $self->Close; - }); - $vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); - - EVT_LEFT_DOWN($self, sub { $self->Close }); - EVT_LEFT_DOWN($logo, sub { $self->Close }); - - return $self; -} - -sub link_clicked { - my ($self, $event) = @_; - - Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref); - $event->Skip(0); -} - -package Slic3r::GUI::AboutDialog::Logo; -use Wx qw(:bitmap :dc); -use Wx::Event qw(EVT_PAINT); -use base 'Wx::Panel'; - -sub new { - my $class = shift; - my $self = $class->SUPER::new(@_); - - $self->{logo} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px.png"), wxBITMAP_TYPE_PNG); - $self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight)); - - EVT_PAINT($self, \&repaint); - - return $self; -} - -sub repaint { - my ($self, $event) = @_; - - my $dc = Wx::PaintDC->new($self); - $dc->SetBackgroundMode(wxTRANSPARENT); - - my $size = $self->GetSize; - my $logo_w = $self->{logo}->GetWidth; - my $logo_h = $self->{logo}->GetHeight; - $dc->DrawBitmap($self->{logo}, ($size->GetWidth - $logo_w) / 2, ($size->GetHeight - $logo_h) / 2, 1); - - $event->Skip; -} - -1; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index b2f51b9e1d..31124e432d 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -237,12 +237,6 @@ sub _init_menubar { $self->repair_stl; }, undef, 'wrench.png'); $fileMenu->AppendSeparator(); - # Cmd+, is standard on OS X - what about other operating systems? - $self->_append_menu_item($fileMenu, L("Preferences…\tCtrl+,"), L('Application preferences'), sub { - # Opening the C++ preferences dialog. - Slic3r::GUI::open_preferences_dialog($self->{preferences_event}); - }, wxID_PREFERENCES); - $fileMenu->AppendSeparator(); $self->_append_menu_item($fileMenu, L("&Quit"), L('Quit Slic3r'), sub { $self->Close(0); }, wxID_EXIT); @@ -348,7 +342,7 @@ sub _init_menubar { Wx::LaunchDefaultBrowser('http://github.com/prusa3d/slic3r/issues/new'); }); $self->_append_menu_item($helpMenu, L("&About Slic3r"), L('Show about dialog'), sub { - wxTheApp->about; + Slic3r::GUI::about; }); } @@ -362,11 +356,9 @@ sub _init_menubar { $menubar->Append($self->{object_menu}, L("&Object")) if $self->{object_menu}; $menubar->Append($windowMenu, L("&Window")); $menubar->Append($self->{viewMenu}, L("&View")) if $self->{viewMenu}; - # Add an optional debug menu - # (Select application language from the list of installed languages) - Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event}); + # Add a configuration menu. + Slic3r::GUI::add_config_menu($menubar, $self->{preferences_event}, $self->{lang_ch_event}); $menubar->Append($helpMenu, L("&Help")); - # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing. $self->SetMenuBar($menubar); } } diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 84f169e578..b9a6dbfee2 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -170,6 +170,8 @@ add_library(libslic3r STATIC ) add_library(libslic3r_gui STATIC + ${LIBDIR}/slic3r/GUI/AboutDialog.cpp + ${LIBDIR}/slic3r/GUI/AboutDialog.hpp ${LIBDIR}/slic3r/GUI/AppConfig.cpp ${LIBDIR}/slic3r/GUI/AppConfig.hpp ${LIBDIR}/slic3r/GUI/BitmapCache.cpp @@ -533,13 +535,13 @@ if (SLIC3R_PRUSACONTROL) set(wxWidgets_UseAlienWx 1) if (wxWidgets_UseAlienWx) set(AlienWx_DEBUG 1) - find_package(AlienWx REQUIRED COMPONENTS base core adv) + find_package(AlienWx REQUIRED COMPONENTS base core adv html) include_directories(${AlienWx_INCLUDE_DIRS}) #add_compile_options(${AlienWx_CXX_FLAGS}) add_definitions(${AlienWx_DEFINITIONS}) set(wxWidgets_LIBRARIES ${AlienWx_LIBRARIES}) else () - find_package(wxWidgets REQUIRED COMPONENTS base core adv) + find_package(wxWidgets REQUIRED COMPONENTS base core adv html) include(${wxWidgets_USE_FILE}) endif () add_definitions(-DSLIC3R_GUI -DSLIC3R_PRUS) diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 47a584343e..06eb041dfb 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -12,7 +12,7 @@ our $VERSION = '0.01'; BEGIN { if ($^O eq 'MSWin32') { eval "use Wx"; -# eval "use Wx::Html"; + eval "use Wx::Html"; eval "use Wx::Print"; # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code) } } diff --git a/xs/src/libslic3r/FileParserError.hpp b/xs/src/libslic3r/FileParserError.hpp index 82a6b328e8..3f560fa4f5 100644 --- a/xs/src/libslic3r/FileParserError.hpp +++ b/xs/src/libslic3r/FileParserError.hpp @@ -4,6 +4,7 @@ #include "libslic3r.h" #include +#include #include namespace Slic3r { @@ -15,6 +16,9 @@ public: file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) : std::runtime_error(format_what(msg, file, line)), m_message(msg), m_filename(file), m_line(line) {} + file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) : + std::runtime_error(format_what(msg, file.string(), line)), + m_message(msg), m_filename(file.string()), m_line(line) {} // gcc 3.4.2 complains about lack of throw specifier on compiler // generated dtor ~file_parser_error() throw() {} diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index 559e4c63cb..91f02ab257 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -205,6 +205,22 @@ size_t SnapshotDB::load_db() return m_snapshots.size(); } +void SnapshotDB::update_slic3r_versions(std::vector &index_db) +{ + for (Snapshot &snapshot : m_snapshots) { + for (Snapshot::VendorConfig &vendor_config : snapshot.vendor_configs) { + auto it = std::find_if(index_db.begin(), index_db.end(), [&vendor_config](const Index &idx) { return idx.vendor() == vendor_config.name; }); + if (it != index_db.end()) { + Index::const_iterator it_version = it->find(vendor_config.version); + if (it_version != it->end()) { + vendor_config.min_slic3r_version = it_version->min_slic3r_version; + vendor_config.max_slic3r_version = it_version->max_slic3r_version; + } + } + } + } +} + static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst) { if (! boost::filesystem::is_directory(path_dst) && @@ -303,6 +319,26 @@ boost::filesystem::path SnapshotDB::create_db_dir() return snapshots_dir; } +SnapshotDB& SnapshotDB::singleton() +{ + static SnapshotDB instance; + bool loaded = false; + if (! loaded) { + try { + loaded = true; + // Load the snapshot database. + instance.load_db(); + // Load the vendor specific configuration indices. + std::vector index_db = Index::load_db(); + // Update the min / max slic3r versions compatible with the configurations stored inside the snapshots + // based on the min / max slic3r versions defined by the vendor specific config indices. + instance.update_slic3r_versions(index_db); + } catch (std::exception &ex) { + } + } + return instance; +} + } // namespace Config } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp index 358797bf76..1d02c8650b 100644 --- a/xs/src/slic3r/Config/Snapshot.hpp +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -6,6 +6,7 @@ #include +#include "Version.hpp" #include "../Utils/Semver.hpp" namespace Slic3r { @@ -15,6 +16,8 @@ class AppConfig; namespace GUI { namespace Config { +class Version; + // A snapshot contains: // Slic3r.ini // vendor/ @@ -75,12 +78,16 @@ public: class SnapshotDB { public: + // Initialize the SnapshotDB singleton instance. Load the database if it has not been loaded yet. + static SnapshotDB& singleton(); + typedef std::vector::const_iterator const_iterator; // Load the snapshot database from the snapshots directory. // If the snapshot directory or its parent does not exist yet, it will be created. // Returns a number of snapshots loaded. size_t load_db(); + void update_slic3r_versions(std::vector &index_db); // Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles, // create an index. diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp index 1102f31494..cc961829de 100644 --- a/xs/src/slic3r/Config/Version.cpp +++ b/xs/src/slic3r/Config/Version.cpp @@ -6,6 +6,8 @@ #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Config.hpp" +#include "../../libslic3r/FileParserError.hpp" +#include "../../libslic3r/Utils.hpp" namespace Slic3r { namespace GUI { @@ -62,11 +64,12 @@ inline std::string unquote_version_comment(char *value, char *end, const std::st return svalue; } -size_t Index::load(const std::string &path) +size_t Index::load(const boost::filesystem::path &path) { m_configs.clear(); + m_vendor = path.stem().string(); - boost::nowide::ifstream ifs(path); + boost::nowide::ifstream ifs(path.string()); std::string line; size_t idx_line = 0; Version ver; @@ -96,7 +99,7 @@ size_t Index::load(const std::string &path) if (semver) throw file_parser_error("Key cannot be a semantic version", path, idx_line); // Verify validity of the key / value pair. - std::string svalue = unquote_value(left_trim(++ value), end, path, idx_line); + std::string svalue = unquote_value(left_trim(++ value), end, path.string(), idx_line); if (key == "min_sic3r_version" || key == "max_slic3r_version") { if (! svalue.empty()) semver = Semver::parse(key); @@ -113,13 +116,24 @@ size_t Index::load(const std::string &path) if (! semver) throw file_parser_error("Invalid semantic version", path, idx_line); ver.config_version = *semver; - ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path, idx_line); + ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path.string(), idx_line); m_configs.emplace_back(ver); } + // Sort the configs by their version. + std::sort(m_configs.begin(), m_configs.end(), [](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; }); return m_configs.size(); } +Index::const_iterator Index::find(const Semver &ver) +{ + Version key; + key.config_version = ver; + auto it = std::lower_bound(m_configs.begin(), m_configs.end(), key, + [](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; }); + return (it == m_configs.end() || it->config_version == ver) ? it : m_configs.end(); +} + Index::const_iterator Index::recommended() const { int idx = -1; @@ -131,6 +145,31 @@ Index::const_iterator Index::recommended() const return highest; } +std::vector Index::load_db() +{ + boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); + boost::filesystem::path vendor_dir = data_dir / "vendor"; + + std::vector index_db; + std::string errors_cummulative; + for (auto &dir_entry : boost::filesystem::directory_iterator(vendor_dir)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) { + Index idx; + try { + idx.load(dir_entry.path()); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + continue; + } + index_db.emplace_back(std::move(idx)); + } + + if (! errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); + return index_db; +} + } // namespace Config } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/Config/Version.hpp b/xs/src/slic3r/Config/Version.hpp index 7af1d4b5b7..43512e82f5 100644 --- a/xs/src/slic3r/Config/Version.hpp +++ b/xs/src/slic3r/Config/Version.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../../libslic3r/FileParserError.hpp" #include "../Utils/Semver.hpp" @@ -54,17 +56,25 @@ public: typedef std::vector::const_iterator const_iterator; // Read a config index file in the simple format described in the Index class comment. // Throws Slic3r::file_parser_error and the standard std file access exceptions. - size_t load(const std::string &path); + size_t load(const boost::filesystem::path &path); + + const std::string& vendor() const { return m_vendor; } const_iterator begin() const { return m_configs.begin(); } const_iterator end() const { return m_configs.end(); } + const_iterator find(const Semver &ver); const std::vector& configs() const { return m_configs; } // Finds a recommended config to be installed for the current Slic3r version. // Returns configs().end() if such version does not exist in the index. This shall never happen // if the index is valid. const_iterator recommended() const; + // Load all vendor specific indices. + // Throws Slic3r::file_parser_error and the standard std file access exceptions. + static std::vector load_db(); + private: + std::string m_vendor; std::vector m_configs; }; diff --git a/xs/src/slic3r/GUI/AboutDialog.cpp b/xs/src/slic3r/GUI/AboutDialog.cpp new file mode 100644 index 0000000000..49cfff2bdd --- /dev/null +++ b/xs/src/slic3r/GUI/AboutDialog.cpp @@ -0,0 +1,125 @@ +#include "AboutDialog.hpp" + +#include "../../libslic3r/Utils.hpp" + +namespace Slic3r { +namespace GUI { + +AboutDialogLogo::AboutDialogLogo(wxWindow* parent) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) +{ + this->SetBackgroundColour(*wxWHITE); + this->logo = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG); + this->SetMinSize(this->logo.GetSize()); + + this->Bind(wxEVT_PAINT, &AboutDialogLogo::onRepaint, this); +} + +void AboutDialogLogo::onRepaint(wxEvent &event) +{ + wxPaintDC dc(this); + dc.SetBackgroundMode(wxTRANSPARENT); + + wxSize size = this->GetSize(); + int logo_w = this->logo.GetWidth(); + int logo_h = this->logo.GetHeight(); + dc.DrawBitmap(this->logo, (size.GetWidth() - logo_w)/2, (size.GetHeight() - logo_h)/2, true); + + event.Skip(); +} + +AboutDialog::AboutDialog() + : wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxSize(600, 340), wxCAPTION) +{ + this->SetBackgroundColour(*wxWHITE); + + wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL); + this->SetSizer(hsizer); + + // logo + AboutDialogLogo* logo = new AboutDialogLogo(this); + hsizer->Add(logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30); + + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); + hsizer->Add(vsizer, 1, wxEXPAND, 0); + + // title + { + wxStaticText* title = new wxStaticText(this, wxID_ANY, "Slic3r Prusa Edition", wxDefaultPosition, wxDefaultSize); + wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + title_font.SetWeight(wxFONTWEIGHT_BOLD); + title_font.SetFamily(wxFONTFAMILY_ROMAN); + title_font.SetPointSize(24); + title->SetFont(title_font); + vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 30); + } + + // version + { + std::string version_string = _(L("Version ")) + std::string(SLIC3R_VERSION); + wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize); + wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + #ifdef __WXMSW__ + version_font.SetPointSize(9); + #else + version_font.SetPointSize(11); + #endif + version->SetFont(version_font); + vsizer->Add(version, 0, wxALIGN_LEFT | wxBOTTOM, 10); + } + + // text + wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); + { + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + #ifdef __WXMSW__ + int size[] = {8,8,8,8,8,8,8}; + #else + int size[] = {11,11,11,11,11,11,11}; + #endif + html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html->SetBorders(2); + const char* text = + "" + "" + "" + "Copyright © 2016-2018 Prusa Research.
" + "Copyright © 2011-2017 Alessandro Ranellucci.
" + "Slic3r is licensed under the " + "GNU Affero General Public License, version 3." + "


" + "Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. " + "Manual by Gary Hodgson. Inspired by the RepRap community.
" + "Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. " + "
" + "" + ""; + html->SetPage(text); + vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20); + html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this); + } + + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE); + this->SetEscapeId(wxID_CLOSE); + this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE); + vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); + + this->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this); + logo->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this); + html->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this); +} + +void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event) +{ + wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref()); + event.Skip(false); +} + +void AboutDialog::onCloseDialog(wxEvent &) +{ + this->EndModal(wxID_CLOSE); + this->Close(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/AboutDialog.hpp b/xs/src/slic3r/GUI/AboutDialog.hpp new file mode 100644 index 0000000000..01f7564c50 --- /dev/null +++ b/xs/src/slic3r/GUI/AboutDialog.hpp @@ -0,0 +1,36 @@ +#ifndef slic3r_GUI_AboutDialog_hpp_ +#define slic3r_GUI_AboutDialog_hpp_ + +#include "GUI.hpp" + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +class AboutDialogLogo : public wxPanel +{ +public: + AboutDialogLogo(wxWindow* parent); + +private: + wxBitmap logo; + void onRepaint(wxEvent &event); +}; + +class AboutDialog : public wxDialog +{ +public: + AboutDialog(); + +private: + void onLinkClicked(wxHtmlLinkEvent &event); + void onCloseDialog(wxEvent &); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 3eca4e7075..48d56ff119 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -42,6 +42,7 @@ #include "Tab.hpp" #include "TabIface.hpp" +#include "AboutDialog.hpp" #include "AppConfig.hpp" #include "Utils.hpp" #include "Preferences.hpp" @@ -330,32 +331,56 @@ void get_installed_languages(wxArrayString & names, } } -void add_debug_menu(wxMenuBar *menu, int event_language_change) +enum ConfigMenuIDs { + ConfigMenuWizard, + ConfigMenuSnapshots, + ConfigMenuTakeSnapshot, + ConfigMenuUpdate, + ConfigMenuPreferences, + ConfigMenuLanguage, + ConfigMenuCnt, +}; + +void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change) { -//#if 0 auto local_menu = new wxMenu(); - local_menu->Append(wxWindow::NewControlId(1), _(L("Change Application Language"))); - local_menu->Bind(wxEVT_MENU, [event_language_change](wxEvent&){ - wxArrayString names; - wxArrayLong identifiers; - get_installed_languages(names, identifiers); - if (select_language(names, identifiers)){ - save_language(); - show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!"))); - if (event_language_change > 0) { - wxCommandEvent event(event_language_change); - g_wxApp->ProcessEvent(event); + wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt); + + // Cmd+, is standard on OS X - what about other operating systems? + local_menu->Append(config_id_base + ConfigMenuWizard, _(L("Configuration Wizard\u2026")), _(L("Run configuration wizard"))); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots\u2026")), _(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 + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences\u2026\tCtrl+,")), _(L("Application preferences"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); + local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ + switch (event.GetId() - config_id_base) { + case ConfigMenuPreferences: + { + auto dlg = new PreferencesDialog(g_wxMainFrame, event_preferences_changed); + dlg->ShowModal(); + break; + } + case ConfigMenuLanguage: + { + wxArrayString names; + wxArrayLong identifiers; + get_installed_languages(names, identifiers); + if (select_language(names, identifiers)) { + save_language(); + show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!"))); + if (event_language_change > 0) { + wxCommandEvent event(event_language_change); + g_wxApp->ProcessEvent(event); + } } + break; + } } }); - menu->Append(local_menu, _(L("&Localization"))); -//#endif -} - -void open_preferences_dialog(int event_preferences) -{ - auto dlg = new PreferencesDialog(g_wxMainFrame, event_preferences); - dlg->ShowModal(); + menu->Append(local_menu, _(L("&Configuration"))); } void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) @@ -737,4 +762,11 @@ int get_export_option(wxFileDialog* dlg) return 0; } +void about() +{ + AboutDialog dlg; + dlg.ShowModal(); + dlg.Destroy(); +} + } } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 362b15307b..0cbdf8729e 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -82,10 +82,7 @@ wxApp* get_app(); wxColour* get_modified_label_clr(); wxColour* get_sys_label_clr(); -void add_debug_menu(wxMenuBar *menu, int event_language_change); - -// Create "Preferences" dialog after selecting menu "Preferences" in Perl part -void open_preferences_dialog(int event_preferences); +void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change); // Create a new preset tab (print, filament and printer), void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed); @@ -134,7 +131,11 @@ ConfigOptionsGroup* get_optgroup(); void add_export_option(wxFileDialog* dlg, const std::string& format); int get_export_option(wxFileDialog* dlg); -} -} + +// Display an About dialog +void about(); + +} // namespace GUI +} // namespace Slic3r #endif diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 1376ff164b..c8d76adbab 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -9,6 +9,9 @@ %package{Slic3r::GUI}; +void about() + %code{% Slic3r::GUI::about(); %}; + void disable_screensaver() %code{% Slic3r::GUI::disable_screensaver(); %}; @@ -32,9 +35,9 @@ void set_main_frame(SV *ui) void set_tab_panel(SV *ui) %code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %}; - -void add_debug_menu(SV *ui, int event_language_change) - %code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %}; + +void add_config_menu(SV *ui, int event_preferences_changed, int event_language_change) + %code%{ Slic3r::GUI::add_config_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_preferences_changed, event_language_change); %}; void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) %code%{ Slic3r::GUI::create_preset_tabs(no_controller, event_value_change, event_presets_changed); %}; @@ -54,9 +57,6 @@ int combochecklist_get_flags(SV *ui) void set_app_config(AppConfig *app_config) %code%{ Slic3r::GUI::set_app_config(app_config); %}; -void open_preferences_dialog(int preferences_event) - %code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %}; - void set_preset_bundle(PresetBundle *preset_bundle) %code%{ Slic3r::GUI::set_preset_bundle(preset_bundle); %}; From 0694fad01626578cea4828ba19749f38e8d5e146 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 10 Apr 2018 16:27:42 +0200 Subject: [PATCH 012/145] Initial implementation of the config snapshot dialog. --- xs/CMakeLists.txt | 6 +- xs/src/slic3r/Config/Snapshot.cpp | 8 +- xs/src/slic3r/Config/Snapshot.hpp | 2 +- xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp | 110 +++++++++++++++++++++ xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp | 30 ++++++ xs/src/slic3r/GUI/GUI.cpp | 14 +++ xs/src/slic3r/Utils/Semver.hpp | 16 ++- xs/src/slic3r/Utils/Time.cpp | 44 ++++++--- xs/src/slic3r/Utils/Time.hpp | 5 +- 9 files changed, 213 insertions(+), 22 deletions(-) create mode 100644 xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp create mode 100644 xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index b9a6dbfee2..e01f11b263 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -176,6 +176,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/AppConfig.hpp ${LIBDIR}/slic3r/GUI/BitmapCache.cpp ${LIBDIR}/slic3r/GUI/BitmapCache.hpp + ${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.cpp + ${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.hpp ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp @@ -218,6 +220,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/Utils/OctoPrint.hpp ${LIBDIR}/slic3r/Utils/Bonjour.cpp ${LIBDIR}/slic3r/Utils/Bonjour.hpp + ${LIBDIR}/slic3r/Utils/Time.cpp + ${LIBDIR}/slic3r/Utils/Time.hpp ) add_library(admesh STATIC @@ -408,7 +412,7 @@ if(APPLE) # Ignore undefined symbols of the perl interpreter, they will be found in the caller image. target_link_libraries(XS "-undefined dynamic_lookup") endif() -target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri) +target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver) if(SLIC3R_PROFILE) target_link_libraries(XS Shiny) endif() diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index 91f02ab257..33c7ef82ff 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -241,7 +241,7 @@ static void delete_existing_ini_files(const boost::filesystem::path &path) boost::filesystem::remove(dir_entry.path()); } -const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment) +const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment) { boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir(); @@ -267,8 +267,10 @@ const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot: } // Vendor specific config bundles and installed printers. + boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; + boost::filesystem::create_directory(snapshot_dir); + // Backup the presets. - boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; for (const char *subdir : { "print", "filament", "printer", "vendor" }) copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir); snapshot.save_ini((snapshot_dir / "snapshot.ini").string()); @@ -322,7 +324,7 @@ boost::filesystem::path SnapshotDB::create_db_dir() SnapshotDB& SnapshotDB::singleton() { static SnapshotDB instance; - bool loaded = false; + static bool loaded = false; if (! loaded) { try { loaded = true; diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp index 1d02c8650b..3c77542735 100644 --- a/xs/src/slic3r/Config/Snapshot.hpp +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -91,7 +91,7 @@ public: // Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles, // create an index. - const Snapshot& make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment); + const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment); void restore_snapshot(const std::string &id, AppConfig &app_config); void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config); diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp new file mode 100644 index 0000000000..a4e4d846b6 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -0,0 +1,110 @@ +#include "ConfigSnapshotDialog.hpp" + +#include "../Config/Snapshot.hpp" +#include "../Utils/Time.hpp" + +#include "../../libslic3r/Utils.hpp" + +namespace Slic3r { +namespace GUI { + +static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_even) +{ + // Start by declaring a row with an alternating background color. + std::string text = ""; + text += ""; +// text += _(L("ID:")) + " " + snapshot.id + "
"; + text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "
"; + text += _(L("slic3r version:")) + " " + snapshot.slic3r_version_captured.to_string() + "
"; + if (! snapshot.comment.empty()) + text += _(L("user comment:")) + " " + snapshot.comment + "
"; +// text += "reason: " + snapshot.reason + "
"; + text += "print: " + snapshot.print + "
"; + text += "filaments: " + snapshot.filaments.front() + "
"; + text += "printer: " + snapshot.printer + "
"; + + for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) { + text += "vendor: " + vc.name + ", ver: " + vc.version.to_string() + ", min slic3r ver: " + vc.min_slic3r_version.to_string() + ", max slic3r ver: " + vc.max_slic3r_version.to_string() + "
"; + } + + text += "

Activate

"; + text += ""; + text += ""; + return text; +} + +static std::string generate_html_page(const Config::SnapshotDB &snapshot_db) +{ + std::string text = + "" + "" + ""; + text += ""; + for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) { + const Config::Snapshot &snapshot = snapshot_db.snapshots()[i_row]; + text += generate_html_row(snapshot, i_row & 1); + } + text += + "
" + "
" + "" + ""; + return text; +} + +ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db) + : wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) +{ + this->SetBackgroundColour(*wxWHITE); + + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); + this->SetSizer(vsizer); + + // text + wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); + { + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + #ifdef __WXMSW__ + int size[] = {8,8,8,8,8,8,8}; + #else + int size[] = {11,11,11,11,11,11,11}; + #endif + html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html->SetBorders(2); + std::string text = generate_html_page(snapshot_db); + FILE *file = ::fopen("d:\\temp\\configsnapshotdialog.html", "wt"); + fwrite(text.data(), 1, text.size(), file); + fclose(file); + html->SetPage(text.c_str()); + vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0); + html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this); + } + + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE); + this->SetEscapeId(wxID_CLOSE); + this->Bind(wxEVT_BUTTON, &ConfigSnapshotDialog::onCloseDialog, this, wxID_CLOSE); + vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); + +/* + this->Bind(wxEVT_LEFT_DOWN, &ConfigSnapshotDialog::onCloseDialog, this); + logo->Bind(wxEVT_LEFT_DOWN, &ConfigSnapshotDialog::onCloseDialog, this); + html->Bind(wxEVT_LEFT_DOWN, &ConfigSnapshotDialog::onCloseDialog, this); +*/ +} + +void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event) +{ + wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref()); + event.Skip(false); +} + +void ConfigSnapshotDialog::onCloseDialog(wxEvent &) +{ + this->EndModal(wxID_CLOSE); + this->Close(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp new file mode 100644 index 0000000000..70187ee995 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp @@ -0,0 +1,30 @@ +#ifndef slic3r_GUI_ConfigSnapshotDialog_hpp_ +#define slic3r_GUI_ConfigSnapshotDialog_hpp_ + +#include "GUI.hpp" + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +namespace Config { + class SnapshotDB; +} + +class ConfigSnapshotDialog : public wxDialog +{ +public: + ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db); + +private: + void onLinkClicked(wxHtmlLinkEvent &event); + void onCloseDialog(wxEvent &); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif /* slic3r_GUI_ConfigSnapshotDialog_hpp_ */ diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 48d56ff119..0a163bf285 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -44,10 +44,13 @@ #include "TabIface.hpp" #include "AboutDialog.hpp" #include "AppConfig.hpp" +#include "ConfigSnapshotDialog.hpp" #include "Utils.hpp" #include "Preferences.hpp" #include "PresetBundle.hpp" +#include "../Config/Snapshot.hpp" + namespace Slic3r { namespace GUI { #if __APPLE__ @@ -357,6 +360,17 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ switch (event.GetId() - config_id_base) { + case ConfigMenuTakeSnapshot: + // Take a configuration snapshot. + Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(*g_AppConfig, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, ""); + break; + case ConfigMenuSnapshots: + { + ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton()); + dlg.ShowModal(); + dlg.Destroy(); + break; + } case ConfigMenuPreferences: { auto dlg = new PreferencesDialog(g_wxMainFrame, event_preferences_changed); diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index 7fc3b8033a..8aa8cc53ff 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -46,11 +46,23 @@ public: return Semver(ver); } - Semver(Semver &&other) { *this = std::move(other); } - Semver(const Semver &other) { *this = other; } + Semver(Semver &&other) : ver(other.ver) + { + other.ver.major = other.ver.minor = other.ver.patch = 0; + other.ver.metadata = other.ver.prerelease = nullptr; + } + + Semver(const Semver &other) : ver(other.ver) + { + if (other.ver.metadata != nullptr) + std::strcpy(ver.metadata, other.ver.metadata); + if (other.ver.prerelease != nullptr) + std::strcpy(ver.prerelease, other.ver.prerelease); + } Semver &operator=(Semver &&other) { + ::semver_free(&ver); ver = other.ver; other.ver.major = other.ver.minor = other.ver.patch = 0; other.ver.metadata = other.ver.prerelease = nullptr; diff --git a/xs/src/slic3r/Utils/Time.cpp b/xs/src/slic3r/Utils/Time.cpp index c4123c7bba..a2b2328af6 100644 --- a/xs/src/slic3r/Utils/Time.cpp +++ b/xs/src/slic3r/Utils/Time.cpp @@ -1,13 +1,18 @@ #include "Time.hpp" +#ifdef WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #undef WIN32_LEAN_AND_MEAN +#endif /* WIN32 */ + namespace Slic3r { namespace Utils { time_t parse_time_ISO8601Z(const std::string &sdate) { - int y, M, d, h, m; - float s; - if (sscanf(sdate.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s) != 6) + int y, M, d, h, m, s; + if (sscanf(sdate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &y, &M, &d, &h, &m, &s) != 6) return (time_t)-1; struct tm tms; tms.tm_year = y - 1900; // Year since 1900 @@ -15,7 +20,7 @@ time_t parse_time_ISO8601Z(const std::string &sdate) tms.tm_mday = d; // 1-31 tms.tm_hour = h; // 0-23 tms.tm_min = m; // 0-59 - tms.tm_sec = (int)s; // 0-61 (0-60 in C++11) + tms.tm_sec = s; // 0-61 (0-60 in C++11) return mktime(&tms); } @@ -23,21 +28,34 @@ std::string format_time_ISO8601Z(time_t time) { struct tm tms; #ifdef WIN32 - gmtime_s(time, &tms); + gmtime_s(&tms, &time); #else - gmtime_r(&tms, time); + gmtime_r(&time, &tms); #endif char buf[128]; - sprintf(buf, "%d-%d-%dT%d:%d:%fZ", - tms.tm_year + 1900 - tms.tm_mon + 1 - tms.tm_mday - tms.tm_hour - tms.tm_min + sprintf(buf, "%04d%02d%02dT%02d%02d%02dZ", + tms.tm_year + 1900, + tms.tm_mon + 1, + tms.tm_mday, + tms.tm_hour, + tms.tm_min, tms.tm_sec); return buf; } +std::string format_local_date_time(time_t time) +{ + struct tm tms; +#ifdef WIN32 + localtime_s(&tms, &time); +#else + localtime_r(&time, &tms); +#endif + char buf[80]; + strftime(buf, 80, "%x %X", &tms); + return buf; +} + time_t get_current_time_utc() { #ifdef WIN32 @@ -59,5 +77,3 @@ time_t get_current_time_utc() }; // namespace Utils }; // namespace Slic3r - -#endif /* slic3r_Utils_Time_hpp_ */ diff --git a/xs/src/slic3r/Utils/Time.hpp b/xs/src/slic3r/Utils/Time.hpp index 6b2fbf8930..7b670bd3ee 100644 --- a/xs/src/slic3r/Utils/Time.hpp +++ b/xs/src/slic3r/Utils/Time.hpp @@ -13,8 +13,11 @@ namespace Utils { extern time_t parse_time_ISO8601Z(const std::string &s); extern std::string format_time_ISO8601Z(time_t time); +// Format the date and time from an UTC time according to the active locales and a local time zone. +extern std::string format_local_date_time(time_t time); + // There is no gmtime() on windows. -time_t get_current_time_utc(); +extern time_t get_current_time_utc(); }; // namespace Utils }; // namespace Slic3r From da2878958bfb23ae65b3ee4789844530d35260d9 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 11 Apr 2018 12:21:15 +0200 Subject: [PATCH 013/145] Wizard runs from the new Config menu, snapshots could be rolled back / forward. --- lib/Slic3r/GUI.pm | 5 +- lib/Slic3r/GUI/Controller.pm | 4 +- lib/Slic3r/GUI/MainFrame.pm | 47 ++---------- resources/profiles/PrusaResearch.ini | 2 +- xs/src/slic3r/Config/Snapshot.cpp | 46 ++++++++++-- xs/src/slic3r/Config/Snapshot.hpp | 6 ++ xs/src/slic3r/GUI/AppConfig.hpp | 3 + xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp | 21 ++++-- xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp | 4 ++ xs/src/slic3r/GUI/GUI.cpp | 84 ++++++++++++++++++---- xs/src/slic3r/GUI/GUI.hpp | 10 ++- xs/src/slic3r/GUI/Tab.cpp | 12 ++-- xs/xsp/GUI.xsp | 7 +- 13 files changed, 165 insertions(+), 86 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index bb56fa886d..b28b84df32 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -8,7 +8,6 @@ use FindBin; use List::Util qw(first); use Slic3r::GUI::2DBed; use Slic3r::GUI::BedShapeDialog; -use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::Controller; use Slic3r::GUI::Controller::ManualControlDialog; use Slic3r::GUI::Controller::PrinterPanel; @@ -150,7 +149,7 @@ sub OnInit { # XXX: ? if ($run_wizard) { # Run the config wizard, don't offer the "reset user profile" checkbox. - $self->{mainframe}->config_wizard(1); + Slic3r::GUI::config_wizard(1); } # $self->{preset_updater}->download($self->{preset_bundle}); @@ -207,7 +206,7 @@ sub recreate_GUI{ # before the UI was up and running. $self->CallAfter(sub { # Run the config wizard, don't offer the "reset user profile" checkbox. - $self->{mainframe}->config_wizard(1); + Slic3r::GUI::config_wizard(1); }); } } diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index 6aa7b34cb8..f7d90c7962 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -7,7 +7,7 @@ use strict; use warnings; use utf8; -use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog); +use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog wxBORDER_NONE); use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU); use base qw(Wx::ScrolledWindow Class::Accessor); use List::Util qw(first); @@ -34,7 +34,7 @@ sub new { # button for adding new printer panels { my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), - wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); $btn->SetToolTipString("Add printer…") if $btn->can('SetToolTipString'); diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index e5b27d1dc8..886c8f4192 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -81,7 +81,7 @@ sub new { # declare events EVT_CLOSE($self, sub { my (undef, $event) = @_; - if ($event->CanVeto && !$self->check_unsaved_changes) { + if ($event->CanVeto && !Slic3r::GUI::check_unsaved_changes) { $event->Veto; return; } @@ -313,11 +313,6 @@ sub _init_menubar { # Help menu my $helpMenu = Wx::Menu->new; { - $self->_append_menu_item($helpMenu, L("&Configuration ").$Slic3r::GUI::ConfigWizard::wizard."…", L("Run Configuration ").$Slic3r::GUI::ConfigWizard::wizard, sub { - # Run the config wizard, offer the "reset user profile" checkbox. - $self->config_wizard(0); - }); - $helpMenu->AppendSeparator(); $self->_append_menu_item($helpMenu, L("Prusa 3D Drivers"), L('Open the Prusa3D drivers download page in your browser'), sub { Wx::LaunchDefaultBrowser('http://www.prusa3d.com/drivers/'); }); @@ -554,7 +549,7 @@ sub export_config { sub load_config_file { my ($self, $file) = @_; if (!$file) { - return unless $self->check_unsaved_changes; + return unless Slic3r::GUI::check_unsaved_changes; my $dlg = Wx::FileDialog->new($self, L('Select configuration to load:'), $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, "config.ini", @@ -573,7 +568,7 @@ sub load_config_file { sub export_configbundle { my ($self) = @_; - return unless $self->check_unsaved_changes; + return unless Slic3r::GUI::check_unsaved_changes; # validate current configuration in case it's dirty eval { wxTheApp->{preset_bundle}->full_config->validate; }; Slic3r::GUI::catch_error($self) and return; @@ -597,7 +592,7 @@ sub export_configbundle { # but that behavior was not documented and likely buggy. sub load_configbundle { my ($self, $file, $reset_user_profile) = @_; - return unless $self->check_unsaved_changes; + return unless Slic3r::GUI::check_unsaved_changes; if (!$file) { my $dlg = Wx::FileDialog->new($self, L('Select configuration to load:'), $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, @@ -631,40 +626,6 @@ sub load_config { $self->{plater}->on_config_change($config) if $self->{plater}; } -sub config_wizard { - my ($self, $fresh_start) = @_; - # Exit wizard if there are unsaved changes and the user cancels the action. - return unless $self->check_unsaved_changes; - - # TODO: Offer "reset user profile" ??? - if (Slic3r::GUI::open_config_wizard(wxTheApp->{preset_bundle})) { - # Load the currently selected preset into the GUI, update the preset selection box. - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_current_preset; - } - } -} - -# This is called when closing the application, when loading a config file or when starting the config wizard -# to notify the user whether he is aware that some preset changes will be lost. -sub check_unsaved_changes { - my $self = shift; - - my @dirty = (); - foreach my $tab (values %{$self->{options_tabs}}) { - push @dirty, $tab->title if $tab->current_preset_is_dirty; - } - - if (@dirty) { - my $titles = join ', ', @dirty; - my $confirm = Wx::MessageDialog->new($self, L("You have unsaved changes ").($titles).L(". Discard changes and continue anyway?"), - L('Unsaved Presets'), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - return $confirm->ShowModal == wxID_YES; - } - - return 1; -} - sub select_tab { my ($self, $tab) = @_; $self->{tabpanel}->SetSelection($tab); diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index b654b4039f..cf82855cf4 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.1 +config_version = 0.1.0 # Where to get the updates from? # TODO: proper URL # config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch.ini diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index 33c7ef82ff..9fabbe0136 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -1,5 +1,6 @@ #include "Snapshot.hpp" #include "../GUI/AppConfig.hpp" +#include "../GUI/PresetBundle.hpp" #include "../Utils/Time.hpp" #include @@ -172,6 +173,14 @@ void Snapshot::export_selections(AppConfig &config) const config.set("presets", "printer", printer); } +void Snapshot::export_vendor_configs(AppConfig &config) const +{ + std::map>> vendors; + for (const VendorConfig &vc : vendor_configs) + vendors[vc.name] = vc.models_variants_installed; + config.set_vendors(std::move(vendors)); +} + size_t SnapshotDB::load_db() { boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir(); @@ -199,7 +208,8 @@ size_t SnapshotDB::load_db() } m_snapshots.emplace_back(std::move(snapshot)); } - + // Sort the snapshots by their date/time. + std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; }); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); return m_snapshots.size(); @@ -266,6 +276,30 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: snapshot.filaments.emplace_back(app_config.get("presets", name)); } // Vendor specific config bundles and installed printers. + for (const std::pair>> &vendor : app_config.vendors()) { + Snapshot::VendorConfig cfg; + cfg.name = vendor.first; + cfg.models_variants_installed = vendor.second; + // Read the active config bundle, parse the config version. + PresetBundle bundle; + bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + for (const VendorProfile &vp : bundle.vendors) + if (vp.id == cfg.name) + cfg.version = *Semver::parse(vp.config_version); + // Fill-in the min/max slic3r version from the config index, if possible. + try { + // Load the config index for the vendor. + Index index; + index.load(data_dir / "vendor" / (cfg.name + ".idx")); + auto it = index.find(cfg.version); + if (it != index.end()) { + cfg.min_slic3r_version = it->min_slic3r_version; + cfg.max_slic3r_version = it->max_slic3r_version; + } + } catch (const std::runtime_error &err) { + } + snapshot.vendor_configs.emplace_back(std::move(cfg)); + } boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; boost::filesystem::create_directory(snapshot_dir); @@ -274,6 +308,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: for (const char *subdir : { "print", "filament", "printer", "vendor" }) copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir); snapshot.save_ini((snapshot_dir / "snapshot.ini").string()); + assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured); m_snapshots.emplace_back(std::move(snapshot)); return m_snapshots.back(); } @@ -293,18 +328,15 @@ void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_confi boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir(); boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; - // Remove existing ini files and restore the ini files from the snapshot. for (const char *subdir : { "print", "filament", "printer", "vendor" }) { delete_existing_ini_files(data_dir / subdir); copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir); } - - // Update app_config from the snapshot. + // Update AppConfig with the selections of the print / filament / printer profiles + // and about the installed printer types and variants. snapshot.export_selections(app_config); - - // Store information about the snapshot. - + snapshot.export_vendor_configs(app_config); } boost::filesystem::path SnapshotDB::create_db_dir() diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp index 3c77542735..a7b8a5aa5d 100644 --- a/xs/src/slic3r/Config/Snapshot.hpp +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_GUI_Snapshot_ #define slic3r_GUI_Snapshot_ +#include +#include #include #include @@ -17,6 +19,7 @@ namespace GUI { namespace Config { class Version; +class Index; // A snapshot contains: // Slic3r.ini @@ -42,6 +45,7 @@ public: // Export the print / filament / printer selections to be activated into the AppConfig. void export_selections(AppConfig &config) const; + void export_vendor_configs(AppConfig &config) const; // ID of a snapshot should equal to the name of the snapshot directory. // The ID contains the date/time, reason and comment to be human readable. @@ -70,6 +74,8 @@ public: Semver min_slic3r_version = Semver::zero(); // Maximum Slic3r version compatible with this vendor configuration, or empty. Semver max_slic3r_version = Semver::inf(); + // Which printer models of this vendor were installed, and which variants of the models? + std::map> models_variants_installed; }; // List of vendor configs contained in this snapshot. std::vector vendor_configs; diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index 7aac95fd6b..40b3a12fd6 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -71,6 +71,9 @@ public: bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const; void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable); void set_vendors(const AppConfig &from); + void set_vendors(const std::map>> &vendors) { m_vendors = vendors; m_dirty = true; } + void set_vendors(std::map>> &&vendors) { m_vendors = std::move(vendors); m_dirty = true; } + const std::map>> vendors() const { return m_vendors; } // return recent/skein_directory or recent/config_directory or empty string. std::string get_last_dir() const; diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp index a4e4d846b6..730b97a320 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -26,7 +26,19 @@ static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_ text += "printer: " + snapshot.printer + "
"; for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) { - text += "vendor: " + vc.name + ", ver: " + vc.version.to_string() + ", min slic3r ver: " + vc.min_slic3r_version.to_string() + ", max slic3r ver: " + vc.max_slic3r_version.to_string() + "
"; + text += "vendor: " + vc.name + ", ver: " + vc.version.to_string() + ", min slic3r ver: " + vc.min_slic3r_version.to_string(); + if (vc.max_slic3r_version != Semver::inf()) + text += ", max slic3r ver: " + vc.max_slic3r_version.to_string(); + text += "
"; + for (const std::pair> &model : vc.models_variants_installed) { + text += "model: " + model.first + ", variants: "; + for (const std::string &variant : model.second) { + if (&variant != &*model.second.begin()) + text += ", "; + text += variant; + } + text += "
"; + } } text += "

Activate

"; @@ -43,7 +55,7 @@ static std::string generate_html_page(const Config::SnapshotDB &snapshot_db) ""; text += ""; for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) { - const Config::Snapshot &snapshot = snapshot_db.snapshots()[i_row]; + const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1]; text += generate_html_row(snapshot, i_row & 1); } text += @@ -96,8 +108,9 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event) { - wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref()); - event.Skip(false); + m_snapshot_to_activate = event.GetLinkInfo().GetHref(); + this->EndModal(wxID_CLOSE); + this->Close(); } void ConfigSnapshotDialog::onCloseDialog(wxEvent &) diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp index 70187ee995..0d11096159 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp @@ -19,9 +19,13 @@ class ConfigSnapshotDialog : public wxDialog public: ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db); + const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; } + private: void onLinkClicked(wxHtmlLinkEvent &event); void onCloseDialog(wxEvent &); + + std::string m_snapshot_to_activate; }; } // namespace GUI diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index dcb21f6449..cef56b8929 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -349,9 +349,17 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l { auto local_menu = new wxMenu(); wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt); - + + // A different naming convention is used for the Wizard on Windows vs. OSX & GTK. +#if WIN32 + std::string config_wizard_menu = _(L("Configuration Wizard")); + std::string config_wizard_tooltip = _(L("Run configuration wizard")); +#else + std::string config_wizard_menu = _(L("Configuration Assistant")); + std::string config_wizard_tooltip = _(L("Run configuration Assistant")); +#endif // Cmd+, is standard on OS X - what about other operating systems? - local_menu->Append(config_id_base + ConfigMenuWizard, _(L("Configuration Wizard\u2026")), _(L("Run configuration wizard"))); + local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_menu + "\u2026", config_wizard_tooltip); local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots\u2026")), _(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 + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); @@ -361,21 +369,35 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ switch (event.GetId() - config_id_base) { + case ConfigMenuWizard: + config_wizard(0); + break; case ConfigMenuTakeSnapshot: // Take a configuration snapshot. - Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(*g_AppConfig, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, ""); + if (check_unsaved_changes()) { + wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name"))); + if (dlg.ShowModal() == wxID_OK) + Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot( + *g_AppConfig, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); + } break; case ConfigMenuSnapshots: - { - ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton()); - dlg.ShowModal(); - dlg.Destroy(); + if (check_unsaved_changes()) { + ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton()); + dlg.ShowModal(); + if (! dlg.snapshot_to_activate().empty()) { + Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *g_AppConfig); + g_PresetBundle->load_presets(*g_AppConfig); + // Load the currently selected preset into the GUI, update the preset selection box. + for (Tab *tab : g_tabs_list) + tab->load_current_preset(); + } + } break; - } case ConfigMenuPreferences: { - auto dlg = new PreferencesDialog(g_wxMainFrame, event_preferences_changed); - dlg->ShowModal(); + PreferencesDialog dlg(g_wxMainFrame, event_preferences_changed); + dlg.ShowModal(); break; } case ConfigMenuLanguage: @@ -398,13 +420,45 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l menu->Append(local_menu, _(L("&Configuration"))); } -bool open_config_wizard(PresetBundle *preset_bundle) +// This is called when closing the application, when loading a config file or when starting the config wizard +// to notify the user whether he is aware that some preset changes will be lost. +bool check_unsaved_changes() { - if (g_wxMainFrame == nullptr) { - throw std::runtime_error("Main frame not set"); - } + std::string dirty; + for (Tab *tab : g_tabs_list) + if (tab->current_preset_is_dirty()) + if (dirty.empty()) + dirty = tab->name(); + else + dirty += std::string(", ") + tab->name(); + if (dirty.empty()) + // No changes, the application may close or reload presets. + return true; + // Ask the user. + auto dialog = new wxMessageDialog(g_wxMainFrame, + _(L("You have unsaved changes ")) + dirty + _(L(". Discard changes and continue anyway?")), + _(L("Unsaved Presets")), + wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); + return dialog->ShowModal() == wxID_YES; +} - return ConfigWizard::run(g_wxMainFrame, preset_bundle); +bool config_wizard(bool fresh_start) +{ + if (g_wxMainFrame == nullptr) + throw std::runtime_error("Main frame not set"); + + // Exit wizard if there are unsaved changes and the user cancels the action. + if (! check_unsaved_changes()) + return false; + + // TODO: Offer "reset user profile" ??? + if (! ConfigWizard::run(g_wxMainFrame, g_PresetBundle)) + return false; + + // Load the currently selected preset into the GUI, update the preset selection box. + for (Tab *tab : g_tabs_list) + tab->load_current_preset(); + return true; } void open_preferences_dialog(int event_preferences) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 938eed4988..2a3667eb3e 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -82,13 +82,17 @@ wxApp* get_app(); wxColour* get_modified_label_clr(); wxColour* get_sys_label_clr(); -void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change); +extern void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change); + +// This is called when closing the application, when loading a config file or when starting the config wizard +// to notify the user whether he is aware that some preset changes will be lost. +extern bool check_unsaved_changes(); // Opens the first-time configuration wizard, returns true if wizard is finished & accepted. -bool open_config_wizard(PresetBundle *preset_bundle); +extern bool config_wizard(bool fresh_start); // Create "Preferences" dialog after selecting menu "Preferences" in Perl part -void open_preferences_dialog(int event_preferences); +extern void open_preferences_dialog(int event_preferences); // Create a new preset tab (print, filament and printer), void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index babff7d801..940987536c 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -2061,9 +2061,9 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox presets.Add(preset.name); } - auto dlg = new wxMultiChoiceDialog(parent, - _(L("Select the printers this profile is compatible with.")), - _(L("Compatible printers")), presets); + wxMultiChoiceDialog dlg(parent, + _(L("Select the printers this profile is compatible with.")), + _(L("Compatible printers")), presets); // # Collect and set indices of printers marked as compatible. wxArrayInt selections; auto *compatible_printers = dynamic_cast(m_config->option("compatible_printers")); @@ -2075,12 +2075,12 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox selections.Add(idx); break; } - dlg->SetSelections(selections); + dlg.SetSelections(selections); std::vector value; // Show the dialog. - if (dlg->ShowModal() == wxID_OK) { + if (dlg.ShowModal() == wxID_OK) { selections.Clear(); - selections = dlg->GetSelections(); + selections = dlg.GetSelections(); for (auto idx : selections) value.push_back(presets[idx].ToStdString()); if (value.empty()) { diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 8dba13cafa..964f350b9e 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -57,8 +57,11 @@ int combochecklist_get_flags(SV *ui) void set_app_config(AppConfig *app_config) %code%{ Slic3r::GUI::set_app_config(app_config); %}; -bool open_config_wizard(PresetBundle *preset_bundle) - %code%{ RETVAL=Slic3r::GUI::open_config_wizard(preset_bundle); %}; +bool check_unsaved_changes() + %code%{ RETVAL=Slic3r::GUI::check_unsaved_changes(); %}; + +bool config_wizard(int fresh_start) + %code%{ RETVAL=Slic3r::GUI::config_wizard(fresh_start != 0); %}; void open_preferences_dialog(int preferences_event) %code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %}; From aaa8f133c00baba3ccff4e2115436254483a1fe0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 11 Apr 2018 15:17:41 +0200 Subject: [PATCH 014/145] Fixed parsing of the config index. --- resources/profiles/PrusaResearch.idx | 13 +++++++ xs/src/semver/semver.c | 2 +- xs/src/slic3r/Config/Snapshot.cpp | 24 ++++++++++--- xs/src/slic3r/Config/Version.cpp | 54 ++++++++++++++++++---------- xs/src/slic3r/Utils/Semver.hpp | 10 +++--- 5 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 resources/profiles/PrusaResearch.idx diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx new file mode 100644 index 0000000000..28f17f10ad --- /dev/null +++ b/resources/profiles/PrusaResearch.idx @@ -0,0 +1,13 @@ +# This is an example configuration version index. +# The index contains version numbers +min_slic3r_version =1.39.0 +0.2.0-alpha "some test comment" +max_slic3r_version= 1.39.4 +0.1.0 another test comment + +# some empty lines + +# version without a comment +min_slic3r_version = 1.0.0 +max_slic3r_version = 1.1.0 +0.0.1 diff --git a/xs/src/semver/semver.c b/xs/src/semver/semver.c index 29bc1868d3..599217f89f 100644 --- a/xs/src/semver/semver.c +++ b/xs/src/semver/semver.c @@ -200,7 +200,7 @@ semver_parse_version (const char *str, semver_t *ver) { slice = next + 1; } - return 0; + return (index == 3) ? 0 : -1; } static int diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index 9fabbe0136..eeb5b6ac58 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -57,6 +57,7 @@ void Snapshot::load_ini(const std::string &path) // Parse snapshot.ini std::string group_name_vendor = "Vendor:"; std::string key_filament = "filament"; + std::string key_prefix_model = "model_"; for (auto §ion : tree) { if (section.first == "snapshot") { // Parse the common section. @@ -107,10 +108,7 @@ void Snapshot::load_ini(const std::string &path) VendorConfig vc; vc.name = section.first.substr(group_name_vendor.size()); for (auto &kvp : section.second) { - if (boost::starts_with(kvp.first, "model_")) { - //model:MK2S = 0.4;xxx - //model:MK3 = 0.4;xxx - } else if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") { + if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") { // Version of the vendor specific config bundle bundled with this snapshot. auto semver = Semver::parse(kvp.second.data()); if (! semver) @@ -121,8 +119,16 @@ void Snapshot::load_ini(const std::string &path) vc.min_slic3r_version = *semver; else vc.max_slic3r_version = *semver; - } + } else if (boost::starts_with(kvp.first, key_prefix_model) && kvp.first.size() > key_prefix_model.size()) { + // Parse the printer variants installed for the current model. + auto &set_variants = vc.models_variants_installed[kvp.first.substr(key_prefix_model.size())]; + std::vector variants; + if (unescape_strings_cstyle(kvp.second.data(), variants)) + for (auto &variant : variants) + set_variants.insert(std::move(variant)); + } } + this->vendor_configs.emplace_back(std::move(vc)); } } } @@ -155,6 +161,14 @@ void Snapshot::save_ini(const std::string &path) c << "version = " << vc.version.to_string() << std::endl; c << "min_slic3r_version = " << vc.min_slic3r_version.to_string() << std::endl; c << "max_slic3r_version = " << vc.max_slic3r_version.to_string() << std::endl; + // Export installed printer models and their variants. + for (const auto &model : vc.models_variants_installed) { + if (model.second.size() == 0) + continue; + const std::vector variants(model.second.begin(), model.second.end()); + const auto escaped = escape_strings_cstyle(variants); + c << "model_" << model.first << " = " << escaped << std::endl; + } } c.close(); } diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp index cc961829de..95b3caf1a3 100644 --- a/xs/src/slic3r/Config/Version.cpp +++ b/xs/src/slic3r/Config/Version.cpp @@ -40,12 +40,13 @@ inline std::string unquote_value(char *value, char *end, const std::string &path if (value == end) { // Empty string is a valid string. } else if (*value == '"') { - if (++ value < -- end || *end != '"') + if (++ value > -- end || *end != '"') throw file_parser_error("String not enquoted correctly", path, idx_line); *end = 0; if (! unescape_string_cstyle(value, svalue)) throw file_parser_error("Invalid escape sequence inside a quoted string", path, idx_line); - } + } else + svalue.assign(value, end); return svalue; } @@ -55,12 +56,13 @@ inline std::string unquote_version_comment(char *value, char *end, const std::st if (value == end) { // Empty string is a valid string. } else if (*value == '"') { - if (++ value < -- end || *end != '"') + if (++ value > -- end || *end != '"') throw file_parser_error("Version comment not enquoted correctly", path, idx_line); *end = 0; if (! unescape_string_cstyle(value, svalue)) throw file_parser_error("Invalid escape sequence inside a quoted version comment", path, idx_line); - } + } else + svalue.assign(value, end); return svalue; } @@ -77,41 +79,55 @@ size_t Index::load(const boost::filesystem::path &path) ++ idx_line; // Skip the initial white spaces. char *key = left_trim(const_cast(line.data())); + if (*key == '#') + // Skip a comment line. + continue; // Right trim the line. char *end = right_trim(key); + if (key == end) + // Skip an empty line. + continue; // Keyword may only contain alphanumeric characters. Semantic version may in addition contain "+.-". char *key_end = key; - bool maybe_semver = false; - for (;; ++ key) { - if (strchr("+.-", *key) != nullptr) - maybe_semver = true; - else if (! std::isalnum(*key)) - break; + bool maybe_semver = true; + for (; *key_end != 0; ++ key_end) { + if (std::isalnum(*key_end) || strchr("+.-", *key_end) != nullptr) { + // It may be a semver. + } else if (*key_end == '_') { + // Cannot be a semver, but it may be a key. + maybe_semver = false; + } else + // End of semver or keyword. + break; } - if (*key != 0 && *key != ' ' && *key != '\t' && *key != '=') + if (*key_end != 0 && *key_end != ' ' && *key_end != '\t' && *key_end != '=') throw file_parser_error("Invalid keyword or semantic version", path, idx_line); - *key_end = 0; + char *value = left_trim(key_end); + bool key_value_pair = *value == '='; + if (key_value_pair) + value = left_trim(value + 1); + *key_end = 0; boost::optional semver; if (maybe_semver) semver = Semver::parse(key); - char *value = left_trim(key_end); - if (*value == '=') { + if (key_value_pair) { if (semver) - throw file_parser_error("Key cannot be a semantic version", path, idx_line); + throw file_parser_error("Key cannot be a semantic version", path, idx_line);\ // Verify validity of the key / value pair. - std::string svalue = unquote_value(left_trim(++ value), end, path.string(), idx_line); - if (key == "min_sic3r_version" || key == "max_slic3r_version") { + std::string svalue = unquote_value(value, end, path.string(), idx_line); + if (strcmp(key, "min_slic3r_version") == 0 || strcmp(key, "max_slic3r_version") == 0) { if (! svalue.empty()) - semver = Semver::parse(key); + semver = Semver::parse(svalue); if (! semver) throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line); - if (key == "min_sic3r_version") + if (strcmp(key, "min_slic3r_version") == 0) ver.min_slic3r_version = *semver; else ver.max_slic3r_version = *semver; } else { // Ignore unknown keys, as there may come new keys in the future. } + continue; } if (! semver) throw file_parser_error("Invalid semantic version", path, idx_line); diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index 8aa8cc53ff..bd8e9b7582 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -55,9 +55,9 @@ public: Semver(const Semver &other) : ver(other.ver) { if (other.ver.metadata != nullptr) - std::strcpy(ver.metadata, other.ver.metadata); + ver.metadata = strdup(other.ver.metadata); if (other.ver.prerelease != nullptr) - std::strcpy(ver.prerelease, other.ver.prerelease); + ver.prerelease = strdup(other.ver.prerelease); } Semver &operator=(Semver &&other) @@ -73,8 +73,10 @@ public: { ::semver_free(&ver); ver = other.ver; - if (other.ver.metadata != nullptr) { std::strcpy(ver.metadata, other.ver.metadata); } - if (other.ver.prerelease != nullptr) { std::strcpy(ver.prerelease, other.ver.prerelease); } + if (other.ver.metadata != nullptr) + ver.metadata = strdup(other.ver.metadata); + if (other.ver.prerelease != nullptr) + ver.prerelease = strdup(other.ver.prerelease); return *this; } From 31ea03feb0af7376a35aa3b3428684b1e59d4a15 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 11 Apr 2018 13:12:08 +0200 Subject: [PATCH 015/145] ConfigWizard: Make bundle installation more intelligent, fixes --- xs/src/slic3r/Config/Snapshot.cpp | 4 +- xs/src/slic3r/GUI/AppConfig.cpp | 19 +++++++ xs/src/slic3r/GUI/AppConfig.hpp | 3 ++ xs/src/slic3r/GUI/ConfigWizard.cpp | 9 ++-- xs/src/slic3r/GUI/Preset.cpp | 75 ++++++++++++++++++++++++++- xs/src/slic3r/GUI/Preset.hpp | 11 +++- xs/src/slic3r/GUI/PresetBundle.cpp | 43 +-------------- xs/src/slic3r/Utils/PresetUpdater.cpp | 32 +++++++----- xs/src/slic3r/Utils/PresetUpdater.hpp | 4 +- xs/src/slic3r/Utils/Semver.hpp | 19 ++++++- xs/xsp/GUI.xsp | 8 ++- 11 files changed, 159 insertions(+), 68 deletions(-) diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index eeb5b6ac58..b6c3565764 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -275,7 +275,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: // Snapshot header. snapshot.time_captured = Slic3r::Utils::get_current_time_utc(); snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured); - snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); + snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); // XXX: have Semver Slic3r version snapshot.comment = comment; snapshot.reason = reason; // Active presets at the time of the snapshot. @@ -299,7 +299,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); for (const VendorProfile &vp : bundle.vendors) if (vp.id == cfg.name) - cfg.version = *Semver::parse(vp.config_version); + cfg.version = vp.config_version; // Fill-in the min/max slic3r version from the config index, if possible. try { // Load the config index for the vendor. diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 9e5ce5f1bc..ee77f877ae 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -228,9 +229,27 @@ bool AppConfig::version_check_enabled() const bool AppConfig::slic3r_update_avail() const { + // FIXME: Update with Semver + // TODO: probably need to move semver to libslic3r return version_check_enabled() && get("version_online") != SLIC3R_VERSION; } +Semver AppConfig::get_slic3r_version() const +{ + // TODO: move to Semver c-tor (???) + auto res = Semver::parse(get("version")); + if (! res) { + throw std::runtime_error(std::string("Could not parse Slic3r version string in application config.")); + } else { + return *res; + } +} + +void AppConfig::set_slic3r_version(const Semver &version) +{ + set("version", version.to_string()); +} + std::string AppConfig::config_path() { return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index 40b3a12fd6..cac2759f1a 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -6,6 +6,7 @@ #include #include "libslic3r/Config.hpp" +#include "slic3r/Utils/Semver.hpp" namespace Slic3r { @@ -91,6 +92,8 @@ public: // Whether the Slic3r version available online differs from this one bool version_check_enabled() const; bool slic3r_update_avail() const; + Semver get_slic3r_version() const; + void set_slic3r_version(const Semver &version); // Get the default config path from Slic3r::data_dir(). static std::string config_path(); diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index f13448c374..0c2a9dd596 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -15,6 +15,7 @@ #include "libslic3r/Utils.hpp" #include "PresetBundle.hpp" #include "GUI.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" namespace fs = boost::filesystem; @@ -699,12 +700,8 @@ ConfigWizard::~ConfigWizard() {} bool ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) { - const auto profiles_dir = fs::path(resources_dir()) / "profiles"; - for (fs::directory_iterator it(profiles_dir); it != fs::directory_iterator(); ++it) { - if (it->path().extension() == ".ini") { - PresetBundle::install_vendor_configbundle(it->path()); - } - } + // FIXME: this should be done always at app startup + PresetUpdater::init_vendors(); ConfigWizard wizard(parent); if (wizard.ShowModal() == wxID_OK) { diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 66836074e9..39bfbd3981 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -24,14 +25,16 @@ #include "../../libslic3r/Utils.hpp" #include "../../libslic3r/PlaceholderParser.hpp" +using boost::property_tree::ptree; + namespace Slic3r { -ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree) +ConfigFileType guess_config_file_type(const ptree &tree) { size_t app_config = 0; size_t bundle = 0; size_t config = 0; - for (const boost::property_tree::ptree::value_type &v : tree) { + for (const ptree::value_type &v : tree) { if (v.second.empty()) { if (v.first == "background_processing" || v.first == "last_output_path" || @@ -59,6 +62,74 @@ ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree) (bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG; } + +VendorProfile VendorProfile::from_ini(const boost::filesystem::path &path, bool load_all) +{ + ptree tree; + boost::filesystem::ifstream ifs(path); + boost::property_tree::read_ini(ifs, tree); + return VendorProfile::from_ini(tree, path, load_all); +} + +VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all) +{ + static const std::string printer_model_key = "printer_model:"; + const std::string id = path.stem().string(); + VendorProfile res(id); + + auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator + { + auto res = tree.find(key); + if (res == tree.not_found()) { + throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); + } + return res; + }; + + const auto &vendor_section = get_or_throw(tree, "vendor")->second; + res.name = get_or_throw(vendor_section, "name")->second.data(); + + auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data(); + auto config_version = Semver::parse(config_version_str); + if (! config_version) { + throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); + } else { + res.config_version = std::move(*config_version); + } + + auto config_update_url = vendor_section.find("config_update_url"); + if (config_update_url != vendor_section.not_found()) { + res.config_update_url = config_update_url->second.data(); + } + + if (! load_all) { + return res; + } + + for (auto §ion : tree) { + if (boost::starts_with(section.first, printer_model_key)) { + VendorProfile::PrinterModel model; + model.id = section.first.substr(printer_model_key.size()); + model.name = section.second.get("name", model.id); + section.second.get("variants", ""); + std::vector variants; + if (Slic3r::unescape_strings_cstyle(section.second.get("variants", ""), variants)) { + for (const std::string &variant_name : variants) { + if (model.variant(variant_name) == nullptr) + model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); + } + } else { + // Log error? // XXX + } + if (! model.id.empty() && ! model.variants.empty()) + res.models.push_back(std::move(model)); + } + } + + return res; +} + + // Suffix to be added to a modified preset name in the combo box. static std::string g_suffix_modified = " (modified)"; const std::string& Preset::suffix_modified() diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 4f734b85e3..d6ccfd4505 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -3,8 +3,12 @@ #include +#include +#include + #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/PrintConfig.hpp" +#include "slic3r/Utils/Semver.hpp" class wxBitmap; class wxChoice; @@ -30,7 +34,7 @@ class VendorProfile public: std::string name; std::string id; - std::string config_version; + Semver config_version; std::string config_update_url; struct PrinterVariant { @@ -54,7 +58,10 @@ public: }; std::vector models; - VendorProfile(std::string id) : id(std::move(id)) {} + VendorProfile(std::string id) : id(std::move(id)), config_version(0, 0, 0) {} + + static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true); + static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true); size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; } diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index bd9e35ca80..ad27bf8c68 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -105,7 +105,7 @@ void PresetBundle::setup_directories() std::initializer_list paths = { data_dir, data_dir / "vendor", - data_dir / "cache", // TODO: rename as vendor-cache? (Check usage elsewhere!) + data_dir / "cache", #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. data_dir / "presets", @@ -672,42 +672,6 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) flatten_configbundle_hierarchy(tree, "printer"); } -static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorProfile &vendor_profile) -{ - const std::string printer_model_key = "printer_model:"; - for (auto §ion : tree) { - if (section.first == "vendor") { - // Load the names of the active presets. - for (auto &kvp : section.second) { - if (kvp.first == "name") - vendor_profile.name = kvp.second.data(); - else if (kvp.first == "id") - vendor_profile.id = kvp.second.data(); - else if (kvp.first == "config_version") - vendor_profile.config_version = kvp.second.data(); - else if (kvp.first == "config_update_url") - vendor_profile.config_update_url = kvp.second.data(); - } - } else if (boost::starts_with(section.first, printer_model_key)) { - VendorProfile::PrinterModel model; - model.id = section.first.substr(printer_model_key.size()); - model.name = section.second.get("name", model.id); - section.second.get("variants", ""); - std::vector variants; - if (Slic3r::unescape_strings_cstyle(section.second.get("variants", ""), variants)) { - for (const std::string &variant_name : variants) { - if (model.variant(variant_name) == nullptr) - model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); - } - } else { - // Log error? - } - if (! model.id.empty() && ! model.variants.empty()) - vendor_profile.models.push_back(std::move(model)); - } - } -} - // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. void PresetBundle::install_vendor_configbundle(const std::string &src_path0) @@ -738,10 +702,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla const VendorProfile *vendor_profile = nullptr; if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { boost::filesystem::path fspath(path); - VendorProfile vp(fspath.stem().string()); - load_vendor_profile(tree, vp); - if (vp.name.empty()) - throw std::runtime_error(std::string("Vendor Config Bundle is not valid: Missing vendor name key.")); + auto vp = VendorProfile::from_ini(tree, fspath.stem().string()); if (vp.num_variants() == 0) return 0; vendor_profile = &(*this->vendors.insert(vp).first); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 040d326b5c..a16a6d8894 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -1,9 +1,8 @@ #include "PresetUpdater.hpp" -#include // XXX #include #include -#include +#include #include #include @@ -22,6 +21,7 @@ namespace Slic3r { // TODO: proper URL +// TODO: Actually, use index static const std::string SLIC3R_VERSION_URL = "https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/e9187c3e15ceaf1a90f29b7c43cf3ccc746140f0/slic3rPE.version"; enum { SLIC3R_VERSION_BODY_MAX = 256, @@ -52,8 +52,6 @@ PresetUpdater::priv::priv(int event) : void PresetUpdater::priv::download(const std::set vendors) const { - std::cerr << "PresetUpdater::priv::download()" << std::endl; - if (!version_check) { return; } // Download current Slic3r version @@ -64,7 +62,6 @@ void PresetUpdater::priv::download(const std::set vendors) const }) .on_complete([&](std::string body, unsigned http_status) { boost::trim(body); - std::cerr << "Got version: " << http_status << ", body: \"" << body << '"' << std::endl; wxCommandEvent* evt = new wxCommandEvent(version_online_event); evt->SetString(body); GUI::get_app()->QueueEvent(evt); @@ -74,25 +71,19 @@ void PresetUpdater::priv::download(const std::set vendors) const if (!preset_update) { return; } // Donwload vendor preset bundles - std::cerr << "Bundle vendors: " << vendors.size() << std::endl; for (const auto &vendor : vendors) { - std::cerr << "vendor: " << vendor.name << std::endl; - std::cerr << " URL: " << vendor.config_update_url << std::endl; - if (cancel) { return; } // TODO: Proper caching auto target_path = cache_path / vendor.id; target_path += ".ini"; - std::cerr << "target_path: " << target_path << std::endl; Http::get(vendor.config_update_url) .on_progress([this](Http::Progress, bool &cancel) { cancel = this->cancel; }) .on_complete([&](std::string body, unsigned http_status) { - std::cerr << "Got ini: " << http_status << ", body: " << body.size() << std::endl; fs::fstream file(target_path, std::ios::out | std::ios::binary | std::ios::trunc); file.write(body.c_str(), body.size()); }) @@ -121,7 +112,6 @@ PresetUpdater::~PresetUpdater() void PresetUpdater::download(PresetBundle *preset_bundle) { - std::cerr << "PresetUpdater::download()" << std::endl; // Copy the whole vendors data for use in the background thread // Unfortunatelly as of C++11, it needs to be copied again @@ -133,5 +123,23 @@ void PresetUpdater::download(PresetBundle *preset_bundle) })); } +void PresetUpdater::init_vendors() +{ + const auto vendors_rources = fs::path(resources_dir()) / "profiles"; + const auto vendors_data = fs::path(Slic3r::data_dir()) / "vendor"; + + for (fs::directory_iterator it(vendors_rources); it != fs::directory_iterator(); ++it) { + if (it->path().extension() == ".ini") { + auto vp = VendorProfile::from_ini(it->path(), false); + const auto path_in_data = vendors_data / it->path().filename(); + + if (! fs::exists(path_in_data) || VendorProfile::from_ini(path_in_data, false).config_version < vp.config_version) { + // FIXME: update vendor bundle properly when snapshotting is ready + PresetBundle::install_vendor_configbundle(it->path()); + } + } + } +} + } diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp index aafe9569b7..b10c61784b 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -19,7 +19,9 @@ public: PresetUpdater &operator=(const PresetUpdater &) = delete; ~PresetUpdater(); - void download(PresetBundle *preset_bundle); + void download(PresetBundle *preset_bundle); // XXX + + static void init_vendors(); private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index bd8e9b7582..2ac2ba7002 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -10,6 +11,7 @@ namespace Slic3r { +// FIXME:: operators=: leak, return class Semver { @@ -18,9 +20,22 @@ public: struct Minor { const int i; Minor(int i) : i(i) {} }; struct Patch { const int i; Patch(int i) : i(i) {} }; + Semver(int major, int minor, int patch, + boost::optional metadata = boost::none, + boost::optional prerelease = boost::none) + { + ver.major = major; + ver.minor = minor; + ver.patch = patch; + ver.metadata = metadata ? std::strcpy(ver.metadata, metadata->c_str()) : nullptr; + ver.prerelease = prerelease ? std::strcpy(ver.prerelease, prerelease->c_str()) : nullptr; + } + + // TODO: throwing ctor ??? + static boost::optional parse(const std::string &str) { - semver_t ver; + semver_t ver = semver_zero(); if (::semver_parse(str.c_str(), &ver) == 0) { return Semver(ver); } else { @@ -121,6 +136,8 @@ private: semver_t ver; Semver(semver_t ver) : ver(ver) {} + + static semver_t semver_zero() { return { 0, 0, 0, nullptr, nullptr }; } }; diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 964f350b9e..46e4ace83f 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -61,7 +61,13 @@ bool check_unsaved_changes() %code%{ RETVAL=Slic3r::GUI::check_unsaved_changes(); %}; bool config_wizard(int fresh_start) - %code%{ RETVAL=Slic3r::GUI::config_wizard(fresh_start != 0); %}; + %code%{ + try { + RETVAL = Slic3r::GUI::config_wizard(fresh_start != 0); + } catch (std::exception& e) { + croak("%s\n", e.what()); + } + %}; void open_preferences_dialog(int preferences_event) %code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %}; From 12b3132b1a55459b550f144b70b7ae548014c751 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 11 Apr 2018 15:20:38 +0200 Subject: [PATCH 016/145] Perform init_vendors at startup --- lib/Slic3r/GUI.pm | 2 ++ xs/src/slic3r/GUI/ConfigWizard.cpp | 3 --- xs/xsp/Utils_PresetUpdater.xsp | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index b28b84df32..2082728cc4 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -101,6 +101,8 @@ sub OnInit { $self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->save; + Slic3r::PresetUpdater::init_vendors(); + # my $version_check = $self->{app_config}->get('version_check'); $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT, $self->{app_config}); my $slic3r_update = $self->{app_config}->slic3r_update_avail; diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 0c2a9dd596..52a896704d 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -700,9 +700,6 @@ ConfigWizard::~ConfigWizard() {} bool ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) { - // FIXME: this should be done always at app startup - PresetUpdater::init_vendors(); - ConfigWizard wizard(parent); if (wizard.ShowModal() == wxID_OK) { wizard.p->apply_config(GUI::get_app_config(), preset_bundle); diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp index 666379f02e..1cb9f1c39e 100644 --- a/xs/xsp/Utils_PresetUpdater.xsp +++ b/xs/xsp/Utils_PresetUpdater.xsp @@ -8,4 +8,5 @@ %name{Slic3r::PresetUpdater} class PresetUpdater { PresetUpdater(int version_online_event, AppConfig *app_config); void download(PresetBundle* preset_bundle); + static void init_vendors(); }; From b030791384a312d8368941fb7bc01c65e1fd6dea Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 11 Apr 2018 17:07:27 +0200 Subject: [PATCH 017/145] Semver fixes, misc fixes --- resources/profiles/BarBaz.ini | 2 +- resources/profiles/Foobar.ini | 2 +- xs/src/semver/semver.c | 18 ++++++++++++++++++ xs/src/semver/semver.h | 3 +++ xs/src/slic3r/GUI/AboutDialog.cpp | 2 +- xs/src/slic3r/GUI/GUI.cpp | 8 ++++---- xs/src/slic3r/Utils/Semver.hpp | 30 +++++------------------------- xs/src/slic3r/Utils/Time.cpp | 3 ++- 8 files changed, 35 insertions(+), 33 deletions(-) diff --git a/resources/profiles/BarBaz.ini b/resources/profiles/BarBaz.ini index 83bb156844..7c8cd3a6bb 100644 --- a/resources/profiles/BarBaz.ini +++ b/resources/profiles/BarBaz.ini @@ -5,7 +5,7 @@ name = Bar Baz # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.1 +config_version = 0.1.0 # Where to get the updates from? config_update_url = https://example.com diff --git a/resources/profiles/Foobar.ini b/resources/profiles/Foobar.ini index 21681398aa..571aa8bb8b 100644 --- a/resources/profiles/Foobar.ini +++ b/resources/profiles/Foobar.ini @@ -5,7 +5,7 @@ name = Foo Bar # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.1 +config_version = 0.1.0 # Where to get the updates from? config_update_url = https://example.com diff --git a/xs/src/semver/semver.c b/xs/src/semver/semver.c index 599217f89f..3e4a30e3a0 100644 --- a/xs/src/semver/semver.c +++ b/xs/src/semver/semver.c @@ -615,3 +615,21 @@ semver_numeric (semver_t *x) { return num; } + +static char *semver_strdup(const char *src) { + if (src == NULL) return NULL; + size_t len = strlen(src) + 1; + char *res = malloc(len); + return res != NULL ? (char *) memcpy(res, src, len) : NULL; +} + +semver_t +semver_copy(const semver_t *ver) { + semver_t res = *ver; + if (ver->metadata != NULL) { + res.metadata = strdup(ver->metadata); + } + if (ver->prerelease != NULL) { + res.prerelease = strdup(ver->prerelease); + } +} \ No newline at end of file diff --git a/xs/src/semver/semver.h b/xs/src/semver/semver.h index 1b48670ca3..7251f51e3a 100644 --- a/xs/src/semver/semver.h +++ b/xs/src/semver/semver.h @@ -98,6 +98,9 @@ semver_is_valid (const char *s); int semver_clean (char *s); +semver_t +semver_copy(const semver_t *ver); + #ifdef __cplusplus } #endif diff --git a/xs/src/slic3r/GUI/AboutDialog.cpp b/xs/src/slic3r/GUI/AboutDialog.cpp index 49cfff2bdd..664bbd1bbd 100644 --- a/xs/src/slic3r/GUI/AboutDialog.cpp +++ b/xs/src/slic3r/GUI/AboutDialog.cpp @@ -56,7 +56,7 @@ AboutDialog::AboutDialog() // version { - std::string version_string = _(L("Version ")) + std::string(SLIC3R_VERSION); + auto version_string = _(L("Version ")) + std::string(SLIC3R_VERSION); wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize); wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); #ifdef __WXMSW__ diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index cef56b8929..d70b47840b 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -352,11 +352,11 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l // A different naming convention is used for the Wizard on Windows vs. OSX & GTK. #if WIN32 - std::string config_wizard_menu = _(L("Configuration Wizard")); - std::string config_wizard_tooltip = _(L("Run configuration wizard")); + auto config_wizard_menu = _(L("Configuration Wizard")); + auto config_wizard_tooltip = _(L("Run configuration wizard")); #else - std::string config_wizard_menu = _(L("Configuration Assistant")); - std::string config_wizard_tooltip = _(L("Run configuration Assistant")); + auto config_wizard_menu = _(L("Configuration Assistant")); + auto config_wizard_tooltip = _(L("Run configuration Assistant")); #endif // Cmd+, is standard on OS X - what about other operating systems? local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_menu + "\u2026", config_wizard_tooltip); diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index 2ac2ba7002..a1f4a92e86 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -43,11 +43,7 @@ public: } } - static const Semver zero() - { - static semver_t ver = { 0, 0, 0, nullptr, nullptr }; - return Semver(ver); - } + static const Semver zero() { return Semver(semver_zero()); } static const Semver inf() { @@ -61,37 +57,21 @@ public: return Semver(ver); } - Semver(Semver &&other) : ver(other.ver) - { - other.ver.major = other.ver.minor = other.ver.patch = 0; - other.ver.metadata = other.ver.prerelease = nullptr; - } - - Semver(const Semver &other) : ver(other.ver) - { - if (other.ver.metadata != nullptr) - ver.metadata = strdup(other.ver.metadata); - if (other.ver.prerelease != nullptr) - ver.prerelease = strdup(other.ver.prerelease); - } + Semver(Semver &&other) : ver(other.ver) { other.ver = semver_zero(); } + Semver(const Semver &other) : ver(::semver_copy(&other.ver)) {} Semver &operator=(Semver &&other) { ::semver_free(&ver); ver = other.ver; - other.ver.major = other.ver.minor = other.ver.patch = 0; - other.ver.metadata = other.ver.prerelease = nullptr; + other.ver = semver_zero(); return *this; } Semver &operator=(const Semver &other) { ::semver_free(&ver); - ver = other.ver; - if (other.ver.metadata != nullptr) - ver.metadata = strdup(other.ver.metadata); - if (other.ver.prerelease != nullptr) - ver.prerelease = strdup(other.ver.prerelease); + ver = ::semver_copy(&other.ver); return *this; } diff --git a/xs/src/slic3r/Utils/Time.cpp b/xs/src/slic3r/Utils/Time.cpp index a2b2328af6..f38c4b4074 100644 --- a/xs/src/slic3r/Utils/Time.cpp +++ b/xs/src/slic3r/Utils/Time.cpp @@ -71,7 +71,8 @@ time_t get_current_time_utc() tm.tm_isdst = -1; return mktime(&tm); #else - return gmtime(); + const time_t current_local = time(nullptr); + return mktime(gmtime(¤t_local)); #endif } From 9ab38f416d20d0ca8fc33c08d80e2be07fc878ff Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 12 Apr 2018 11:24:03 +0200 Subject: [PATCH 018/145] Improvement of the snapshot dialog, fixed storing of the snapshot "reason" field. --- xs/src/slic3r/Config/Snapshot.cpp | 22 ++++++++++++++- xs/src/slic3r/Config/Snapshot.hpp | 2 ++ xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp | 31 +++++++++++++++++----- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index eeb5b6ac58..c50021e2ee 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -133,6 +133,21 @@ void Snapshot::load_ini(const std::string &path) } } +static std::string reason_string(const Snapshot::Reason reason) +{ + switch (reason) { + case Snapshot::SNAPSHOT_UPGRADE: + return "upgrade"; + case Snapshot::SNAPSHOT_DOWNGRADE: + return "downgrade"; + case Snapshot::SNAPSHOT_USER: + return "user"; + case Snapshot::SNAPSHOT_UNKNOWN: + default: + return "unknown"; + } +} + void Snapshot::save_ini(const std::string &path) { boost::nowide::ofstream c; @@ -145,7 +160,7 @@ void Snapshot::save_ini(const std::string &path) c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl; c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl; c << "comment = " << this->comment << std::endl; - c << "reason = " << this->reason << std::endl; + c << "reason = " << reason_string(this->reason) << std::endl; // Export the active presets at the time of the snapshot. c << std::endl << "[presets]" << std::endl; @@ -294,6 +309,11 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: Snapshot::VendorConfig cfg; cfg.name = vendor.first; cfg.models_variants_installed = vendor.second; + for (auto it = cfg.models_variants_installed.begin(); it != cfg.models_variants_installed.end();) + if (it->second.empty()) + cfg.models_variants_installed.erase(it ++); + else + ++ it; // Read the active config bundle, parse the config version. PresetBundle bundle; bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp index a7b8a5aa5d..8f27027a4d 100644 --- a/xs/src/slic3r/Config/Snapshot.hpp +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -57,6 +57,8 @@ public: std::string comment; Reason reason; + std::string format_reason() const; + // Active presets at the time of the snapshot. std::string print; std::vector filaments; diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 730b97a320..5bc8b1012f 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -8,6 +8,21 @@ namespace Slic3r { namespace GUI { +static std::string format_reason(const Config::Snapshot::Reason reason) +{ + switch (reason) { + case Config::Snapshot::SNAPSHOT_UPGRADE: + return std::string(_(L("Upgrade"))); + case Config::Snapshot::SNAPSHOT_DOWNGRADE: + return std::string(_(L("Downgrade"))); + case Config::Snapshot::SNAPSHOT_USER: + return std::string(_(L("User"))); + case Config::Snapshot::SNAPSHOT_UNKNOWN: + default: + return std::string(_(L("Unknown"))); + } +} + static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_even) { // Start by declaring a row with an alternating background color. @@ -15,11 +30,15 @@ static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_ text += row_even ? "#FFFFFF" : "#C0C0C0"; text += "\">"; text += ""; text += ""; text += ""; return text; } -static std::string generate_html_page(const Config::SnapshotDB &snapshot_db) +static std::string generate_html_page(const Config::SnapshotDB &snapshot_db, const std::string &on_snapshot) { std::string text = "" @@ -75,7 +79,7 @@ static std::string generate_html_page(const Config::SnapshotDB &snapshot_db) text += "
"; -// text += _(L("ID:")) + " " + snapshot.id + "
"; - text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "
"; - text += _(L("slic3r version:")) + " " + snapshot.slic3r_version_captured.to_string() + "
"; + // Format the row header. + text += std::string("") + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason); if (! snapshot.comment.empty()) - text += _(L("user comment:")) + " " + snapshot.comment + "
"; + text += " (" + snapshot.comment + ")"; + text += "

"; + // End of row header. +// text += _(L("ID:")) + " " + snapshot.id + "
"; + // text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "
"; + text += _(L("slic3r version:")) + " " + snapshot.slic3r_version_captured.to_string() + "
"; // text += "reason: " + snapshot.reason + "
"; text += "print: " + snapshot.print + "
"; text += "filaments: " + snapshot.filaments.front() + "
"; @@ -79,9 +98,9 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db { wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); #ifdef __WXMSW__ - int size[] = {8,8,8,8,8,8,8}; + int size[] = {8,8,8,8,11,11,11}; #else - int size[] = {11,11,11,11,11,11,11}; + int size[] = {11,11,11,11,14,14,14}; #endif html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); html->SetBorders(2); From b49b59cbb25ee8903837e34af1da1f647ef62c84 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 12 Apr 2018 20:04:48 +0200 Subject: [PATCH 019/145] Configuration update application at startup --- lib/Slic3r/GUI.pm | 18 +- xs/src/semver/semver.c | 3 +- xs/src/slic3r/Config/Version.cpp | 7 +- xs/src/slic3r/Config/Version.hpp | 2 +- xs/src/slic3r/GUI/AppConfig.hpp | 9 +- xs/src/slic3r/GUI/ConfigWizard.cpp | 63 +++++-- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 12 +- xs/src/slic3r/GUI/Preset.cpp | 7 + xs/src/slic3r/GUI/Preset.hpp | 3 +- xs/src/slic3r/GUI/PresetBundle.cpp | 3 +- xs/src/slic3r/Utils/PresetUpdater.cpp | 208 +++++++++++++++++++-- xs/src/slic3r/Utils/PresetUpdater.hpp | 2 +- xs/src/slic3r/Utils/Semver.hpp | 3 +- xs/xsp/Utils_PresetUpdater.xsp | 2 +- 14 files changed, 288 insertions(+), 54 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 2082728cc4..b592c4289b 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -101,11 +101,14 @@ sub OnInit { $self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->save; - Slic3r::PresetUpdater::init_vendors(); - # my $version_check = $self->{app_config}->get('version_check'); $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT, $self->{app_config}); - my $slic3r_update = $self->{app_config}->slic3r_update_avail; + eval { $self->{preset_updater}->config_update() }; + if ($@) { + warn $@ . "\n"; + fatal_error(undef, $@); + } + # my $slic3r_update = $self->{app_config}->slic3r_update_avail; Slic3r::GUI::set_app_config($self->{app_config}); Slic3r::GUI::load_language(); @@ -123,6 +126,7 @@ sub OnInit { Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); # application frame + print STDERR "Creating main frame...\n"; Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->new); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. @@ -145,9 +149,9 @@ sub OnInit { # before the UI was up and running. $self->CallAfter(sub { # XXX: recreate_GUI ??? - if ($slic3r_update) { - # TODO - } + # if ($slic3r_update) { + # # TODO + # } # XXX: ? if ($run_wizard) { # Run the config wizard, don't offer the "reset user profile" checkbox. @@ -159,6 +163,7 @@ sub OnInit { # The following event is emited by the C++ menu implementation of application language change. EVT_COMMAND($self, -1, $LANGUAGE_CHANGE_EVENT, sub{ + print STDERR "LANGUAGE_CHANGE_EVENT\n"; $self->recreate_GUI; }); @@ -179,6 +184,7 @@ sub OnInit { } sub recreate_GUI{ + print STDERR "recreate_GUI\n"; my ($self) = @_; my $topwindow = $self->GetTopWindow(); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( diff --git a/xs/src/semver/semver.c b/xs/src/semver/semver.c index 3e4a30e3a0..0285fe40ef 100644 --- a/xs/src/semver/semver.c +++ b/xs/src/semver/semver.c @@ -632,4 +632,5 @@ semver_copy(const semver_t *ver) { if (ver->prerelease != NULL) { res.prerelease = strdup(ver->prerelease); } -} \ No newline at end of file + return res; +} diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp index 95b3caf1a3..5430e569c4 100644 --- a/xs/src/slic3r/Config/Version.cpp +++ b/xs/src/slic3r/Config/Version.cpp @@ -141,7 +141,7 @@ size_t Index::load(const boost::filesystem::path &path) return m_configs.size(); } -Index::const_iterator Index::find(const Semver &ver) +Index::const_iterator Index::find(const Semver &ver) const { Version key; key.config_version = ver; @@ -163,12 +163,11 @@ Index::const_iterator Index::recommended() const std::vector Index::load_db() { - boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); - boost::filesystem::path vendor_dir = data_dir / "vendor"; + boost::filesystem::path cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "cache"; std::vector index_db; std::string errors_cummulative; - for (auto &dir_entry : boost::filesystem::directory_iterator(vendor_dir)) + for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir)) if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) { Index idx; try { diff --git a/xs/src/slic3r/Config/Version.hpp b/xs/src/slic3r/Config/Version.hpp index 43512e82f5..c010a1748b 100644 --- a/xs/src/slic3r/Config/Version.hpp +++ b/xs/src/slic3r/Config/Version.hpp @@ -62,7 +62,7 @@ public: const_iterator begin() const { return m_configs.begin(); } const_iterator end() const { return m_configs.end(); } - const_iterator find(const Semver &ver); + const_iterator find(const Semver &ver) const; const std::vector& configs() const { return m_configs; } // Finds a recommended config to be installed for the current Slic3r version. // Returns configs().end() if such version does not exist in the index. This shall never happen diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index cac2759f1a..ffda083ec3 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -69,12 +69,13 @@ public: void clear_section(const std::string §ion) { m_storage[section].clear(); } + typedef std::map>> VendorMap; bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const; void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable); void set_vendors(const AppConfig &from); - void set_vendors(const std::map>> &vendors) { m_vendors = vendors; m_dirty = true; } - void set_vendors(std::map>> &&vendors) { m_vendors = std::move(vendors); m_dirty = true; } - const std::map>> vendors() const { return m_vendors; } + void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; } + void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; } + const VendorMap& vendors() const { return m_vendors; } // return recent/skein_directory or recent/config_directory or empty string. std::string get_last_dir() const; @@ -105,7 +106,7 @@ private: // Map of section, name -> value std::map> m_storage; // Map of enabled vendors / models / variants - std::map>> m_vendors; + VendorMap m_vendors; // Has any value been modified since the config.ini has been last saved or loaded? bool m_dirty; }; diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 52a896704d..f353ab7f7e 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -1,8 +1,8 @@ #include "ConfigWizard_private.hpp" +#include // XXX #include #include -#include #include #include @@ -17,7 +17,6 @@ #include "GUI.hpp" #include "slic3r/Utils/PresetUpdater.hpp" -namespace fs = boost::filesystem; namespace Slic3r { namespace GUI { @@ -62,25 +61,25 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, cons auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); namefont.SetWeight(wxFONTWEIGHT_BOLD); - for (auto model = models.cbegin(); model != models.cend(); ++model) { + for (const auto &model : models) { auto *panel = new wxPanel(this); auto *sizer = new wxBoxSizer(wxVERTICAL); panel->SetSizer(sizer); - auto *title = new wxStaticText(panel, wxID_ANY, model->name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + auto *title = new wxStaticText(panel, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); title->SetFont(namefont); sizer->Add(title, 0, wxBOTTOM, 3); - auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model->id); + auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model.id); wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG); auto *bitmap_widget = new wxStaticBitmap(panel, wxID_ANY, bitmap); sizer->Add(bitmap_widget, 0, wxBOTTOM, 3); sizer->AddSpacer(20); - const auto model_id = model->id; + const auto model_id = model.id; - for (const auto &variant : model->variants) { + for (const auto &variant : model.variants) { const auto variant_name = variant.name; auto *cbox = new wxCheckBox(panel, wxID_ANY, wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle")))); bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant_name); @@ -177,16 +176,18 @@ PageWelcome::PageWelcome(ConfigWizard *parent) : { append_text(_(L("Hello, welcome to Slic3r Prusa Edition! TODO: This text."))); - const PresetBundle &bundle = wizard_p()->bundle_vendors; - const auto &vendors = bundle.vendors; - const auto vendor_prusa = std::find(vendors.cbegin(), vendors.cend(), VendorProfile("PrusaResearch")); + // const PresetBundle &bundle = wizard_p()->bundle_vendors; + // const auto &vendors = bundle.vendors; + const auto &vendors = wizard_p()->vendors; + // const auto vendor_prusa = std::find(vendors.cbegin(), vendors.cend(), VendorProfile("PrusaResearch")); + const auto vendor_prusa = vendors.find("PrusaResearch"); if (vendor_prusa != vendors.cend()) { - const auto &models = vendor_prusa->models; + const auto &models = vendor_prusa->second.models; AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; - printer_picker = new PrinterPicker(this, *vendor_prusa, appconfig_vendors); + printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors); printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); this->on_variant_checked(); @@ -248,14 +249,17 @@ PageVendors::PageVendors(ConfigWizard *parent) : { append_text(_(L("Pick another vendor supported by Slic3r PE:"))); - const PresetBundle &bundle = wizard_p()->bundle_vendors; + // const PresetBundle &bundle = wizard_p()->bundle_vendors; + // const auto &vendors = wizard_p()->vendors; auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); boldfont.SetWeight(wxFONTWEIGHT_BOLD); AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; wxArrayString choices_vendors; - for (const auto &vendor : bundle.vendors) { + // for (const auto &vendor : vendors) { + for (const auto vendor_pair : wizard_p()->vendors) { + const auto &vendor = vendor_pair.second; if (vendor.id == "PrusaResearch") { continue; } auto *picker = new PrinterPicker(this, vendor, appconfig_vendors); @@ -549,9 +553,25 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) void ConfigWizard::priv::load_vendors() { const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor"; + const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles"; + + // Load vendors from the "vendors" directory in datadir for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) { if (it->path().extension() == ".ini") { - bundle_vendors.load_configbundle(it->path().string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + auto vp = VendorProfile::from_ini(it->path()); + vendors[vp.id] = std::move(vp); + } + } + + // Additionally load up vendors from the application resources directory, but only those not seen in the datadir + for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) { + if (it->path().extension() == ".ini") { + const auto id = it->path().stem().string(); + if (vendors.find(id) == vendors.end()) { + auto vp = VendorProfile::from_ini(it->path()); + vendors_rsrc[vp.id] = it->path(); + vendors[vp.id] = std::move(vp); + } } } @@ -624,6 +644,19 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese const bool is_custom_setup = page_welcome->page_next() == page_firmware; if (! is_custom_setup) { + const auto enabled_vendors = appconfig_vendors.vendors(); + for (const auto &vendor_rsrc : vendors_rsrc) { + const auto vendor = enabled_vendors.find(vendor_rsrc.first); + if (vendor == enabled_vendors.end()) { continue; } + + size_t size_sum = 0; + for (const auto &model : vendor->second) { size_sum += model.second.size(); } + if (size_sum == 0) { continue; } + + // This vendor needs to be installed + PresetBundle::install_vendor_configbundle(vendor_rsrc.second); + } + app_config->set_vendors(appconfig_vendors); app_config->set("version_check", page_update->version_check ? "1" : "0"); app_config->set("preset_update", page_update->preset_update ? "1" : "0"); diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index 652328aaad..137b276b85 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -4,6 +4,9 @@ #include "ConfigWizard.hpp" #include +#include +#include +#include #include #include @@ -13,13 +16,16 @@ #include "libslic3r/PrintConfig.hpp" #include "AppConfig.hpp" -#include "PresetBundle.hpp" +#include "Preset.hpp" +// #include "PresetBundle.hpp" #include "BedShapeDialog.hpp" +namespace fs = boost::filesystem; namespace Slic3r { namespace GUI { +// typedef std::unordered_map VendorMap; enum { CONTENT_WIDTH = 500, @@ -168,7 +174,9 @@ struct ConfigWizard::priv { ConfigWizard *q; AppConfig appconfig_vendors; - PresetBundle bundle_vendors; + // PresetBundle bundle_vendors; + std::unordered_map vendors; + std::unordered_map vendors_rsrc; std::unique_ptr custom_config; wxBoxSizer *topsizer = nullptr; diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 39bfbd3981..26f0ff5947 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -5,6 +5,8 @@ #include "AppConfig.hpp" #include +#include +#include #include #include #include @@ -75,6 +77,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem { static const std::string printer_model_key = "printer_model:"; const std::string id = path.stem().string(); + + if (! boost::filesystem::exists(path)) { + throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); + } + VendorProfile res(id); auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index d6ccfd4505..8855bf1e78 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -58,7 +58,8 @@ public: }; std::vector models; - VendorProfile(std::string id) : id(std::move(id)), config_version(0, 0, 0) {} + VendorProfile() {} + VendorProfile(std::string id) : id(std::move(id)) {} static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true); static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index ad27bf8c68..d1d66df60a 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -701,8 +701,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla const VendorProfile *vendor_profile = nullptr; if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { - boost::filesystem::path fspath(path); - auto vp = VendorProfile::from_ini(tree, fspath.stem().string()); + auto vp = VendorProfile::from_ini(tree, path); if (vp.num_variants() == 0) return 0; vendor_profile = &(*this->vendors.insert(vp).first); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index a16a6d8894..28b9773213 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -1,21 +1,32 @@ #include "PresetUpdater.hpp" +#include // XXX +#include #include +#include #include #include #include #include #include +#include #include "libslic3r/Utils.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/Utils/Http.hpp" -#include "slic3r/Utils/Semver.hpp" +#include "slic3r/Config/Version.hpp" +#include "slic3r/Config/Snapshot.hpp" namespace fs = boost::filesystem; +using Slic3r::GUI::Config::Index; +using Slic3r::GUI::Config::Version; +using Slic3r::GUI::Config::Snapshot; +using Slic3r::GUI::Config::SnapshotDB; +// XXX: Prevent incomplete file downloads: download a tmp file, then move +// Delete incomplete ones on startup. namespace Slic3r { @@ -27,26 +38,55 @@ enum { SLIC3R_VERSION_BODY_MAX = 256, }; + +struct Update +{ + fs::path source; + fs::path target; + Version version; + + Update(const fs::path &source, fs::path &&target, const Version &version) : + source(source), + target(std::move(target)), + version(version) + {} + + std::string name() { return source.stem().string(); } +}; + +typedef std::vector Updates; + + struct PresetUpdater::priv { int version_online_event; + AppConfig *app_config; bool version_check; bool preset_update; fs::path cache_path; + fs::path rsrc_path; + fs::path vendor_path; + bool cancel; std::thread thread; - priv(int event); + priv(int event, AppConfig *app_config); void download(const std::set vendors) const; + + void check_install_indices() const; + Updates config_update() const; }; -PresetUpdater::priv::priv(int event) : +PresetUpdater::priv::priv(int event, AppConfig *app_config) : version_online_event(event), + app_config(app_config), version_check(false), preset_update(false), cache_path(fs::path(Slic3r::data_dir()) / "cache"), + rsrc_path(fs::path(resources_dir()) / "profiles"), + vendor_path(fs::path(Slic3r::data_dir()) / "vendor"), cancel(false) {} @@ -91,11 +131,119 @@ void PresetUpdater::priv::download(const std::set vendors) const } } +void PresetUpdater::priv::check_install_indices() const +{ + for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) { + const auto &path = it->path(); + if (path.extension() == ".idx") { + const auto path_in_cache = cache_path / path.filename(); + + // TODO: compare versions + if (! fs::exists(path_in_cache)) { + fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists); + } + } + } +} + +Updates PresetUpdater::priv::config_update() const +{ + priv::check_install_indices(); + const auto index_db = Index::load_db(); // TODO: Keep in Snapshots singleton? + + Updates updates; + + for (const auto idx : index_db) { + const auto bundle_path = vendor_path / (idx.vendor() + ".ini"); + + // If the bundle doesn't exist at all, update from resources + // if (! fs::exists(bundle_path)) { + // auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini"); + + // // Otherwise load it and check for chached updates + // const auto rsrc_vp = VendorProfile::from_ini(path_in_rsrc, false); + + // const auto rsrc_ver = idx.find(rsrc_vp.config_version); + // if (rsrc_ver == idx.end()) { + // // TODO: throw + // } + + // if (fs::exists(path_in_rsrc)) { + // updates.emplace_back(bundle_path, std::move(path_in_rsrc), *rsrc_ver); + // } else { + // // XXX: ??? + // } + + // continue; + // } + + if (! fs::exists(bundle_path)) { + continue; + } + + // Perform a basic load and check the version + const auto vp = VendorProfile::from_ini(bundle_path, false); + + const auto ver_current = idx.find(vp.config_version); + if (ver_current == idx.end()) { + // TODO: throw + } + + const auto recommended = idx.recommended(); + if (recommended == idx.end()) { + // TODO: throw + } + + if (! ver_current->is_current_slic3r_supported()) { + + // TODO: Downgrade situation + + } else if (recommended->config_version > ver_current->config_version) { + // Config bundle update situation + + auto path_in_cache = cache_path / (idx.vendor() + ".ini"); + const auto cached_vp = VendorProfile::from_ini(path_in_cache, false); + if (cached_vp.config_version == recommended->config_version) { + updates.emplace_back(bundle_path, std::move(path_in_cache), *ver_current); + } else { + // XXX: ??? + } + } + } + + // Check for bundles that don't have an index + // for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) { + // if (it->path().extension() == ".ini") { + // const auto &path = it->path(); + // const auto vendor_id = path.stem().string(); + + // const auto needle = std::find_if(index_db.begin(), index_db.end(), [&vendor_id](const Index &idx) { + // return idx.vendor() == vendor_id; + // }); + // if (needle != index_db.end()) { + // continue; + // } + + // auto vp = VendorProfile::from_ini(path, false); + // auto path_in_data = vendor_path / path.filename(); + + // if (! fs::exists(path_in_data)) { + // Version version; + // version.config_version = vp.config_version; + // updates.emplace_back(path, std::move(path_in_data), version); + // } + // } + // } + + return updates; +} + + PresetUpdater::PresetUpdater(int version_online_event, AppConfig *app_config) : - p(new priv(version_online_event)) + p(new priv(version_online_event, app_config)) { p->preset_update = app_config->get("preset_update") == "1"; - // preset_update implies version_check: + // preset_update implies version_check: // XXX: not any more p->version_check = p->preset_update || app_config->get("version_check") == "1"; } @@ -123,19 +271,49 @@ void PresetUpdater::download(PresetBundle *preset_bundle) })); } -void PresetUpdater::init_vendors() +void PresetUpdater::config_update() { - const auto vendors_rources = fs::path(resources_dir()) / "profiles"; - const auto vendors_data = fs::path(Slic3r::data_dir()) / "vendor"; + const auto updates = p->config_update(); + if (updates.size() > 0) { + const auto msg = _(L("Configuration update is available. Would you like to install it?")); - for (fs::directory_iterator it(vendors_rources); it != fs::directory_iterator(); ++it) { - if (it->path().extension() == ".ini") { - auto vp = VendorProfile::from_ini(it->path(), false); - const auto path_in_data = vendors_data / it->path().filename(); + auto ext_msg = _(L( + "Note that a full configuration snapshot will be created first. It can then be restored at any time " + "should there be a problem with the new version.\n\n" + "Updated configuration bundles:\n" + )); - if (! fs::exists(path_in_data) || VendorProfile::from_ini(path_in_data, false).config_version < vp.config_version) { - // FIXME: update vendor bundle properly when snapshotting is ready - PresetBundle::install_vendor_configbundle(it->path()); + for (const auto &update : updates) { + ext_msg += update.target.stem().string() + " " + update.version.config_version.to_string(); + if (! update.version.comment.empty()) { + ext_msg += std::string(" (") + update.version.comment + ")"; + } + ext_msg += "\n"; + } + + wxMessageDialog dlg(NULL, msg, _(L("Configuration update")), wxYES_NO|wxCENTRE); + dlg.SetExtendedMessage(ext_msg); + const auto res = dlg.ShowModal(); + std::cerr << "After modal" << std::endl; + if (res == wxID_YES) { + // User gave clearance, updates are go + + // TODO: Comment? + SnapshotDB::singleton().take_snapshot(*p->app_config, Snapshot::SNAPSHOT_UPGRADE, ""); + + for (const auto &update : updates) { + fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); + + PresetBundle bundle; + bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + + auto preset_remover = [](const Preset &preset) { + fs::remove(preset.file); + }; + + for (const auto &preset : bundle.prints) { preset_remover(preset); } + for (const auto &preset : bundle.filaments) { preset_remover(preset); } + for (const auto &preset : bundle.printers) { preset_remover(preset); } } } } diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp index b10c61784b..8fd6e45289 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -21,7 +21,7 @@ public: void download(PresetBundle *preset_bundle); // XXX - static void init_vendors(); + void config_update(); private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index a1f4a92e86..2c27ce9826 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -11,7 +11,6 @@ namespace Slic3r { -// FIXME:: operators=: leak, return class Semver { @@ -20,6 +19,8 @@ public: struct Minor { const int i; Minor(int i) : i(i) {} }; struct Patch { const int i; Patch(int i) : i(i) {} }; + Semver() : ver(semver_zero()) {} + Semver(int major, int minor, int patch, boost::optional metadata = boost::none, boost::optional prerelease = boost::none) diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp index 1cb9f1c39e..4c1a637e49 100644 --- a/xs/xsp/Utils_PresetUpdater.xsp +++ b/xs/xsp/Utils_PresetUpdater.xsp @@ -8,5 +8,5 @@ %name{Slic3r::PresetUpdater} class PresetUpdater { PresetUpdater(int version_online_event, AppConfig *app_config); void download(PresetBundle* preset_bundle); - static void init_vendors(); + void config_update(); }; From 82890ec815080898c601222b8e370d4475a3af2e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 13 Apr 2018 14:49:33 +0200 Subject: [PATCH 020/145] Removed some obsolete Perl binding. Added Version Index "version" method. Implemented automatic selection of default_print_profile and default_filament_profile, when the print / filament profiles are not compatible with the selected printer profile. Fixed selection of a printer profile, if the currently selected printer profile becomes invisible. --- lib/Slic3r/GUI/ConfigWizard.pm | 458 --------------------- resources/profiles/PrusaResearch.idx | 3 + xs/src/perlglue.cpp | 1 - xs/src/semver/semver.c | 7 +- xs/src/slic3r/Config/Version.cpp | 9 + xs/src/slic3r/Config/Version.hpp | 3 + xs/src/slic3r/GUI/ConfigWizard.cpp | 11 +- xs/src/slic3r/GUI/ConfigWizard.hpp | 2 +- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 2 +- xs/src/slic3r/GUI/GUI.cpp | 2 +- xs/src/slic3r/GUI/Preset.cpp | 41 +- xs/src/slic3r/GUI/Preset.hpp | 45 +- xs/src/slic3r/GUI/PresetBundle.cpp | 59 ++- xs/src/slic3r/GUI/Tab.cpp | 5 +- xs/src/slic3r/GUI/Tab.hpp | 2 +- xs/xsp/GUI_Preset.xsp | 42 -- xs/xsp/my.map | 2 - xs/xsp/typemap.xspt | 2 - 18 files changed, 146 insertions(+), 550 deletions(-) delete mode 100644 lib/Slic3r/GUI/ConfigWizard.pm diff --git a/lib/Slic3r/GUI/ConfigWizard.pm b/lib/Slic3r/GUI/ConfigWizard.pm deleted file mode 100644 index a32d345ed0..0000000000 --- a/lib/Slic3r/GUI/ConfigWizard.pm +++ /dev/null @@ -1,458 +0,0 @@ -# The config wizard is executed when the Slic3r is first started. -# The wizard helps the user to specify the 3D printer properties. - -package Slic3r::GUI::ConfigWizard; -use strict; -use warnings; -use utf8; - -use Wx; -use base 'Wx::Wizard'; - -# adhere to various human interface guidelines -our $wizard = 'Wizard'; -$wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK; - -sub new { - my ($class, $parent, $presets, $fresh_start) = @_; - my $self = $class->SUPER::new($parent, -1, "Configuration $wizard"); - - # initialize an empty repository - $self->{config} = Slic3r::Config->new; - - my $welcome_page = Slic3r::GUI::ConfigWizard::Page::Welcome->new($self, $fresh_start); - $self->add_page($welcome_page); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Firmware->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Bed->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Nozzle->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Filament->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Temperature->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::BedTemperature->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Finished->new($self)); - - $_->build_index for @{$self->{pages}}; - - $welcome_page->set_selection_presets([@{$presets}, 'Other']); - - return $self; -} - -sub add_page { - my ($self, $page) = @_; - - my $n = push @{$self->{pages}}, $page; - # add first page to the page area sizer - $self->GetPageAreaSizer->Add($page) if $n == 1; - # link pages - $self->{pages}[$n-2]->set_next_page($page) if $n >= 2; - $page->set_previous_page($self->{pages}[$n-2]) if $n >= 2; -} - -sub run { - my ($self) = @_; - my $result; - if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) { - my $preset_name = $self->{pages}[0]->{preset_name}; - $result = { - preset_name => $preset_name, - reset_user_profile => $self->{pages}[0]->{reset_user_profile} - }; - if ($preset_name eq 'Other') { - # it would be cleaner to have these defined inside each page class, - # in some event getting called before leaving the page - # set first_layer_height + layer_height based on nozzle_diameter - my $nozzle = $self->{config}->nozzle_diameter; - $self->{config}->set('first_layer_height', $nozzle->[0]); - $self->{config}->set('layer_height', $nozzle->[0] - 0.1); - - # set first_layer_temperature to temperature + 5 - $self->{config}->set('first_layer_temperature', [$self->{config}->temperature->[0] + 5]); - - # set first_layer_bed_temperature to temperature + 5 - $self->{config}->set('first_layer_bed_temperature', - [ ($self->{config}->bed_temperature->[0] > 0) ? ($self->{config}->bed_temperature->[0] + 5) : 0 ]); - $result->{config} = $self->{config}; - } - } - $self->Destroy; - return $result; -} - -package Slic3r::GUI::ConfigWizard::Index; -use Wx qw(:bitmap :dc :font :misc :sizer :systemsettings :window); -use Wx::Event qw(EVT_ERASE_BACKGROUND EVT_PAINT); -use base 'Wx::Panel'; - -sub new { - my $class = shift; - my ($parent, $title) = @_; - my $self = $class->SUPER::new($parent); - - push @{$self->{titles}}, $title; - $self->{own_index} = 0; - - $self->{bullets}->{before} = Wx::Bitmap->new(Slic3r::var("bullet_black.png"), wxBITMAP_TYPE_PNG); - $self->{bullets}->{own} = Wx::Bitmap->new(Slic3r::var("bullet_blue.png"), wxBITMAP_TYPE_PNG); - $self->{bullets}->{after} = Wx::Bitmap->new(Slic3r::var("bullet_white.png"), wxBITMAP_TYPE_PNG); - - $self->{background} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px_transparent.png"), wxBITMAP_TYPE_PNG); - $self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight)); - - EVT_PAINT($self, \&repaint); - - return $self; -} - -sub repaint { - my ($self, $event) = @_; - my $size = $self->GetClientSize; - my $gap = 5; - - my $dc = Wx::PaintDC->new($self); - $dc->SetBackgroundMode(wxTRANSPARENT); - $dc->SetFont($self->GetFont); - $dc->SetTextForeground($self->GetForegroundColour); - - my $background_h = $self->{background}->GetHeight; - my $background_w = $self->{background}->GetWidth; - $dc->DrawBitmap($self->{background}, ($size->GetWidth - $background_w) / 2, ($size->GetHeight - $background_h) / 2, 1); - - my $label_h = $self->{bullets}->{own}->GetHeight; - $label_h = $dc->GetCharHeight if $dc->GetCharHeight > $label_h; - my $label_w = $size->GetWidth; - - my $i = 0; - foreach (@{$self->{titles}}) { - my $bullet = $self->{bullets}->{own}; - $bullet = $self->{bullets}->{before} if $i < $self->{own_index}; - $bullet = $self->{bullets}->{after} if $i > $self->{own_index}; - - $dc->SetTextForeground(Wx::Colour->new(128, 128, 128)) if $i > $self->{own_index}; - $dc->DrawLabel($_, $bullet, Wx::Rect->new(0, $i * ($label_h + $gap), $label_w, $label_h)); - # Only show the first bullet if this is the only wizard page to be displayed. - last if $i == 0 && $self->{just_welcome}; - $i++; - } - - $event->Skip; -} - -sub prepend_title { - my $self = shift; - my ($title) = @_; - - unshift @{$self->{titles}}, $title; - $self->{own_index}++; - $self->Refresh; -} - -sub append_title { - my $self = shift; - my ($title) = @_; - - push @{$self->{titles}}, $title; - $self->Refresh; -} - -package Slic3r::GUI::ConfigWizard::Page; -use Wx qw(:font :misc :sizer :staticline :systemsettings); -use base 'Wx::WizardPage'; - -sub new { - my $class = shift; - my ($parent, $title, $short_title) = @_; - my $self = $class->SUPER::new($parent); - - my $sizer = Wx::FlexGridSizer->new(0, 2, 10, 10); - $sizer->AddGrowableCol(1, 1); - $sizer->AddGrowableRow(1, 1); - $sizer->AddStretchSpacer(0); - $self->SetSizer($sizer); - - # title - my $text = Wx::StaticText->new($self, -1, $title, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - my $bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - $bold_font->SetWeight(wxFONTWEIGHT_BOLD); - $bold_font->SetPointSize(14); - $text->SetFont($bold_font); - $sizer->Add($text, 0, wxALIGN_LEFT, 0); - - # index - $self->{short_title} = $short_title ? $short_title : $title; - $self->{index} = Slic3r::GUI::ConfigWizard::Index->new($self, $self->{short_title}); - $sizer->Add($self->{index}, 1, wxEXPAND | wxTOP | wxRIGHT, 10); - - # contents - $self->{width} = 430; - $self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add($self->{vsizer}, 1); - - return $self; -} - -sub append_text { - my $self = shift; - my ($text) = @_; - - my $para = Wx::StaticText->new($self, -1, $text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - $para->Wrap($self->{width}); - $para->SetMinSize([$self->{width}, -1]); - $self->{vsizer}->Add($para, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10); -} - -sub append_option { - my $self = shift; - my ($full_key) = @_; - - # populate repository with the factory default - my ($opt_key, $opt_index) = split /#/, $full_key, 2; - $self->config->apply(Slic3r::Config::new_from_defaults_keys([$opt_key])); - - # draw the control - my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( - parent => $self, - title => '', - config => $self->config, - full_labels => 1, - ); - $optgroup->append_single_option_line($opt_key, $opt_index); - $self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); -} - -sub append_panel { - my ($self, $panel) = @_; - $self->{vsizer}->Add($panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); -} - -sub set_previous_page { - my $self = shift; - my ($previous_page) = @_; - $self->{previous_page} = $previous_page; -} - -sub GetPrev { - my $self = shift; - return $self->{previous_page}; -} - -sub set_next_page { - my $self = shift; - my ($next_page) = @_; - $self->{next_page} = $next_page; -} - -sub GetNext { - my $self = shift; - return $self->{next_page}; -} - -sub get_short_title { - my $self = shift; - return $self->{short_title}; -} - -sub build_index { - my $self = shift; - - my $page = $self; - $self->{index}->prepend_title($page->get_short_title) while ($page = $page->GetPrev); - $page = $self; - $self->{index}->append_title($page->get_short_title) while ($page = $page->GetNext); -} - -sub config { - my ($self) = @_; - return $self->GetParent->{config}; -} - -package Slic3r::GUI::ConfigWizard::Page::Welcome; -use base 'Slic3r::GUI::ConfigWizard::Page'; -use Wx qw(:misc :sizer wxID_FORWARD); -use Wx::Event qw(EVT_ACTIVATE EVT_CHOICE EVT_CHECKBOX); - -sub new { - my ($class, $parent, $fresh_start) = @_; - my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome'); - $self->{full_wizard_workflow} = 1; - $self->{reset_user_profile} = 0; - - # Test for the existence of the old config path. - my $message_has_legacy; - { - my $datadir = Slic3r::data_dir; - if ($datadir =~ /Slic3rPE/) { - # Check for existence of the legacy Slic3r directory. - my $datadir_legacy = substr $datadir, 0, -2; - my $dir_enc = Slic3r::encode_path($datadir_legacy); - if (-e $dir_enc && -d $dir_enc && - -e ($dir_enc . '/print') && -d ($dir_enc . '/print') && - -e ($dir_enc . '/filament') && -d ($dir_enc . '/filament') && - -e ($dir_enc . '/printer') && -d ($dir_enc . '/printer') && - -e ($dir_enc . '/slic3r.ini')) { - $message_has_legacy = "Starting with Slic3r 1.38.4, the user profile directory has been renamed to $datadir. You may consider closing Slic3r and renaming $datadir_legacy to $datadir."; - } - } - } - - $self->append_text('Hello, welcome to Slic3r Prusa Edition! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.'); - $self->append_text('Please select your printer vendor and printer type. If your printer is not listed, you may try your luck and select a similar one. If you select "Other", this ' . lc($wizard) . ' will let you set the basic 3D printer parameters.'); - $self->append_text($message_has_legacy) if defined $message_has_legacy; - # To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.'); - $self->append_text('If you received a configuration file or a config bundle from your 3D printer vendor, cancel this '.lc($wizard).' and use the "File->Load Config" or "File->Load Config Bundle" menu.'); - - $self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []); - $self->{vsizer}->Add($choice, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); - if (! $fresh_start) { - $self->{reset_checkbox} = Wx::CheckBox->new($self, -1, "Reset user profile, install from scratch"); - $self->{vsizer}->Add($self->{reset_checkbox}, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); - } - - EVT_CHOICE($parent, $choice, sub { - my $sel = $self->{choice}->GetStringSelection; - $self->{preset_name} = $sel; - $self->set_full_wizard_workflow(($sel eq 'Other') || ($sel eq '')); - }); - - if (! $fresh_start) { - EVT_CHECKBOX($self, $self->{reset_checkbox}, sub { - $self->{reset_user_profile} = $self->{reset_checkbox}->GetValue(); - }); - } - - EVT_ACTIVATE($parent, sub { - $self->set_full_wizard_workflow($self->{preset_name} eq 'Other'); - }); - - return $self; -} - -sub set_full_wizard_workflow { - my ($self, $full_workflow) = @_; - $self->{full_wizard_workflow} = $full_workflow; - $self->{index}->{just_welcome} = !$full_workflow; - $self->{index}->Refresh; - my $next_button = $self->GetParent->FindWindow(wxID_FORWARD); - $next_button->SetLabel($full_workflow ? "&Next >" : "&Finish"); -} - -# Set the preset names, select the first item. -sub set_selection_presets { - my ($self, $names) = @_; - $self->{choice}->Append($names); - $self->{choice}->SetSelection(0); - $self->{preset_name} = $names->[0]; -} - -sub GetNext { - my $self = shift; - return $self->{full_wizard_workflow} ? $self->{next_page} : undef; -} - -package Slic3r::GUI::ConfigWizard::Page::Firmware; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Firmware Type'); - - $self->append_text('Choose the type of firmware used by your printer, then click Next.'); - $self->append_option('gcode_flavor'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Bed; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Bed Size'); - - $self->append_text('Set the shape of your printer\'s bed, then click Next.'); - - $self->config->apply(Slic3r::Config::new_from_defaults_keys(['bed_shape'])); - $self->{bed_shape_panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $self->config->bed_shape); - $self->{bed_shape_panel}->on_change(sub { - $self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue); - }); - $self->append_panel($self->{bed_shape_panel}); - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Nozzle; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Nozzle Diameter'); - - $self->append_text('Enter the diameter of your printer\'s hot end nozzle, then click Next.'); - $self->append_option('nozzle_diameter#0'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Filament; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Filament Diameter'); - - $self->append_text('Enter the diameter of your filament, then click Next.'); - $self->append_text('Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.'); - $self->append_option('filament_diameter#0'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Temperature; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Extrusion Temperature'); - - $self->append_text('Enter the temperature needed for extruding your filament, then click Next.'); - $self->append_text('A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.'); - $self->append_option('temperature#0'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::BedTemperature; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Bed Temperature'); - - $self->append_text('Enter the bed temperature needed for getting your filament to stick to your heated bed, then click Next.'); - $self->append_text('A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.'); - $self->append_option('bed_temperature#0'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Finished; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Congratulations!', 'Finish'); - - $self->append_text("You have successfully completed the Slic3r Configuration $wizard. " . - 'Slic3r is now configured for your printer and filament.'); - $self->append_text('To close this '.lc($wizard).' and apply the newly created configuration, click Finish.'); - - return $self; -} - -1; diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 28f17f10ad..3bc2aeffdd 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,6 +1,9 @@ # This is an example configuration version index. # The index contains version numbers min_slic3r_version =1.39.0 +max_slic3r_version= 1.39.5 +1.1.1 +1.1.0 0.2.0-alpha "some test comment" max_slic3r_version= 1.39.4 0.1.0 another test comment diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 9706ced2cd..205eec2188 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -62,7 +62,6 @@ REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection"); REGISTER_CLASS(Preset, "GUI::Preset"); REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); -REGISTER_CLASS(PresetHints, "GUI::PresetHints"); REGISTER_CLASS(TabIface, "GUI::Tab"); REGISTER_CLASS(PresetUpdater, "PresetUpdater"); REGISTER_CLASS(OctoPrint, "OctoPrint"); diff --git a/xs/src/semver/semver.c b/xs/src/semver/semver.c index 3e4a30e3a0..68d18af091 100644 --- a/xs/src/semver/semver.c +++ b/xs/src/semver/semver.c @@ -175,6 +175,9 @@ semver_parse_version (const char *str, semver_t *ver) { slice = (char *) str; index = 0; + // non mandatory + ver->patch = 0; + while (slice != NULL && index++ < 4) { next = strchr(slice, DELIMITER[0]); if (next == NULL) @@ -200,7 +203,8 @@ semver_parse_version (const char *str, semver_t *ver) { slice = next + 1; } - return (index == 3) ? 0 : -1; + // Major and minor versions are mandatory, patch version is not mandatory. + return (index == 2 || index == 3) ? 0 : -1; } static int @@ -632,4 +636,5 @@ semver_copy(const semver_t *ver) { if (ver->prerelease != NULL) { res.prerelease = strdup(ver->prerelease); } + return res; } \ No newline at end of file diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp index 95b3caf1a3..a80b0b6e99 100644 --- a/xs/src/slic3r/Config/Version.cpp +++ b/xs/src/slic3r/Config/Version.cpp @@ -141,6 +141,15 @@ size_t Index::load(const boost::filesystem::path &path) return m_configs.size(); } +Semver Index::version() const +{ + Semver ver = Semver::zero(); + for (const Version &cv : m_configs) + if (cv.config_version >= ver) + ver = cv.config_version; + return ver; +} + Index::const_iterator Index::find(const Semver &ver) { Version key; diff --git a/xs/src/slic3r/Config/Version.hpp b/xs/src/slic3r/Config/Version.hpp index 43512e82f5..c4243ca756 100644 --- a/xs/src/slic3r/Config/Version.hpp +++ b/xs/src/slic3r/Config/Version.hpp @@ -59,6 +59,9 @@ public: size_t load(const boost::filesystem::path &path); const std::string& vendor() const { return m_vendor; } + // Returns version of the index as the highest version of all the configs. + // If there is no config, Semver::zero() is returned. + Semver version() const; const_iterator begin() const { return m_configs.begin(); } const_iterator end() const { return m_configs.end(); } diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 52a896704d..30d1bf4e3a 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -619,7 +619,7 @@ void ConfigWizard::priv::on_custom_setup() set_page(page_firmware); } -void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle) +void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, bool fresh_start) { const bool is_custom_setup = page_welcome->page_next() == page_firmware; @@ -627,7 +627,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese app_config->set_vendors(appconfig_vendors); app_config->set("version_check", page_update->version_check ? "1" : "0"); app_config->set("preset_update", page_update->preset_update ? "1" : "0"); - app_config->reset_selections(); // XXX: only on "fresh start"? + if (fresh_start) + app_config->reset_selections(); preset_bundle->load_presets(*app_config); } else { for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) { @@ -635,6 +636,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } preset_bundle->load_config("My Settings", *custom_config); } + // Update the selections from the compatibilty. + preset_bundle->export_selections(*app_config); } // Public @@ -698,11 +701,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : ConfigWizard::~ConfigWizard() {} -bool ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) +bool ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle, bool fresh_start) { ConfigWizard wizard(parent); if (wizard.ShowModal() == wxID_OK) { - wizard.p->apply_config(GUI::get_app_config(), preset_bundle); + wizard.p->apply_config(GUI::get_app_config(), preset_bundle, fresh_start); return true; } else { return false; diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp index 40ecf09a1e..4e791e2796 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -22,7 +22,7 @@ public: ConfigWizard &operator=(const ConfigWizard &) = delete; ~ConfigWizard(); - static bool run(wxWindow *parent, PresetBundle *preset_bundle); + static bool run(wxWindow *parent, PresetBundle *preset_bundle, bool fresh_start); private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index 652328aaad..6881f6f556 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -202,7 +202,7 @@ struct ConfigWizard::priv void on_other_vendors(); void on_custom_setup(); - void apply_config(AppConfig *app_config, PresetBundle *preset_bundle); + void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, bool fresh_start); }; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index d70b47840b..036e089931 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -452,7 +452,7 @@ bool config_wizard(bool fresh_start) return false; // TODO: Offer "reset user profile" ??? - if (! ConfigWizard::run(g_wxMainFrame, g_PresetBundle)) + if (! ConfigWizard::run(g_wxMainFrame, g_PresetBundle, fresh_start)) return false; // Load the currently selected preset into the GUI, update the preset selection box. diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 39bfbd3981..853c229fff 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -521,18 +521,6 @@ size_t PresetCollection::first_visible_idx() const return idx; } -// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. -size_t PresetCollection::first_compatible_idx() const -{ - size_t idx = m_default_suppressed ? 1 : 0; - for (; idx < this->m_presets.size(); ++ idx) - if (m_presets[idx].is_compatible) - break; - if (idx == this->m_presets.size()) - idx = 0; - return idx; -} - void PresetCollection::set_default_suppressed(bool default_suppressed) { if (m_default_suppressed != default_suppressed) { @@ -541,7 +529,7 @@ void PresetCollection::set_default_suppressed(bool default_suppressed) } } -void PresetCollection::update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible) +size_t PresetCollection::update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible) { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); @@ -552,14 +540,12 @@ void PresetCollection::update_compatible_with_printer(const Preset &active_print Preset &preset_selected = m_presets[idx_preset]; Preset &preset_edited = selected ? m_edited_preset : preset_selected; if (! preset_edited.update_compatible_with_printer(active_printer, &config) && - selected && select_other_if_incompatible) + selected && unselect_if_incompatible) m_idx_selected = (size_t)-1; if (selected) preset_selected.is_compatible = preset_edited.is_compatible; } - if (m_idx_selected == (size_t)-1) - // Find some other compatible preset, or the "-- default --" preset. - this->select_preset(first_compatible_idx()); + return m_idx_selected; } // Save the preset under a new name. If the name is different from the old one, @@ -689,8 +675,8 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = 0; - if (it != m_presets.end() && it->name == name) - // Preset found by its name. + if (it != m_presets.end() && it->name == name && it->is_visible) + // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { // Find the first visible preset. @@ -711,6 +697,23 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b return false; } +bool PresetCollection::select_preset_by_name_strict(const std::string &name) +{ + // 1) Try to find the preset by its name. + auto it = this->find_preset_internal(name); + size_t idx = (size_t)-1; + if (it != m_presets.end() && it->name == name && it->is_visible) + // Preset found by its name. + idx = it - m_presets.begin(); + // 2) Select the new preset. + if (idx != (size_t)-1) { + this->select_preset(idx); + return true; + } + m_idx_selected = idx; + return false; +} + std::string PresetCollection::name() const { switch (this->type()) { diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index d6ccfd4505..08caf4d4ec 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -18,6 +18,7 @@ class wxItemContainer; namespace Slic3r { class AppConfig; +class PresetBundle; enum ConfigFileType { @@ -243,19 +244,49 @@ public: { return const_cast(this)->find_preset(name, first_visible_if_not_found); } size_t first_visible_idx() const; - size_t first_compatible_idx() const; + // Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. + // If one of the prefered_alternates is compatible, select it. + template + size_t first_compatible_idx(PreferedCondition prefered_condition) const + { + size_t i = m_default_suppressed ? 1 : 0; + size_t n = this->m_presets.size(); + size_t i_compatible = n; + for (; i < n; ++ i) + if (m_presets[i].is_compatible) { + if (prefered_condition(m_presets[i].name)) + return i; + if (i_compatible == n) + // Store the first compatible profile into i_compatible. + i_compatible = i; + } + return (i_compatible == n) ? 0 : i_compatible; + } + // Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. + size_t first_compatible_idx() const { return this->first_compatible_idx([](const std::string&){return true;}); } + // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. // Return the first visible preset. Certainly at least the '- default -' preset shall be visible. Preset& first_visible() { return this->preset(this->first_visible_idx()); } const Preset& first_visible() const { return this->preset(this->first_visible_idx()); } Preset& first_compatible() { return this->preset(this->first_compatible_idx()); } + template + Preset& first_compatible(PreferedCondition prefered_condition) { return this->preset(this->first_compatible_idx(prefered_condition)); } const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); } // Return number of presets including the "- default -" preset. size_t size() const { return this->m_presets.size(); } // For Print / Filament presets, disable those, which are not compatible with the printer. - void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible); + template + void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible, PreferedCondition prefered_condition) + { + if (this->update_compatible_with_printer_internal(active_printer, select_other_if_incompatible) == (size_t)-1) + // Find some other compatible preset, or the "-- default --" preset. + this->select_preset(this->first_compatible_idx(prefered_condition)); + } + void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible) + { this->update_compatible_with_printer(active_printer, select_other_if_incompatible, [](const std::string&){return true;}); } size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } @@ -291,6 +322,11 @@ public: // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; +protected: + // Select a preset, if it exists. If it does not exist, select an invalid (-1) index. + // This is a temporary state, which shall be fixed immediately by the following step. + bool select_preset_by_name_strict(const std::string &name); + private: PresetCollection(); PresetCollection(const PresetCollection &other); @@ -308,6 +344,8 @@ private: std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } + size_t update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible); + static std::vector dirty_options(const Preset *edited, const Preset *reference); // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. @@ -333,6 +371,9 @@ private: wxBitmap *m_bitmap_main_frame; // Path to the directory to store the config files into. std::string m_dir_path; + + // to access select_preset_by_name_strict() + friend class PresetBundle; }; } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index ad27bf8c68..244915864b 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -209,22 +209,34 @@ void PresetBundle::load_installed_printers(const AppConfig &config) // This is done just once on application start up. void PresetBundle::load_selections(const AppConfig &config) { - prints.select_preset_by_name(remove_ini_suffix(config.get("presets", "print")), true); - filaments.select_preset_by_name(remove_ini_suffix(config.get("presets", "filament")), true); - printers.select_preset_by_name(remove_ini_suffix(config.get("presets", "printer")), true); + // Update visibility of presets based on application vendor / model / variant configuration. + this->load_installed_printers(config); + + // Parse the initial print / filament / printer profile names. + std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); + std::vector initial_filament_profile_names; + std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); + auto *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); - this->set_filament_preset(0, filaments.get_selected_preset().name); + initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", "filament"))); + this->set_filament_preset(0, initial_filament_profile_names.back()); for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) { char name[64]; sprintf(name, "filament_%d", i); if (! config.has("presets", name)) break; - this->set_filament_preset(i, remove_ini_suffix(config.get("presets", name))); + initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", name))); + this->set_filament_preset(i, initial_filament_profile_names.back()); } - // Update visibility of presets based on application vendor / model / variant configuration. - this->load_installed_printers(config); + // Activate print / filament / printer profiles from the config. + // If the printer profile enumerated by the config are not visible, select an alternate preset. + // Do not select alternate profiles for the print / filament profiles as those presets + // will be selected by the following call of this->update_compatible_with_printer(true). + prints.select_preset_by_name_strict(initial_print_profile_name); + filaments.select_preset_by_name_strict(initial_filament_profile_names.front()); + printers.select_preset_by_name(initial_printer_profile_name, true); // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, @@ -861,14 +873,35 @@ void PresetBundle::update_multi_material_filament_presets() void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) { - this->prints.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible); - this->filaments.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible); + const Preset &printer_preset = this->printers.get_edited_preset(); + const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); + const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; + prefered_print_profile.empty() ? + this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); + prefered_filament_profiles.empty() ? + this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_filament_profiles](const std::string& profile_name) + { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); }); if (select_other_if_incompatible) { // Verify validity of the current filament presets. - for (std::string &filament_name : this->filament_presets) { - Preset *preset = this->filaments.find_preset(filament_name, false); - if (preset == nullptr || ! preset->is_compatible) - filament_name = this->filaments.first_compatible().name; + this->filament_presets.front() = this->filaments.get_edited_preset().name; + for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) { + std::string &filament_name = this->filament_presets[idx]; + Preset *preset = this->filaments.find_preset(filament_name, false); + if (preset == nullptr || ! preset->is_compatible) { + // Pick a compatible profile. If there are prefered_filament_profiles, use them. + if (prefered_filament_profiles.empty()) + filament_name = this->filaments.first_compatible().name; + else { + const std::string &preferred = (idx < prefered_filament_profiles.size()) ? + prefered_filament_profiles[idx] : prefered_filament_profiles.front(); + filament_name = this->filaments.first_compatible( + [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name; + } + } } } } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 940987536c..ed2b4b9514 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -540,7 +540,8 @@ void Tab::load_key_value(std::string opt_key, boost::any value) change_opt_value(*m_config, opt_key, value); // Mark the print & filament enabled if they are compatible with the currently selected preset. if (opt_key.compare("compatible_printers") == 0) { - m_preset_bundle->update_compatible_with_printer(0); + // Don't select another profile if this profile happens to become incompatible. + m_preset_bundle->update_compatible_with_printer(false); } m_presets->update_dirty_ui(m_presets_choice); on_presets_changed(); @@ -1772,7 +1773,7 @@ void Tab::rebuild_page_tree() // Called by the UI combo box when the user switches profiles. // Select a preset by a name.If !defined(name), then the default preset is selected. // If the current profile is modified, user is asked to save the changes. -void Tab::select_preset(std::string preset_name /*= ""*/) +void Tab::select_preset(const std::string &preset_name /*= ""*/) { std::string name = preset_name; auto force = false; diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index f39ff728ca..bd9672bb2d 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -143,7 +143,7 @@ public: void create_preset_tab(PresetBundle *preset_bundle); void load_current_preset(); void rebuild_page_tree(); - void select_preset(std::string preset_name = ""); + void select_preset(const std::string &preset_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, std::string new_printer_name = ""); wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn); diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 1187a1cf56..d0d4057b22 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -44,9 +44,6 @@ Ref find_preset(char *name, bool first_visible_if_not_found = false) %code%{ RETVAL = THIS->find_preset(name, first_visible_if_not_found); %}; - bool current_is_dirty(); - std::vector current_dirty_options(); - void update_tab_ui(SV *ui, bool show_incompatible) %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); THIS->update_tab_ui(cb, show_incompatible); %}; @@ -55,30 +52,6 @@ %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); THIS->update_platter_ui(cb); %}; - bool update_dirty_ui(SV *ui) - %code%{ RETVAL = THIS->update_dirty_ui((wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox")); %}; - - void select_preset(int idx); - bool select_preset_by_name(char *name) %code%{ RETVAL = THIS->select_preset_by_name(name, true); %}; - void discard_current_changes(); - - void save_current_preset(char *new_name) - %code%{ - try { - THIS->save_current_preset(new_name); - } catch (std::exception& e) { - croak("Error saving a preset %s:\n%s\n", new_name, e.what()); - } - %}; - void delete_current_preset() - %code%{ - try { - THIS->delete_current_preset(); - } catch (std::exception& e) { - croak("Error deleting a preset file %s:\n%s\n", THIS->get_selected_preset().file.c_str(), e.what()); - } - %}; - %{ SV* @@ -173,9 +146,6 @@ PresetCollection::arrayref() std::vector filament_presets() %code%{ RETVAL = THIS->filament_presets; %}; void set_filament_preset(int idx, const char *name); - void update_multi_material_filament_presets(); - - void update_compatible_with_printer(bool select_other_if_incompatible); Clone full_config() %code%{ RETVAL = THIS->full_config(); %}; @@ -183,15 +153,3 @@ PresetCollection::arrayref() %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox"); THIS->update_platter_filament_ui(extruder_idx, cb); %}; }; - -%name{Slic3r::GUI::PresetHints} class PresetHints { - PresetHints(); - ~PresetHints(); - - static std::string cooling_description(Preset *preset) - %code%{ RETVAL = PresetHints::cooling_description(*preset); %}; - static std::string maximum_volumetric_flow_description(PresetBundle *preset) - %code%{ RETVAL = PresetHints::maximum_volumetric_flow_description(*preset); %}; - static std::string recommended_thin_wall_thickness(PresetBundle *preset) - %code%{ RETVAL = PresetHints::recommended_thin_wall_thickness(*preset); %}; -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index c1ca58827b..79b71143bf 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -231,8 +231,6 @@ PresetCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PresetBundle* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -PresetHints* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T TabIface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 0214a158d6..a2bca2c7b1 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -208,8 +208,6 @@ %typemap{Ref}{simple}; %typemap{PresetBundle*}; %typemap{Ref}{simple}; -%typemap{PresetHints*}; -%typemap{Ref}{simple}; %typemap{TabIface*}; %typemap{Ref}{simple}; From 7dbb2ed6a3f97b585ed7341a9199bfd40627da6b Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 13 Apr 2018 15:08:58 +0200 Subject: [PATCH 021/145] Configuration updates downloading --- lib/Slic3r/GUI.pm | 5 +- resources/profiles/PrusaResearch.idx | 3 - resources/profiles/PrusaResearch.ini | 3 +- xs/src/libslic3r/utils.cpp | 2 +- xs/src/slic3r/Config/Version.cpp | 4 +- xs/src/slic3r/GUI/AppConfig.cpp | 6 +- xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp | 3 - xs/src/slic3r/GUI/ConfigWizard.cpp | 3 +- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 6 +- xs/src/slic3r/Utils/PresetUpdater.cpp | 224 ++++++++++++--------- xs/src/slic3r/Utils/PresetUpdater.hpp | 4 +- xs/xsp/Utils_PresetUpdater.xsp | 4 +- 12 files changed, 148 insertions(+), 119 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index b592c4289b..40cfcad896 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -103,7 +103,7 @@ sub OnInit { # my $version_check = $self->{app_config}->get('version_check'); $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT, $self->{app_config}); - eval { $self->{preset_updater}->config_update() }; + eval { $self->{preset_updater}->config_update($self->{app_config}) }; if ($@) { warn $@ . "\n"; fatal_error(undef, $@); @@ -158,7 +158,8 @@ sub OnInit { Slic3r::GUI::config_wizard(1); } - # $self->{preset_updater}->download($self->{preset_bundle}); + # TODO: call periodically? + $self->{preset_updater}->sync($self->{app_config}, $self->{preset_bundle}); }); # The following event is emited by the C++ menu implementation of application language change. diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 28f17f10ad..837bc7bac6 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,8 +1,5 @@ # This is an example configuration version index. # The index contains version numbers -min_slic3r_version =1.39.0 -0.2.0-alpha "some test comment" -max_slic3r_version= 1.39.4 0.1.0 another test comment # some empty lines diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index cf82855cf4..0ca094880b 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -8,8 +8,7 @@ name = Prusa Research config_version = 0.1.0 # Where to get the updates from? # TODO: proper URL -# config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch.ini -config_update_url = https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/e9187c3e15ceaf1a90f29b7c43cf3ccc746140f0/PrusaResearch.ini +config_update_url = https://raw.githubusercontent.com/vojtechkral/slic3r-settings-tmp/master/PrusaResearch # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 703d5ff660..733757e25a 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -119,7 +119,7 @@ static std::string g_data_dir; void set_data_dir(const std::string &dir) { - g_data_dir = dir + "-alpha"; // FIXME: Resolve backcompat problems + g_data_dir = dir; } const std::string& data_dir() diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp index 5430e569c4..b1abc5c636 100644 --- a/xs/src/slic3r/Config/Version.cpp +++ b/xs/src/slic3r/Config/Version.cpp @@ -153,10 +153,10 @@ Index::const_iterator Index::find(const Semver &ver) const Index::const_iterator Index::recommended() const { int idx = -1; - const_iterator highest = m_configs.end(); + const_iterator highest = this->end(); for (const_iterator it = this->begin(); it != this->end(); ++ it) if (it->is_current_slic3r_supported() && - (highest == this->end() || highest->max_slic3r_version < it->max_slic3r_version)) + (highest == this->end() || highest->config_version < it->config_version)) highest = it; return highest; } diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index ee77f877ae..d0f2f1019c 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -46,11 +46,15 @@ void AppConfig::set_defaults() set("no_defaults", "1"); if (get("show_incompatible_presets").empty()) set("show_incompatible_presets", "0"); - // Version check is enabled by default in the config, but it is not implemented yet. // XXX + if (get("version_check").empty()) set("version_check", "1"); + // TODO: proper URL + if (get("version_check_url").empty()) + set("version_check_url", "https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/e9187c3e15ceaf1a90f29b7c43cf3ccc746140f0/slic3rPE.version"); if (get("preset_update").empty()) set("preset_update", "1"); + // Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers. // https://github.com/prusa3d/Slic3r/issues/233 if (get("use_legacy_opengl").empty()) diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 730b97a320..8739b8fa25 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -86,9 +86,6 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); html->SetBorders(2); std::string text = generate_html_page(snapshot_db); - FILE *file = ::fopen("d:\\temp\\configsnapshotdialog.html", "wt"); - fwrite(text.data(), 1, text.size(), file); - fclose(file); html->SetPage(text.c_str()); vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0); html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this); diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index f353ab7f7e..5b49fc0258 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -660,7 +660,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese app_config->set_vendors(appconfig_vendors); app_config->set("version_check", page_update->version_check ? "1" : "0"); app_config->set("preset_update", page_update->preset_update ? "1" : "0"); - app_config->reset_selections(); // XXX: only on "fresh start"? + app_config->reset_selections(); + // ^ TODO: replace with appropriate printer selection preset_bundle->load_presets(*app_config); } else { for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) { diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index 137b276b85..c93e0a80d5 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -17,7 +17,6 @@ #include "libslic3r/PrintConfig.hpp" #include "AppConfig.hpp" #include "Preset.hpp" -// #include "PresetBundle.hpp" #include "BedShapeDialog.hpp" namespace fs = boost::filesystem; @@ -173,9 +172,8 @@ private: struct ConfigWizard::priv { ConfigWizard *q; - AppConfig appconfig_vendors; - // PresetBundle bundle_vendors; - std::unordered_map vendors; + AppConfig appconfig_vendors; // TODO: use order-preserving container + std::unordered_map vendors; // TODO: just set? std::unordered_map vendors_rsrc; std::unique_ptr custom_config; diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 28b9773213..257c7f552e 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -25,19 +27,17 @@ using Slic3r::GUI::Config::Version; using Slic3r::GUI::Config::Snapshot; using Slic3r::GUI::Config::SnapshotDB; -// XXX: Prevent incomplete file downloads: download a tmp file, then move -// Delete incomplete ones on startup. namespace Slic3r { -// TODO: proper URL -// TODO: Actually, use index -static const std::string SLIC3R_VERSION_URL = "https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/e9187c3e15ceaf1a90f29b7c43cf3ccc746140f0/slic3rPE.version"; enum { SLIC3R_VERSION_BODY_MAX = 256, }; +static const char *INDEX_FILENAME = "index.idx"; +static const char *TMP_EXTENSION = ".download"; + struct Update { @@ -45,7 +45,7 @@ struct Update fs::path target; Version version; - Update(const fs::path &source, fs::path &&target, const Version &version) : + Update(fs::path &&source, const fs::path &target, const Version &version) : source(source), target(std::move(target)), version(version) @@ -60,9 +60,11 @@ typedef std::vector Updates; struct PresetUpdater::priv { int version_online_event; - AppConfig *app_config; - bool version_check; - bool preset_update; + std::vector index_db; + + bool enabled_version_check; + bool enabled_config_update; + std::string version_check_url; fs::path cache_path; fs::path rsrc_path; @@ -73,7 +75,11 @@ struct PresetUpdater::priv priv(int event, AppConfig *app_config); - void download(const std::set vendors) const; + 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_version() const; + void sync_config(const std::set vendors) const; void check_install_indices() const; Updates config_update() const; @@ -81,21 +87,62 @@ struct PresetUpdater::priv PresetUpdater::priv::priv(int event, AppConfig *app_config) : version_online_event(event), - app_config(app_config), - version_check(false), - preset_update(false), cache_path(fs::path(Slic3r::data_dir()) / "cache"), rsrc_path(fs::path(resources_dir()) / "profiles"), vendor_path(fs::path(Slic3r::data_dir()) / "vendor"), cancel(false) -{} - -void PresetUpdater::priv::download(const std::set vendors) const { - if (!version_check) { return; } + set_download_prefs(app_config); + check_install_indices(); + index_db = std::move(Index::load_db()); +} - // Download current Slic3r version - Http::get(SLIC3R_VERSION_URL) +void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) +{ + enabled_version_check = app_config->get("version_check") == "1"; + version_check_url = app_config->get("version_check_url"); + enabled_config_update = app_config->get("preset_update") == "1"; +} + +bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &target_path) const +{ + std::cerr << "get_file(): " << url << " -> " << target_path << std::endl; + + // TODO: Proper caching + + bool res = false; + fs::path tmp_path = target_path; + tmp_path += TMP_EXTENSION; + + Http::get(url) + .on_progress([this](Http::Progress, bool &cancel) { + if (cancel) { cancel = true; } + }) + .on_complete([&](std::string body, unsigned http_status) { + fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(body.c_str(), body.size()); + fs::rename(tmp_path, target_path); + res = true; + }) + .perform_sync(); + + return res; +} + +void PresetUpdater::priv::prune_tmps() const +{ + for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) { + if (it->path().extension() == TMP_EXTENSION) { + fs::remove(it->path()); + } + } +} + +void PresetUpdater::priv::sync_version() const +{ + if (! enabled_version_check) { return; } + + Http::get(version_check_url) .size_limit(SLIC3R_VERSION_BODY_MAX) .on_progress([this](Http::Progress, bool &cancel) { cancel = this->cancel; @@ -107,27 +154,55 @@ void PresetUpdater::priv::download(const std::set vendors) const GUI::get_app()->QueueEvent(evt); }) .perform_sync(); +} - if (!preset_update) { return; } +void PresetUpdater::priv::sync_config(const std::set vendors) const +{ + std::cerr << "sync_config()" << std::endl; + + if (!enabled_config_update) { return; } // Donwload vendor preset bundles - for (const auto &vendor : vendors) { + for (const auto &index : index_db) { if (cancel) { return; } - // TODO: Proper caching + std::cerr << "Index: " << index.vendor() << std::endl; - auto target_path = cache_path / vendor.id; - target_path += ".ini"; + const auto vendor_it = vendors.find(VendorProfile(index.vendor())); + if (vendor_it == vendors.end()) { continue; } - Http::get(vendor.config_update_url) - .on_progress([this](Http::Progress, bool &cancel) { - cancel = this->cancel; - }) - .on_complete([&](std::string body, unsigned http_status) { - fs::fstream file(target_path, std::ios::out | std::ios::binary | std::ios::trunc); - file.write(body.c_str(), body.size()); - }) - .perform_sync(); + const VendorProfile &vendor = *vendor_it; + if (vendor.config_update_url.empty()) { continue; } + + // Download a fresh index + const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME; + const auto idx_path = cache_path / (vendor.id + ".idx"); + if (! get_file(idx_url, idx_path)) { continue; } + if (cancel) { return; } + + std::cerr << "Got a new index: " << idx_path << std::endl; + + // Load the fresh index up + Index new_index; + new_index.load(idx_path); + + // See if a there's a new version to download + const auto recommended_it = new_index.recommended(); + if (recommended_it == new_index.end()) { continue; } + const auto recommended = recommended_it->config_version; + + std::cerr << "Current vendor version: " << vendor.config_version.to_string() << std::endl; + std::cerr << "Recommended version:\t" << recommended.to_string() << std::endl; + + if (vendor.config_version >= recommended) { continue; } + + // Download a fresh bundle + const auto bundle_url = (boost::format("%1%/%2%.ini") % vendor.config_update_url % recommended.to_string()).str(); + const auto bundle_path = cache_path / (vendor.id + ".ini"); + if (! get_file(bundle_url, bundle_path)) { continue; } + if (cancel) { return; } + + std::cerr << "Got a new bundle: " << bundle_path << std::endl; } } @@ -148,35 +223,11 @@ void PresetUpdater::priv::check_install_indices() const Updates PresetUpdater::priv::config_update() const { - priv::check_install_indices(); - const auto index_db = Index::load_db(); // TODO: Keep in Snapshots singleton? - Updates updates; for (const auto idx : index_db) { const auto bundle_path = vendor_path / (idx.vendor() + ".ini"); - // If the bundle doesn't exist at all, update from resources - // if (! fs::exists(bundle_path)) { - // auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini"); - - // // Otherwise load it and check for chached updates - // const auto rsrc_vp = VendorProfile::from_ini(path_in_rsrc, false); - - // const auto rsrc_ver = idx.find(rsrc_vp.config_version); - // if (rsrc_ver == idx.end()) { - // // TODO: throw - // } - - // if (fs::exists(path_in_rsrc)) { - // updates.emplace_back(bundle_path, std::move(path_in_rsrc), *rsrc_ver); - // } else { - // // XXX: ??? - // } - - // continue; - // } - if (! fs::exists(bundle_path)) { continue; } @@ -186,12 +237,12 @@ Updates PresetUpdater::priv::config_update() const const auto ver_current = idx.find(vp.config_version); if (ver_current == idx.end()) { - // TODO: throw + // TODO: throw / ignore ? } const auto recommended = idx.recommended(); if (recommended == idx.end()) { - // TODO: throw + throw std::runtime_error((boost::format("Invalid index: `%1%`") % idx.vendor()).str()); } if (! ver_current->is_current_slic3r_supported()) { @@ -202,50 +253,26 @@ Updates PresetUpdater::priv::config_update() const // Config bundle update situation auto path_in_cache = cache_path / (idx.vendor() + ".ini"); + if (! fs::exists(path_in_cache)) { + continue; + } + const auto cached_vp = VendorProfile::from_ini(path_in_cache, false); if (cached_vp.config_version == recommended->config_version) { - updates.emplace_back(bundle_path, std::move(path_in_cache), *ver_current); + updates.emplace_back(std::move(path_in_cache), bundle_path, *recommended); } else { - // XXX: ??? + // XXX: ? } } } - // Check for bundles that don't have an index - // for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) { - // if (it->path().extension() == ".ini") { - // const auto &path = it->path(); - // const auto vendor_id = path.stem().string(); - - // const auto needle = std::find_if(index_db.begin(), index_db.end(), [&vendor_id](const Index &idx) { - // return idx.vendor() == vendor_id; - // }); - // if (needle != index_db.end()) { - // continue; - // } - - // auto vp = VendorProfile::from_ini(path, false); - // auto path_in_data = vendor_path / path.filename(); - - // if (! fs::exists(path_in_data)) { - // Version version; - // version.config_version = vp.config_version; - // updates.emplace_back(path, std::move(path_in_data), version); - // } - // } - // } - return updates; } PresetUpdater::PresetUpdater(int version_online_event, AppConfig *app_config) : p(new priv(version_online_event, app_config)) -{ - p->preset_update = app_config->get("preset_update") == "1"; - // preset_update implies version_check: // XXX: not any more - p->version_check = p->preset_update || app_config->get("version_check") == "1"; -} +{} // Public @@ -258,8 +285,10 @@ PresetUpdater::~PresetUpdater() } } -void PresetUpdater::download(PresetBundle *preset_bundle) +void PresetUpdater::sync(AppConfig *app_config, PresetBundle *preset_bundle) { + p->set_download_prefs(app_config); + if (!p->enabled_version_check && !p->enabled_config_update) { return; } // Copy the whole vendors data for use in the background thread // Unfortunatelly as of C++11, it needs to be copied again @@ -267,12 +296,16 @@ void PresetUpdater::download(PresetBundle *preset_bundle) std::set vendors = preset_bundle->vendors; p->thread = std::move(std::thread([this, vendors]() { - this->p->download(std::move(vendors)); + this->p->prune_tmps(); + this->p->sync_version(); + this->p->sync_config(std::move(vendors)); })); } -void PresetUpdater::config_update() +void PresetUpdater::config_update(AppConfig *app_config) { + if (! p->enabled_config_update) { return; } + const auto updates = p->config_update(); if (updates.size() > 0) { const auto msg = _(L("Configuration update is available. Would you like to install it?")); @@ -298,12 +331,11 @@ void PresetUpdater::config_update() if (res == wxID_YES) { // User gave clearance, updates are go - // TODO: Comment? - SnapshotDB::singleton().take_snapshot(*p->app_config, Snapshot::SNAPSHOT_UPGRADE, ""); + SnapshotDB::singleton().take_snapshot(*app_config, Snapshot::SNAPSHOT_UPGRADE, ""); for (const auto &update : updates) { fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); - + PresetBundle bundle; bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp index 8fd6e45289..966dd14647 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -19,9 +19,9 @@ public: PresetUpdater &operator=(const PresetUpdater &) = delete; ~PresetUpdater(); - void download(PresetBundle *preset_bundle); // XXX + void sync(AppConfig *app_config, PresetBundle *preset_bundle); - void config_update(); + void config_update(AppConfig *app_config); private: struct priv; std::unique_ptr p; diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp index 4c1a637e49..3a4d55a01e 100644 --- a/xs/xsp/Utils_PresetUpdater.xsp +++ b/xs/xsp/Utils_PresetUpdater.xsp @@ -7,6 +7,6 @@ %name{Slic3r::PresetUpdater} class PresetUpdater { PresetUpdater(int version_online_event, AppConfig *app_config); - void download(PresetBundle* preset_bundle); - void config_update(); + void sync(AppConfig *app_config, PresetBundle* preset_bundle); + void config_update(AppConfig *app_config); }; From 6d25ed2b00447ca3b0ed48a9d0ec288ee8b24503 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 13 Apr 2018 16:15:30 +0200 Subject: [PATCH 022/145] Version's compatibility with Slic3r extended with pre-release compatibility check: A release Slic3r is not compatible with alpha and beta configs, a beta Slic3r is not compatible with alpha configs, but is compatible with beta configs etc. --- xs/src/slic3r/Config/Version.cpp | 120 +++++++++++++++++++++++++++++++ xs/src/slic3r/Config/Version.hpp | 2 +- xs/src/slic3r/Utils/Semver.hpp | 7 ++ 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp index a80b0b6e99..8344e2822a 100644 --- a/xs/src/slic3r/Config/Version.cpp +++ b/xs/src/slic3r/Config/Version.cpp @@ -15,11 +15,131 @@ namespace Config { static boost::optional s_current_slic3r_semver = Semver::parse(SLIC3R_VERSION); +// Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix. +static int compare_prerelease(const char *p1, const char *p2) +{ + for (;;) { + char c1 = *p1 ++; + char c2 = *p2 ++; + bool a1 = std::isalpha(c1) && c1 != 0; + bool a2 = std::isalpha(c2) && c2 != 0; + if (a1) { + if (a2) { + if (c1 != c2) + return (c1 < c2) ? -1 : 1; + } else + return 1; + } else { + if (a2) + return -1; + else + return 0; + } + } + // This shall never happen. + return 0; +} + +bool Version::is_slic3r_supported(const Semver &slic3r_version) const +{ + if (! slic3r_version.in_range(min_slic3r_version, max_slic3r_version)) + return false; + // Now verify, whether the configuration pre-release status is compatible with the Slic3r's pre-release status. + // Alpha Slic3r will happily load any configuration, while beta Slic3r will ignore alpha configurations etc. + const char *prerelease_slic3r = slic3r_version.prerelease(); + const char *prerelease_config = this->config_version.prerelease(); + if (prerelease_config == nullptr) + // Released config is always supported. + return true; + else if (prerelease_slic3r == nullptr) + // Released slic3r only supports released configs. + return false; + // Compare the pre-release status of Slic3r against the config. + // If the prerelease status of slic3r is lexicographically lower or equal + // to the prerelease status of the config, accept it. + return compare_prerelease(prerelease_slic3r, prerelease_config) != 1; +} + bool Version::is_current_slic3r_supported() const { return this->is_slic3r_supported(*s_current_slic3r_semver); } +#if 0 +//TODO: This test should be moved to a unit test, once we have C++ unit tests in place. +static int version_test() +{ + Version v; + v.config_version = *Semver::parse("1.1.2"); + v.min_slic3r_version = *Semver::parse("1.38.0"); + v.max_slic3r_version = Semver::inf(); + assert(v.is_slic3r_supported(*Semver::parse("1.38.0"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha"))); + // Test the prerelease status. + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-alpha"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-alpha1"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-beta"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-rc"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-rc2"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + // Test the upper boundary. + v.config_version = *Semver::parse("1.1.2"); + v.max_slic3r_version = *Semver::parse("1.39.3-beta1"); + assert(v.is_slic3r_supported(*Semver::parse("1.38.0"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha"))); + return 0; +} +static int version_test_run = version_test(); +#endif + inline char* left_trim(char *c) { for (; *c == ' ' || *c == '\t'; ++ c); diff --git a/xs/src/slic3r/Config/Version.hpp b/xs/src/slic3r/Config/Version.hpp index c4243ca756..fb45c17eb3 100644 --- a/xs/src/slic3r/Config/Version.hpp +++ b/xs/src/slic3r/Config/Version.hpp @@ -27,7 +27,7 @@ struct Version // Single comment line. std::string comment; - bool is_slic3r_supported(const Semver &slicer_version) const { return slicer_version.in_range(min_slic3r_version, max_slic3r_version); } + bool is_slic3r_supported(const Semver &slicer_version) const; bool is_current_slic3r_supported() const; }; diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index a1f4a92e86..538a3f1443 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -77,6 +77,13 @@ public: ~Semver() { ::semver_free(&ver); } + // const accessors + int major() const { return ver.major; } + int minor() const { return ver.minor; } + int patch() const { return ver.patch; } + const char* prerelease() const { return ver.prerelease; } + const char* metadata() const { return ver.metadata; } + // Comparison bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; } bool operator<=(const Semver &b) const { return ::semver_compare(ver, b.ver) <= 0; } From c18b28e27ca4acf68482f981620632d86b1ac356 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 16 Apr 2018 11:03:08 +0200 Subject: [PATCH 023/145] Implemented caching of often rendered bitmaps on the Tab UI. --- xs/src/slic3r/GUI/Field.cpp | 8 ++--- xs/src/slic3r/GUI/Field.hpp | 32 +++++++++++++++++-- xs/src/slic3r/GUI/OptionsGroup.cpp | 4 +-- xs/src/slic3r/GUI/OptionsGroup.hpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 50 +++++++++++++++--------------- xs/src/slic3r/GUI/Tab.hpp | 19 +++++++++--- 6 files changed, 76 insertions(+), 39 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index c68b2304c0..a32943da05 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -26,16 +26,16 @@ namespace Slic3r { namespace GUI { m_Undo_btn->SetBackgroundColour(color); m_Undo_to_sys_btn->SetBackgroundColour(color); } - m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); +// m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); BUILD(); } - void Field::set_nonsys_btn_icon(const std::string& icon){ - m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(icon)), wxBITMAP_TYPE_PNG)); - } +// void Field::set_nonsys_btn_icon(const wxBitmap& icon){ +// m_Undo_to_sys_btn->SetBitmap(icon); +// } void Field::on_kill_focus(wxEvent& event) { // Without this, there will be nasty focus bugs on Windows. diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index cdc7c0d812..2b2646b7cb 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -91,8 +91,6 @@ public: virtual void disable() = 0; wxStaticText* m_Label = nullptr; - wxButton* m_Undo_btn = nullptr; - wxButton* m_Undo_to_sys_btn = nullptr; /// Fires the enable or disable function, based on the input. inline void toggle(bool en) { en ? enable() : disable(); } @@ -100,7 +98,7 @@ public: virtual wxString get_tooltip_text(const wxString& default_string); // set icon to "UndoToSystemValue" button according to an inheritance of preset - void set_nonsys_btn_icon(const std::string& icon); +// void set_nonsys_btn_icon(const wxBitmap& icon); Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}; Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}; @@ -120,6 +118,34 @@ public: p->PostInitialize(); return std::move(p); //!p; } + + bool set_undo_bitmap(const wxBitmap *bmp) { + if (m_undo_bitmap != bmp) { + m_undo_bitmap = bmp; + m_Undo_btn->SetBitmap(*bmp); + return true; + } + return false; + } + + bool set_undo_to_sys_bitmap(const wxBitmap *bmp) { + if (m_undo_to_sys_bitmap != bmp) { + m_undo_to_sys_bitmap = bmp; + m_Undo_to_sys_btn->SetBitmap(*bmp); + return true; + } + return false; + } + +protected: + wxButton* m_Undo_btn = nullptr; + // Bitmap for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. + const wxBitmap* m_undo_bitmap = nullptr; + wxButton* m_Undo_to_sys_btn = nullptr; + // Bitmap for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. + const wxBitmap* m_undo_to_sys_bitmap = nullptr; + + friend class OptionsGroup; }; /// Convenience function, accepts a const reference to t_field and checks to see whether diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 168ffcdc93..c71dd5ba6b 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -90,8 +90,8 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co field->m_Undo_btn->Hide(); field->m_Undo_to_sys_btn->Hide(); } - if (nonsys_btn_icon != nullptr) - field->set_nonsys_btn_icon(nonsys_btn_icon()); +// if (nonsys_btn_icon != nullptr) +// field->set_nonsys_btn_icon(*nonsys_btn_icon); // assign function objects for callbacks, etc. return field; diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index dd6d48f46c..92ebb44881 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -86,7 +86,7 @@ public: wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; - std::function nonsys_btn_icon{ nullptr }; +// std::function nonsys_btn_icon{ nullptr }; /// Returns a copy of the pointer of the parent wxWindow. /// Accessor function is because users are not allowed to change the parent diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index cc4b18c7c6..f2511708b5 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -88,9 +88,9 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) if (wxMSW) m_btn_delete_preset->SetBackgroundColour(color); m_show_incompatible_presets = false; - m_bmp_show_incompatible_presets = new wxBitmap(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG); - m_bmp_hide_incompatible_presets = new wxBitmap(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG); - m_btn_hide_incompatible_presets = new wxBitmapButton(panel, wxID_ANY, *m_bmp_hide_incompatible_presets, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + m_bmp_show_incompatible_presets.LoadFile(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG); + m_bmp_hide_incompatible_presets.LoadFile(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG); + m_btn_hide_incompatible_presets = new wxBitmapButton(panel, wxID_ANY, m_bmp_hide_incompatible_presets, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); if (wxMSW) m_btn_hide_incompatible_presets->SetBackgroundColour(color); m_btn_save_preset->SetToolTip(_(L("Save current ")) + m_title); @@ -103,9 +103,16 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_undo_btn->SetBackgroundColour(color); m_undo_to_sys_btn->SetBackgroundColour(color); } - m_undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); + // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_bmp_value_lock .LoadFile(from_u8(var("sys_lock.png")), wxBITMAP_TYPE_PNG); + m_bmp_value_unlock .LoadFile(from_u8(var("sys_unlock.png")), wxBITMAP_TYPE_PNG); + m_bmp_non_system = &m_bmp_white_bullet; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + m_bmp_value_revert .LoadFile(from_u8(var("action_undo.png")), wxBITMAP_TYPE_PNG); + m_bmp_white_bullet .LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); + m_undo_btn->SetBitmap(m_bmp_white_bullet); m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); - m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); + m_undo_to_sys_btn->SetBitmap(m_bmp_white_bullet); m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); m_hsizer = new wxBoxSizer(wxHORIZONTAL); @@ -204,8 +211,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) void Tab::load_initial_data() { m_config = &m_presets->get_edited_preset().config; - m_nonsys_btn_icon = m_presets->get_selected_preset_parent() == nullptr ? - "bullet_white.png" : "sys_unlock.png"; + m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; } PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages/* = false*/) @@ -334,12 +340,12 @@ void Tab::update_changed_ui() { bool is_nonsys_value = false; bool is_modified_value = true; - std::string sys_icon = "sys_lock.png"; - std::string icon = "action_undo.png"; + const wxBitmap *sys_icon = &m_bmp_value_lock; + const wxBitmap *icon = &m_bmp_value_revert; wxColour color = get_sys_label_clr(); if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { is_nonsys_value = true; - sys_icon = m_nonsys_btn_icon; + sys_icon = m_bmp_non_system; if(find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); else @@ -348,7 +354,7 @@ void Tab::update_changed_ui() if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) { is_modified_value = false; - icon = "bullet_white.png"; + icon = &m_bmp_white_bullet; } if (opt_key == "bed_shape" || opt_key == "compatible_printers") { if (m_colored_Label != nullptr) { @@ -362,8 +368,8 @@ void Tab::update_changed_ui() if (field == nullptr) continue; field->m_is_nonsys_value = is_nonsys_value; field->m_is_modified_value = is_modified_value; - field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(var(icon)), wxBITMAP_TYPE_PNG)); - field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(sys_icon)), wxBITMAP_TYPE_PNG)); + field->set_undo_bitmap(icon); + field->set_undo_to_sys_bitmap(sys_icon); if (field->m_Label != nullptr){ field->m_Label->SetForegroundColour(color); field->m_Label->Refresh(true); @@ -420,7 +426,7 @@ void Tab::update_sys_ui_after_sel_preset() for (const auto opt_key : m_full_options_list){ Field* field = get_field(opt_key); if (field != nullptr){ - field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(m_nonsys_btn_icon)), wxBITMAP_TYPE_PNG)); + field->set_undo_to_sys_bitmap(m_bmp_non_system); field->m_is_nonsys_value = true; if (field->m_Label != nullptr){ field->m_Label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); @@ -493,11 +499,8 @@ void Tab::update_changed_tree_ui() void Tab::update_undo_buttons() { - const std::string& undo_icon = !m_is_modified_values ? "bullet_white.png" : "action_undo.png"; - const std::string& undo_to_sys_icon = m_is_nonsys_values ? m_nonsys_btn_icon : "sys_lock.png"; - - m_undo_btn->SetBitmap(wxBitmap(from_u8(var(undo_icon)), wxBITMAP_TYPE_PNG)); - m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(undo_to_sys_icon)), wxBITMAP_TYPE_PNG)); + m_undo_btn->SetBitmap(m_is_modified_values ? m_bmp_value_revert : m_bmp_white_bullet); + m_undo_to_sys_btn->SetBitmap(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); } void Tab::on_back_to_initial_value() @@ -1853,8 +1856,7 @@ void Tab::load_current_preset() on_preset_loaded(); // Reload preset pages with the new configuration values. reload_config(); - const Preset* parent = m_presets->get_selected_preset_parent(); - m_nonsys_btn_icon = parent == nullptr ? "bullet_white.png" : "sys_unlock.png"; + m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; // use CallAfter because some field triggers schedule on_change calls using CallAfter, // and we don't want them to be called after this update_dirty() as they would mark the @@ -2142,7 +2144,7 @@ void Tab::toggle_show_hide_incompatible() void Tab::update_show_hide_incompatible_button() { m_btn_hide_incompatible_presets->SetBitmap(m_show_incompatible_presets ? - *m_bmp_show_incompatible_presets : *m_bmp_hide_incompatible_presets); + m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); @@ -2460,9 +2462,7 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la return static_cast(GetParent())->m_presets->get_selected_preset_parent() != nullptr; }; - optgroup->nonsys_btn_icon = [this](){ - return static_cast(GetParent())->m_nonsys_btn_icon; - }; +// optgroup->nonsys_btn_icon = m_bmp_non_system; vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); m_optgroups.push_back(optgroup); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index f9ee4f3639..cb622582f1 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -85,8 +85,6 @@ protected: wxBitmapComboBox* m_presets_choice; wxBitmapButton* m_btn_save_preset; wxBitmapButton* m_btn_delete_preset; - wxBitmap* m_bmp_show_incompatible_presets; - wxBitmap* m_bmp_hide_incompatible_presets; wxBitmapButton* m_btn_hide_incompatible_presets; wxBoxSizer* m_hsizer; wxBoxSizer* m_left_sizer; @@ -95,11 +93,25 @@ protected: wxCheckBox* m_compatible_printers_checkbox; wxButton* m_compatible_printers_btn; wxButton* m_undo_btn; - wxButton* m_undo_to_sys_btn; + wxButton* m_undo_to_sys_btn; wxComboCtrl* m_cc_presets_choice; wxDataViewTreeCtrl* m_presetctrl; wxImageList* m_preset_icons; + // Cached bitmaps. + // A "flag" icon to be displayned next to the preset name in the Tab's combo box. + wxBitmap m_bmp_show_incompatible_presets; + wxBitmap m_bmp_hide_incompatible_presets; + // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + wxBitmap m_bmp_value_lock; + wxBitmap m_bmp_value_unlock; + wxBitmap m_bmp_white_bullet; + // The following bitmap points to either m_bmp_value_unlock or m_bmp_white_bullet, depending on whether the current preset has a parent preset. + wxBitmap *m_bmp_non_system; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + wxBitmap m_bmp_value_revert; + wxBitmap m_bmp_value_unmodified; + int m_icon_count; std::map m_icon_index; // Map from an icon file name to its index std::vector m_pages; @@ -124,7 +136,6 @@ public: bool m_show_btn_incompatible_presets = false; PresetCollection* m_presets; DynamicPrintConfig* m_config; - std::string m_nonsys_btn_icon; ogStaticText* m_parent_preset_description_line; wxStaticText* m_colored_Label = nullptr; From 5d3912698963d30edf0beada8b5e3b6182a81d73 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 16 Apr 2018 13:43:01 +0200 Subject: [PATCH 024/145] Changes to performance. --- xs/src/slic3r/GUI/Tab.cpp | 45 ++++++++++++++++++++++++++++----------- xs/src/slic3r/GUI/Tab.hpp | 3 ++- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index cc4b18c7c6..7fd50d5aa7 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -268,6 +268,8 @@ void add_correct_opts_to_sys_options(const std::string &opt_key, std::vectorcurrent_dirty_options(); if (name() == "printer"){ @@ -504,6 +506,8 @@ void Tab::on_back_to_initial_value() { if (!m_is_modified_values) return; + m_postpone_update_ui = true; + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); for (auto page : m_pages) if (page->title() == selection) { @@ -513,16 +517,21 @@ void Tab::on_back_to_initial_value() group->back_to_initial_value("extruders_count"); } if (group->title == _("Size and coordinates")){ - if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()) + if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()){ group->back_to_initial_value("bed_shape"); + load_key_value("bed_shape", true/*some value*/, true); + } + } if (group->title == _("Profile dependencies")){ - if (find(m_dirty_options.begin(), m_dirty_options.end(), "compatible_printers") != m_dirty_options.end()) + if (find(m_dirty_options.begin(), m_dirty_options.end(), "compatible_printers") != m_dirty_options.end()){ group->back_to_initial_value("compatible_printers"); + load_key_value("compatible_printers", true/*some value*/, true); - bool is_empty = m_config->option("compatible_printers")->values.empty(); - m_compatible_printers_checkbox->SetValue(is_empty); - is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + bool is_empty = m_config->option("compatible_printers")->values.empty(); + m_compatible_printers_checkbox->SetValue(is_empty); + is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + } } for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { const std::string& opt_key = it->first; @@ -532,6 +541,8 @@ void Tab::on_back_to_initial_value() } break; } + + m_postpone_update_ui = false; update_changed_ui(); } @@ -539,6 +550,8 @@ void Tab::on_back_to_sys_value() { if (!m_is_nonsys_values) return; + m_postpone_update_ui = true; + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); for (auto page : m_pages) if (page->title() == selection) { @@ -548,16 +561,20 @@ void Tab::on_back_to_sys_value() group->back_to_sys_value("extruders_count"); } if (group->title == _("Size and coordinates")){ - if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()) + if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()){ group->back_to_sys_value("bed_shape"); + load_key_value("bed_shape", true/*some value*/, true); + } } if (group->title == _("Profile dependencies")){ - if (find(m_sys_options.begin(), m_sys_options.end(), "compatible_printers") == m_sys_options.end()) + if (find(m_sys_options.begin(), m_sys_options.end(), "compatible_printers") == m_sys_options.end()){ group->back_to_sys_value("compatible_printers"); + load_key_value("compatible_printers", true/*some value*/, true); - bool is_empty = m_config->option("compatible_printers")->values.empty(); - m_compatible_printers_checkbox->SetValue(is_empty); - is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + bool is_empty = m_config->option("compatible_printers")->values.empty(); + m_compatible_printers_checkbox->SetValue(is_empty); + is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + } } for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { const std::string& opt_key = it->first; @@ -567,6 +584,8 @@ void Tab::on_back_to_sys_value() } break; } + + m_postpone_update_ui = false; update_changed_ui(); } @@ -636,9 +655,11 @@ bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) // To be called by custom widgets, load a value into a config, // update the preset selection boxes (the dirty flags) -void Tab::load_key_value(const std::string& opt_key, const boost::any& value) +// If value is saved before calling this function, put saved_value = true, +// and value can be some random value because in this case it will not been used +void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value /*= false*/) { - change_opt_value(*m_config, opt_key, value); + if (!saved_value) change_opt_value(*m_config, opt_key, value); // Mark the print & filament enabled if they are compatible with the currently selected preset. if (opt_key.compare("compatible_printers") == 0) { m_preset_bundle->update_compatible_with_printer(0); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index f9ee4f3639..f7eba61862 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -118,6 +118,7 @@ protected: bool m_is_modified_values{ false }; bool m_is_nonsys_values{ true }; + bool m_postpone_update_ui {false}; public: PresetBundle* m_preset_bundle; @@ -153,7 +154,7 @@ public: wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn); void update_presetsctrl(wxDataViewTreeCtrl* ui, bool show_incompatible); - void load_key_value(const std::string& opt_key, const boost::any& value); + void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false); void reload_compatible_printers_widget(); void OnTreeSelChange(wxTreeEvent& event); From c733e3151b2d15fdf1507c8417c2a96898d1d019 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 16 Apr 2018 16:52:11 +0200 Subject: [PATCH 025/145] Updating: Detect legacy datadir, remove conflicting presets --- lib/Slic3r/GUI.pm | 34 ++---- xs/src/libslic3r/libslic3r.h | 2 +- xs/src/slic3r/Config/Snapshot.hpp | 2 +- xs/src/slic3r/GUI/AppConfig.cpp | 5 +- xs/src/slic3r/GUI/AppConfig.hpp | 6 +- xs/src/slic3r/GUI/ConfigWizard.cpp | 120 +++++++++++++++------ xs/src/slic3r/GUI/ConfigWizard.hpp | 3 +- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 24 ++++- xs/src/slic3r/GUI/GUI.cpp | 48 +++++++-- xs/src/slic3r/GUI/GUI.hpp | 9 +- xs/src/slic3r/Utils/PresetUpdater.cpp | 60 +++++++---- xs/src/slic3r/Utils/PresetUpdater.hpp | 3 +- xs/src/slic3r/Utils/Semver.hpp | 2 - xs/xsp/GUI.xsp | 7 +- xs/xsp/my.map | 1 - xs/xsp/typemap.xspt | 2 + 16 files changed, 232 insertions(+), 96 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 40cfcad896..473fc6b90b 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -95,14 +95,15 @@ sub OnInit { warn $@ . "\n"; fatal_error(undef, $@); } - my $run_wizard = ! $self->{app_config}->exists; + my $app_conf_exists = $self->{app_config}->exists; # load settings - $self->{app_config}->load if ! $run_wizard; + $self->{app_config}->load if $app_conf_exists; $self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->save; # my $version_check = $self->{app_config}->get('version_check'); $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT, $self->{app_config}); + Slic3r::GUI::set_preset_updater($self->{preset_updater}); eval { $self->{preset_updater}->config_update($self->{app_config}) }; if ($@) { warn $@ . "\n"; @@ -120,8 +121,6 @@ sub OnInit { warn $@ . "\n"; show_error(undef, $@); } - # TODO: check previously downloaded updates - $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); @@ -148,16 +147,8 @@ sub OnInit { # On OSX the UI was not initialized correctly if the wizard was called # before the UI was up and running. $self->CallAfter(sub { - # XXX: recreate_GUI ??? - # if ($slic3r_update) { - # # TODO - # } - # XXX: ? - if ($run_wizard) { - # Run the config wizard, don't offer the "reset user profile" checkbox. - Slic3r::GUI::config_wizard(1); - } - + Slic3r::GUI::config_wizard_startup($app_conf_exists); + # TODO: call periodically? $self->{preset_updater}->sync($self->{app_config}, $self->{preset_bundle}); }); @@ -209,15 +200,12 @@ sub recreate_GUI{ $self->{app_config}->save if $self->{app_config}->dirty; }); - my $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; - if ($run_wizard) { - # On OSX the UI was not initialized correctly if the wizard was called - # before the UI was up and running. - $self->CallAfter(sub { - # Run the config wizard, don't offer the "reset user profile" checkbox. - Slic3r::GUI::config_wizard(1); - }); - } + # On OSX the UI was not initialized correctly if the wizard was called + # before the UI was up and running. + $self->CallAfter(sub { + # Run the config wizard, don't offer the "reset user profile" checkbox. + Slic3r::GUI::config_wizard_startup(1); + }); } sub system_info { diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 0f192c37c6..4aef4d5c16 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.39.0" +#define SLIC3R_VERSION "1.40.0" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp index a7b8a5aa5d..584a374005 100644 --- a/xs/src/slic3r/Config/Snapshot.hpp +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -97,7 +97,7 @@ public: // Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles, // create an index. - const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment); + const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment = ""); void restore_snapshot(const std::string &id, AppConfig &app_config); void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config); diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index d0f2f1019c..5104078b13 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -98,6 +98,10 @@ void AppConfig::load() } } + // Figure out if datadir has legacy presets + auto ini_ver = Semver::parse(get("version")); + m_legacy_datadir = ini_ver ? *ini_ver < Semver(1, 40, 0) : true; + // Override missing or keys with their defaults. this->set_defaults(); m_dirty = false; @@ -240,7 +244,6 @@ bool AppConfig::slic3r_update_avail() const Semver AppConfig::get_slic3r_version() const { - // TODO: move to Semver c-tor (???) auto res = Semver::parse(get("version")); if (! res) { throw std::runtime_error(std::string("Could not parse Slic3r version string in application config.")); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index ffda083ec3..88ba0a6623 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -13,7 +13,7 @@ namespace Slic3r { class AppConfig { public: - AppConfig() : m_dirty(false) { this->reset(); } + AppConfig() : m_dirty(false), m_legacy_datadir(false) { this->reset(); } // Clear and reset to defaults. void reset(); @@ -98,6 +98,8 @@ public: // Get the default config path from Slic3r::data_dir(). static std::string config_path(); + + bool legacy_datadir() const { return m_legacy_datadir; } // Does the config file exist? static bool exists(); @@ -109,6 +111,8 @@ private: VendorMap m_vendors; // Has any value been modified since the config.ini has been last saved or loaded? bool m_dirty; + // Whether the existing version is before system profiles & configuration updating + bool m_legacy_datadir; }; }; // namespace Slic3r diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 5b49fc0258..a059d234b4 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -1,8 +1,8 @@ #include "ConfigWizard_private.hpp" -#include // XXX #include #include +#include #include #include @@ -17,6 +17,7 @@ #include "GUI.hpp" #include "slic3r/Utils/PresetUpdater.hpp" +// TODO: Wizard vs Assistant namespace Slic3r { namespace GUI { @@ -54,48 +55,80 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, cons const auto vendor_id = vendor.id; const auto &models = vendor.models; + auto *sizer = new wxBoxSizer(wxVERTICAL); + auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20); printer_grid->SetFlexibleDirection(wxVERTICAL); - SetSizer(printer_grid); + sizer->Add(printer_grid); auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); namefont.SetWeight(wxFONTWEIGHT_BOLD); for (const auto &model : models) { auto *panel = new wxPanel(this); - auto *sizer = new wxBoxSizer(wxVERTICAL); - panel->SetSizer(sizer); + auto *col_sizer = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(col_sizer); auto *title = new wxStaticText(panel, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); title->SetFont(namefont); - sizer->Add(title, 0, wxBOTTOM, 3); + col_sizer->Add(title, 0, wxBOTTOM, 3); auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model.id); wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG); auto *bitmap_widget = new wxStaticBitmap(panel, wxID_ANY, bitmap); - sizer->Add(bitmap_widget, 0, wxBOTTOM, 3); + col_sizer->Add(bitmap_widget, 0, wxBOTTOM, 3); - sizer->AddSpacer(20); + col_sizer->AddSpacer(20); const auto model_id = model.id; for (const auto &variant : model.variants) { - const auto variant_name = variant.name; - auto *cbox = new wxCheckBox(panel, wxID_ANY, wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle")))); - bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant_name); + const auto label = wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle"))); + auto *cbox = new Checkbox(panel, label, model_id, variant.name); + const size_t idx = cboxes.size(); + cboxes.push_back(cbox); + bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant.name); variants_checked += enabled; cbox->SetValue(enabled); - sizer->Add(cbox, 0, wxBOTTOM, 3); - cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { - this->variants_checked += event.IsChecked() ? 1 : -1; - PrinterPickerEvent evt(EVT_PRINTER_PICK, this->GetId(), std::move(vendor_id), std::move(model_id), std::move(variant_name), event.IsChecked()); - this->AddPendingEvent(evt); + col_sizer->Add(cbox, 0, wxBOTTOM, 3); + cbox->Bind(wxEVT_CHECKBOX, [this, idx](wxCommandEvent &event) { + if (idx >= this->cboxes.size()) { return; } + this->on_checkbox(this->cboxes[idx], event.IsChecked()); }); } printer_grid->Add(panel); } + auto *all_none_sizer = new wxBoxSizer(wxHORIZONTAL); + auto *sel_all = new wxButton(this, wxID_ANY, _(L("Select all"))); + auto *sel_none = new wxButton(this, wxID_ANY, _(L("Select none"))); + sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true); }); + sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); + all_none_sizer->AddStretchSpacer(); + all_none_sizer->Add(sel_all); + all_none_sizer->Add(sel_none); + sizer->AddStretchSpacer(); + sizer->Add(all_none_sizer, 0, wxEXPAND); + + SetSizer(sizer); +} + +void PrinterPicker::select_all(bool select) +{ + for (const auto &cb : cboxes) { + if (cb->GetValue() != select) { + cb->SetValue(select); + on_checkbox(cb, select); + } + } +} + +void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) +{ + variants_checked += checked ? 1 : -1; + PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); + AddPendingEvent(evt); } @@ -176,15 +209,10 @@ PageWelcome::PageWelcome(ConfigWizard *parent) : { append_text(_(L("Hello, welcome to Slic3r Prusa Edition! TODO: This text."))); - // const PresetBundle &bundle = wizard_p()->bundle_vendors; - // const auto &vendors = bundle.vendors; const auto &vendors = wizard_p()->vendors; - // const auto vendor_prusa = std::find(vendors.cbegin(), vendors.cend(), VendorProfile("PrusaResearch")); const auto vendor_prusa = vendors.find("PrusaResearch"); if (vendor_prusa != vendors.cend()) { - const auto &models = vendor_prusa->second.models; - AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors); @@ -550,6 +578,17 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) // 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("MK2S", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2S", "0.4") }, + { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2S", "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_vendors() { const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor"; @@ -569,13 +608,28 @@ void ConfigWizard::priv::load_vendors() const auto id = it->path().stem().string(); if (vendors.find(id) == vendors.end()) { auto vp = VendorProfile::from_ini(it->path()); - vendors_rsrc[vp.id] = it->path(); + vendors_rsrc[vp.id] = it->path().filename().string(); vendors[vp.id] = std::move(vp); } } } - appconfig_vendors.set_vendors(*GUI::get_app_config()); + // Load up the set of vendors / models / variants the user has had enabled up till now + const AppConfig *app_config = GUI::get_app_config(); + if (! app_config->legacy_datadir()) { + appconfig_vendors.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 (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) { + auto needle = legacy_preset_map.find(it->path().filename().string()); + if (needle == legacy_preset_map.end()) { continue; } + + const auto &model = needle->second.first; + const auto &variant = needle->second.second; + appconfig_vendors.set_variant("PrusaResearch", model, variant, true); + } + } } void ConfigWizard::priv::index_refresh() @@ -639,22 +693,28 @@ void ConfigWizard::priv::on_custom_setup() set_page(page_firmware); } -void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle) +void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, PresetUpdater *updater) { const bool is_custom_setup = page_welcome->page_next() == page_firmware; if (! is_custom_setup) { const auto enabled_vendors = appconfig_vendors.vendors(); + + // Install bundles from resources if needed: + std::vector install_bundles; for (const auto &vendor_rsrc : vendors_rsrc) { const auto vendor = enabled_vendors.find(vendor_rsrc.first); if (vendor == enabled_vendors.end()) { continue; } - + size_t size_sum = 0; for (const auto &model : vendor->second) { size_sum += model.second.size(); } if (size_sum == 0) { continue; } // This vendor needs to be installed - PresetBundle::install_vendor_configbundle(vendor_rsrc.second); + install_bundles.emplace_back(vendor_rsrc.second); + } + if (install_bundles.size() > 0) { + updater->install_bundles_rsrc(app_config, std::move(install_bundles)); } app_config->set_vendors(appconfig_vendors); @@ -732,14 +792,10 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : ConfigWizard::~ConfigWizard() {} -bool ConfigWizard::run(wxWindow *parent, PresetBundle *preset_bundle) +void ConfigWizard::run(PresetBundle *preset_bundle, PresetUpdater *updater) { - ConfigWizard wizard(parent); - if (wizard.ShowModal() == wxID_OK) { - wizard.p->apply_config(GUI::get_app_config(), preset_bundle); - return true; - } else { - return false; + if (ShowModal() == wxID_OK) { + p->apply_config(GUI::get_app_config(), preset_bundle, updater); } } diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp index 40ecf09a1e..b34467011d 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -8,6 +8,7 @@ namespace Slic3r { class PresetBundle; +class PresetUpdater; namespace GUI { @@ -22,7 +23,7 @@ public: ConfigWizard &operator=(const ConfigWizard &) = delete; ~ConfigWizard(); - static bool run(wxWindow *parent, PresetBundle *preset_bundle); + void run(PresetBundle *preset_bundle, PresetUpdater *updater); private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index c93e0a80d5..8aab1cc19b 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -15,6 +15,7 @@ #include #include "libslic3r/PrintConfig.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" #include "AppConfig.hpp" #include "Preset.hpp" #include "BedShapeDialog.hpp" @@ -24,8 +25,6 @@ namespace fs = boost::filesystem; namespace Slic3r { namespace GUI { -// typedef std::unordered_map VendorMap; - enum { CONTENT_WIDTH = 500, @@ -38,9 +37,26 @@ enum { struct PrinterPicker: wxPanel { + struct Checkbox : wxCheckBox + { + Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) : + wxCheckBox(parent, wxID_ANY, label), + model(model), + variant(variant) + {} + + std::string model; + std::string variant; + }; + + const std::string vendor_id; + std::vector cboxes; unsigned variants_checked; PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors); + + void select_all(bool select); + void on_checkbox(const Checkbox *cbox, bool checked); }; struct ConfigWizardPage: wxPanel @@ -174,7 +190,7 @@ struct ConfigWizard::priv ConfigWizard *q; AppConfig appconfig_vendors; // TODO: use order-preserving container std::unordered_map vendors; // TODO: just set? - std::unordered_map vendors_rsrc; + std::unordered_map vendors_rsrc; std::unique_ptr custom_config; wxBoxSizer *topsizer = nullptr; @@ -208,7 +224,7 @@ struct ConfigWizard::priv void on_other_vendors(); void on_custom_setup(); - void apply_config(AppConfig *app_config, PresetBundle *preset_bundle); + void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, PresetUpdater *updater); }; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index d70b47840b..916c407aff 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include "wxExtensions.hpp" @@ -50,6 +51,7 @@ #include "Preferences.hpp" #include "PresetBundle.hpp" +#include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" namespace Slic3r { namespace GUI { @@ -179,6 +181,7 @@ wxFrame *g_wxMainFrame = nullptr; wxNotebook *g_wxTabPanel = nullptr; AppConfig *g_AppConfig = nullptr; PresetBundle *g_PresetBundle= nullptr; +PresetUpdater *g_PresetUpdater = nullptr; std::vector g_tabs_list; @@ -212,6 +215,11 @@ void set_preset_bundle(PresetBundle *preset_bundle) g_PresetBundle = preset_bundle; } +void set_preset_updater(PresetUpdater *updater) +{ + g_PresetUpdater = updater; +} + std::vector& get_tabs_list() { return g_tabs_list; @@ -442,23 +450,51 @@ bool check_unsaved_changes() return dialog->ShowModal() == wxID_YES; } -bool config_wizard(bool fresh_start) +void config_wizard_startup(bool app_config_exists) +{ + if (! app_config_exists || g_PresetBundle->has_defauls_only()) { + config_wizard(true); + } else if (g_AppConfig->legacy_datadir()) { + // Looks like user has legacy pre-vendorbundle data directory, + // explain what this is and run the wizard + + const auto msg = _(L("Configuration update")); + const auto ext_msg = _(L( + "Slic3r PE now uses an updated configuration structure.\n\n" + + "So called 'System presets' have been introduced, which hold the built-in default settings for various " + "printers. These System presets cannot be modified, instead, users now may create their" + "own presets inheriting settings from one of the System presets.\n" + "An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n" + + // TODO: Assistant vs Wizard + "Please proceed with the Configuration wizard that follows to set up the new presets " + "and to choose whether to enable automatic preset updates." + )); + wxMessageDialog dlg(NULL, msg, _(L("Configuration update")), wxOK|wxCENTRE); + dlg.SetExtendedMessage(ext_msg); + const auto res = dlg.ShowModal(); + + config_wizard(true); + } +} + +void config_wizard(bool fresh_start) // TODO: fresh_start useful ? { if (g_wxMainFrame == nullptr) throw std::runtime_error("Main frame not set"); // Exit wizard if there are unsaved changes and the user cancels the action. if (! check_unsaved_changes()) - return false; + return; - // TODO: Offer "reset user profile" ??? - if (! ConfigWizard::run(g_wxMainFrame, g_PresetBundle)) - return false; + // TODO: Offer "reset user profile" ??? + ConfigWizard wizard(g_wxMainFrame); + wizard.run(g_PresetBundle, g_PresetUpdater); // Load the currently selected preset into the GUI, update the preset selection box. for (Tab *tab : g_tabs_list) tab->load_current_preset(); - return true; } void open_preferences_dialog(int event_preferences) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 2a3667eb3e..6a23ed4eb8 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -25,6 +25,7 @@ namespace Slic3r { class PresetBundle; class PresetCollection; class AppConfig; +class PresetUpdater; class DynamicPrintConfig; class TabIface; @@ -76,6 +77,7 @@ void set_main_frame(wxFrame *main_frame); void set_tab_panel(wxNotebook *tab_panel); void set_app_config(AppConfig *app_config); void set_preset_bundle(PresetBundle *preset_bundle); +void set_preset_updater(PresetUpdater *updater); AppConfig* get_app_config(); wxApp* get_app(); @@ -88,8 +90,11 @@ extern void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int // to notify the user whether he is aware that some preset changes will be lost. extern bool check_unsaved_changes(); -// Opens the first-time configuration wizard, returns true if wizard is finished & accepted. -extern bool config_wizard(bool fresh_start); +// Checks if configuration wizard needs to run, calls config_wizard if so +extern void config_wizard_startup(bool app_config_exists); + +// Opens the configuration wizard, returns true if wizard is finished & accepted. +extern void config_wizard(bool fresh_start); // Create "Preferences" dialog after selecting menu "Preferences" in Perl part extern void open_preferences_dialog(int event_preferences); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 257c7f552e..3291af7e03 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -51,6 +51,11 @@ struct Update version(version) {} + Update(fs::path &&source, fs::path &&target) : + source(source), + target(std::move(target)) + {} + std::string name() { return source.stem().string(); } }; @@ -83,6 +88,7 @@ struct PresetUpdater::priv void check_install_indices() const; Updates config_update() const; + void perform_updates(AppConfig *app_config, Updates &&updates) const; }; PresetUpdater::priv::priv(int event, AppConfig *app_config) : @@ -121,6 +127,7 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe .on_complete([&](std::string body, unsigned http_status) { fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); file.write(body.c_str(), body.size()); + file.close(); fs::rename(tmp_path, target_path); res = true; }) @@ -269,6 +276,26 @@ Updates PresetUpdater::priv::config_update() const return updates; } +void PresetUpdater::priv::perform_updates(AppConfig *app_config, Updates &&updates) const +{ + SnapshotDB::singleton().take_snapshot(*app_config, Snapshot::SNAPSHOT_UPGRADE); + + for (const auto &update : updates) { + fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); + + PresetBundle bundle; + bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + + auto preset_remover = [](const Preset &preset) { + fs::remove(preset.file); + }; + + for (const auto &preset : bundle.prints) { preset_remover(preset); } + for (const auto &preset : bundle.filaments) { preset_remover(preset); } + for (const auto &preset : bundle.printers) { preset_remover(preset); } + } +} + PresetUpdater::PresetUpdater(int version_online_event, AppConfig *app_config) : p(new priv(version_online_event, app_config)) @@ -306,7 +333,7 @@ void PresetUpdater::config_update(AppConfig *app_config) { if (! p->enabled_config_update) { return; } - const auto updates = p->config_update(); + auto updates = p->config_update(); if (updates.size() > 0) { const auto msg = _(L("Configuration update is available. Would you like to install it?")); @@ -330,26 +357,23 @@ void PresetUpdater::config_update(AppConfig *app_config) std::cerr << "After modal" << std::endl; if (res == wxID_YES) { // User gave clearance, updates are go - - SnapshotDB::singleton().take_snapshot(*app_config, Snapshot::SNAPSHOT_UPGRADE, ""); - - for (const auto &update : updates) { - fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); - - PresetBundle bundle; - bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); - - auto preset_remover = [](const Preset &preset) { - fs::remove(preset.file); - }; - - for (const auto &preset : bundle.prints) { preset_remover(preset); } - for (const auto &preset : bundle.filaments) { preset_remover(preset); } - for (const auto &preset : bundle.printers) { preset_remover(preset); } - } + p->perform_updates(app_config, std::move(updates)); } } } +void PresetUpdater::install_bundles_rsrc(AppConfig *app_config, std::vector &&bundles) +{ + Updates updates; + + for (const auto &bundle : bundles) { + auto path_in_rsrc = p->rsrc_path / bundle; + auto path_in_vendors = p->vendor_path / bundle; + updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors)); + } + + p->perform_updates(app_config, std::move(updates)); +} + } diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp index 966dd14647..1499570db7 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -2,6 +2,7 @@ #define slic3r_PresetUpdate_hpp_ #include +#include namespace Slic3r { @@ -20,8 +21,8 @@ public: ~PresetUpdater(); void sync(AppConfig *app_config, PresetBundle *preset_bundle); - void config_update(AppConfig *app_config); + void install_bundles_rsrc(AppConfig *app_config, std::vector &&bundles); private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index 2c27ce9826..3e9276be6a 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -32,8 +32,6 @@ public: ver.prerelease = prerelease ? std::strcpy(ver.prerelease, prerelease->c_str()) : nullptr; } - // TODO: throwing ctor ??? - static boost::optional parse(const std::string &str) { semver_t ver = semver_zero(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 46e4ace83f..0d9f0b62e7 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -60,10 +60,10 @@ void set_app_config(AppConfig *app_config) bool check_unsaved_changes() %code%{ RETVAL=Slic3r::GUI::check_unsaved_changes(); %}; -bool config_wizard(int fresh_start) +void config_wizard_startup(int app_config_exists) %code%{ try { - RETVAL = Slic3r::GUI::config_wizard(fresh_start != 0); + Slic3r::GUI::config_wizard_startup(app_config_exists != 0); } catch (std::exception& e) { croak("%s\n", e.what()); } @@ -75,6 +75,9 @@ void open_preferences_dialog(int preferences_event) void set_preset_bundle(PresetBundle *preset_bundle) %code%{ Slic3r::GUI::set_preset_bundle(preset_bundle); %}; +void set_preset_updater(PresetUpdater* updater) + %code%{ Slic3r::GUI::set_preset_updater(updater); %}; + void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_sizer) %code%{ Slic3r::GUI::add_frequently_changed_parameters((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), diff --git a/xs/xsp/my.map b/xs/xsp/my.map index c1ca58827b..393338f3b7 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -238,7 +238,6 @@ Ref O_OBJECT_SLIC3R_T PresetUpdater* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T OctoPrint* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 0214a158d6..2539811b3c 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -208,6 +208,8 @@ %typemap{Ref}{simple}; %typemap{PresetBundle*}; %typemap{Ref}{simple}; +%typemap{PresetUpdater*}; +%typemap{Ref}{simple}; %typemap{PresetHints*}; %typemap{Ref}{simple}; %typemap{TabIface*}; From d26c8e5336bbc92b79875141f43182ccb7461616 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 16 Apr 2018 17:43:23 +0200 Subject: [PATCH 026/145] Fix: Avoid the infamous `major` & `minor` macros on GCC --- xs/src/slic3r/Utils/Semver.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index 762f5bd70b..bf3c78964c 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -77,8 +77,8 @@ public: ~Semver() { ::semver_free(&ver); } // const accessors - int major() const { return ver.major; } - int minor() const { return ver.minor; } + int maj() const { return ver.major; } + int min() const { return ver.minor; } int patch() const { return ver.patch; } const char* prerelease() const { return ver.prerelease; } const char* metadata() const { return ver.metadata; } From 37cf839b2779bb5f14b0cdf7a408956e580e80cd Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 16 Apr 2018 18:33:33 +0200 Subject: [PATCH 027/145] ConfigWizard: Fix regression --- xs/src/slic3r/GUI/ConfigWizard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index cd1ed64cb7..467c13e16e 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -50,9 +50,9 @@ wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors) : wxPanel(parent), + vendor_id(vendor.id), variants_checked(0) { - const auto vendor_id = vendor.id; const auto &models = vendor.models; auto *sizer = new wxBoxSizer(wxVERTICAL); From d254c39a7743aaf202e8ba2bba2141c456b0e3b0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 17 Apr 2018 10:15:48 +0200 Subject: [PATCH 028/145] Added "smart" setting of label color --- xs/src/slic3r/GUI/Field.cpp | 9 ++------- xs/src/slic3r/GUI/Field.hpp | 18 +++++++++++++++--- xs/src/slic3r/GUI/Tab.cpp | 27 +++++++++++++++------------ xs/src/slic3r/GUI/Tab.hpp | 5 +++++ 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index a32943da05..e959c7966c 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -20,23 +20,18 @@ namespace Slic3r { namespace GUI { void Field::PostInitialize(){ auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(16, 16), wxNO_BORDER); + m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(16, 16), wxNO_BORDER); if (wxMSW) { m_Undo_btn->SetBackgroundColour(color); m_Undo_to_sys_btn->SetBackgroundColour(color); } -// m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); BUILD(); } -// void Field::set_nonsys_btn_icon(const wxBitmap& icon){ -// m_Undo_to_sys_btn->SetBitmap(icon); -// } - void Field::on_kill_focus(wxEvent& event) { // Without this, there will be nasty focus bugs on Windows. // Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 2b2646b7cb..292bfd81f8 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -90,9 +90,7 @@ public: virtual void enable() = 0; virtual void disable() = 0; - wxStaticText* m_Label = nullptr; - - /// Fires the enable or disable function, based on the input. + /// Fires the enable or disable function, based on the input. inline void toggle(bool en) { en ? enable() : disable(); } virtual wxString get_tooltip_text(const wxString& default_string); @@ -137,6 +135,16 @@ public: return false; } + bool set_label_colour(const wxColour *clr) { + if (m_Label == nullptr) return false; + if (m_label_color != clr) { + m_label_color = clr; + m_Label->SetForegroundColour(*clr); + m_Label->Refresh(true); + } + return false; + } + protected: wxButton* m_Undo_btn = nullptr; // Bitmap for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. @@ -145,6 +153,10 @@ protected: // Bitmap for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. const wxBitmap* m_undo_to_sys_bitmap = nullptr; + wxStaticText* m_Label = nullptr; + // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. + const wxColour* m_label_color; + friend class OptionsGroup; }; diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6ed124eaa4..8252437495 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -23,6 +23,7 @@ #include #include "wxExtensions.hpp" +#include namespace Slic3r { namespace GUI { @@ -115,6 +116,11 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_undo_to_sys_btn->SetBitmap(m_bmp_white_bullet); m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); + // Colors for ui "decoration" + m_sys_label_clr = get_sys_label_clr(); + m_modified_label_clr = get_modified_label_clr(); + m_default_text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxBOTTOM, 3); m_hsizer->Add(m_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); @@ -344,14 +350,14 @@ void Tab::update_changed_ui() bool is_modified_value = true; const wxBitmap *sys_icon = &m_bmp_value_lock; const wxBitmap *icon = &m_bmp_value_revert; - wxColour color = get_sys_label_clr(); + const wxColour *color = &m_sys_label_clr; if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { is_nonsys_value = true; sys_icon = m_bmp_non_system; if(find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) - color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + color = &m_default_text_clr; else - color = get_modified_label_clr(); + color = &m_modified_label_clr; } if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) { @@ -360,7 +366,7 @@ void Tab::update_changed_ui() } if (opt_key == "bed_shape" || opt_key == "compatible_printers") { if (m_colored_Label != nullptr) { - m_colored_Label->SetForegroundColour(color); + m_colored_Label->SetForegroundColour(*color); m_colored_Label->Refresh(true); } continue; @@ -372,10 +378,7 @@ void Tab::update_changed_ui() field->m_is_modified_value = is_modified_value; field->set_undo_bitmap(icon); field->set_undo_to_sys_bitmap(sys_icon); - if (field->m_Label != nullptr){ - field->m_Label->SetForegroundColour(color); - field->m_Label->Refresh(true); - } + field->set_label_colour(color); } Thaw(); @@ -425,15 +428,13 @@ void Tab::update_full_options_list() void Tab::update_sys_ui_after_sel_preset() { + const wxColour* clr = &m_default_text_clr; for (const auto opt_key : m_full_options_list){ Field* field = get_field(opt_key); if (field != nullptr){ field->set_undo_to_sys_bitmap(m_bmp_non_system); field->m_is_nonsys_value = true; - if (field->m_Label != nullptr){ - field->m_Label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); - field->m_Label->Refresh(true); - } + field->set_label_colour(clr); } } m_sys_options.resize(0); @@ -2039,6 +2040,8 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr void Tab::OnTreeSelChange(wxTreeEvent& event) { if (m_disable_tree_sel_changed_event) return; + wxWindowUpdateLocker noUpdates(this); + Page* page = nullptr; auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); for (auto p : m_pages) diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index df91ef01be..4668c1238a 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -112,6 +112,11 @@ protected: wxBitmap m_bmp_value_revert; wxBitmap m_bmp_value_unmodified; + // Colors for ui "decoration" + wxColour m_sys_label_clr; + wxColour m_modified_label_clr; + wxColour m_default_text_clr; + int m_icon_count; std::map m_icon_index; // Map from an icon file name to its index std::vector m_pages; From b506aa11fab60fa5413fc82d3615e15a7a9624ea Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 17 Apr 2018 10:28:32 +0200 Subject: [PATCH 029/145] PresetUpdater: Fix: Compare versions when installing indices --- xs/src/slic3r/Utils/PresetUpdater.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 3291af7e03..dc3b95d798 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -220,9 +220,16 @@ void PresetUpdater::priv::check_install_indices() const if (path.extension() == ".idx") { const auto path_in_cache = cache_path / path.filename(); - // TODO: compare versions if (! fs::exists(path_in_cache)) { fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists); + } else { + Index idx_rsrc, idx_cache; + idx_rsrc.load(path); + idx_cache.load(path_in_cache); + + if (idx_cache.version() < idx_rsrc.version()) { + fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists); + } } } } @@ -267,8 +274,6 @@ Updates PresetUpdater::priv::config_update() const const auto cached_vp = VendorProfile::from_ini(path_in_cache, false); if (cached_vp.config_version == recommended->config_version) { updates.emplace_back(std::move(path_in_cache), bundle_path, *recommended); - } else { - // XXX: ? } } } From a05c4402632756b8401f1fe909f043349b570f02 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 17 Apr 2018 10:55:18 +0200 Subject: [PATCH 030/145] Fixed potential crashes due to the Perl worker thread releasing memory allocated by the GUI thread. --- lib/Slic3r.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index e55e24a7ad..ab06a64554 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -161,7 +161,12 @@ sub thread_cleanup { *Slic3r::Print::SupportMaterial2::DESTROY = sub {}; *Slic3r::TriangleMesh::DESTROY = sub {}; *Slic3r::GUI::AppConfig::DESTROY = sub {}; + *Slic3r::GUI::GCodePreviewData::DESTROY = sub {}; + *Slic3r::GUI::OctoPrint::DESTROY = sub {}; *Slic3r::GUI::PresetBundle::DESTROY = sub {}; + *Slic3r::GUI::PresetHints::DESTROY = sub {}; + *Slic3r::GUI::PresetUpdater::DESTROY = sub {}; + *Slic3r::GUI::TabIface::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } From 98785e47b18c7052245e91966915c99f984aae05 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 17 Apr 2018 10:55:58 +0200 Subject: [PATCH 031/145] Removed the "The Wipe Tower currently supports only:\n" "- first layer height 0.2mm\n" "- layer height from 0.15mm to 0.35mm\n" message as the new wipe tower is more generic. --- xs/src/slic3r/GUI/Tab.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 851022489b..3476a897d1 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1015,29 +1015,6 @@ void TabPrint::update() on_value_change("fill_density", fill_density); } - auto first_layer_height = m_config->option("first_layer_height")->value; - auto layer_height = m_config->opt_float("layer_height"); - if (m_config->opt_bool("wipe_tower") && - (first_layer_height != 0.2 || layer_height < 0.15 || layer_height > 0.35)) { - wxString msg_text = _(L("The Wipe Tower currently supports only:\n" - "- first layer height 0.2mm\n" - "- layer height from 0.15mm to 0.35mm\n" - "\nShall I adjust those settings in order to enable the Wipe Tower?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - const auto &val = *m_config->option("first_layer_height"); - auto percent = val.percent; - new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, percent)); - - if (m_config->opt_float("layer_height") < 0.15) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.15)); - if (m_config->opt_float("layer_height") > 0.35) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.35)); - } - else - new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); - load_config(new_conf); - } - if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && m_config->opt_float("support_material_contact_distance") > 0. && (m_config->opt_int("support_material_extruder") != 0 || m_config->opt_int("support_material_interface_extruder") != 0)) { From 5e6cc5ddcbc5c937e8434edb0176aceb4d98b394 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 17 Apr 2018 11:06:15 +0200 Subject: [PATCH 032/145] Updated max_print_height of PrusaResearch.ini --- resources/profiles/PrusaResearch.ini | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 0da7f22d14..b487aae4c8 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -69,6 +69,7 @@ infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% interface_shells = 0 +max_print_height = 200 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 @@ -120,7 +121,7 @@ thin_walls = 0 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 travel_speed = 180 -wipe_tower = 0 +wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 @@ -995,6 +996,7 @@ inherits = *common* end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 @@ -1006,6 +1008,7 @@ printer_variant = 0.25 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.10mm DETAIL MK3 @@ -1017,6 +1020,7 @@ printer_variant = 0.6 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 From 6286c9ee7c8cd2f03b4de3a38e26d96f8c7108ec Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 17 Apr 2018 11:54:59 +0200 Subject: [PATCH 033/145] ConfigWizard & updating: Fixes & cleanups --- resources/profiles/PrusaResearch.idx | 1 - xs/src/slic3r/GUI/ConfigWizard.cpp | 34 ++++++++++++++++++++------- xs/src/slic3r/GUI/ConfigWizard.hpp | 2 ++ xs/src/slic3r/GUI/GUI.cpp | 11 ++------- xs/src/slic3r/Utils/PresetUpdater.cpp | 6 ++--- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 3bc2aeffdd..b43e266635 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,7 +1,6 @@ # This is an example configuration version index. # The index contains version numbers min_slic3r_version =1.39.0 -max_slic3r_version= 1.39.5 1.1.1 1.1.0 0.2.0-alpha "some test comment" diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 467c13e16e..00f8399e68 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -203,7 +203,7 @@ void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable) // Wizard pages PageWelcome::PageWelcome(ConfigWizard *parent) : - ConfigWizardPage(parent, _(L("Welcome to the Slic3r Configuration assistant")), _(L("Welcome"))), + ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))), printer_picker(nullptr), others_buttons(new wxPanel(parent)) { @@ -257,16 +257,26 @@ PageUpdate::PageUpdate(ConfigWizard *parent) : preset_update(true) { const AppConfig *app_config = GUI::get_app_config(); + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); - append_text(_(L("TODO: text"))); - auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for Slic3r updates"))); + auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for application updates"))); box_slic3r->SetValue(app_config->get("version_check") == "1"); append(box_slic3r); + append_text(_(L("If enabled, Slic3r checks for new versions of Slic3r PE 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."))); + + append_spacer(VERTICAL_SPACING); - append_text(_(L("TODO: text"))); 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(_(L("If enabled, Slic3r 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."))); + 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(CONTENT_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(); }); @@ -277,15 +287,12 @@ PageVendors::PageVendors(ConfigWizard *parent) : { append_text(_(L("Pick another vendor supported by Slic3r PE:"))); - // const PresetBundle &bundle = wizard_p()->bundle_vendors; - // const auto &vendors = wizard_p()->vendors; auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); boldfont.SetWeight(wxFONTWEIGHT_BOLD); AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; wxArrayString choices_vendors; - // for (const auto &vendor : vendors) { for (const auto vendor_pair : wizard_p()->vendors) { const auto &vendor = vendor_pair.second; if (vendor.id == "PrusaResearch") { continue; } @@ -737,7 +744,7 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // Public ConfigWizard::ConfigWizard(wxWindow *parent, bool fresh_start) : - wxDialog(parent, wxID_ANY, _(L("Configuration Assistant")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + wxDialog(parent, wxID_ANY, name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), p(new priv(this)) { p->fresh_start = fresh_start; @@ -804,5 +811,16 @@ void ConfigWizard::run(PresetBundle *preset_bundle, PresetUpdater *updater) } +const wxString& ConfigWizard::name() +{ + // A different naming convention is used for the Wizard on Windows vs. OSX & GTK. +#if WIN32 + static const wxString config_wizard_name = _(L("Configuration Wizard")); +#else + static const wxString config_wizard_name = _(L("Configuration Assistant")); +#endif + return config_wizard_name; +} + } } diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp index d1d994ac1e..66c8b3a95a 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -24,6 +24,8 @@ public: ~ConfigWizard(); void run(PresetBundle *preset_bundle, PresetUpdater *updater); + + static const wxString& name(); private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 96d1371658..bfdd46287f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -377,16 +377,9 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l auto local_menu = new wxMenu(); wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt); - // A different naming convention is used for the Wizard on Windows vs. OSX & GTK. -#if WIN32 - auto config_wizard_menu = _(L("Configuration Wizard")); - auto config_wizard_tooltip = _(L("Run configuration wizard")); -#else - auto config_wizard_menu = _(L("Configuration Assistant")); - auto config_wizard_tooltip = _(L("Run configuration Assistant")); -#endif + const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), ConfigWizard::name()); // Cmd+, is standard on OS X - what about other operating systems? - local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_menu + "\u2026", config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuWizard, ConfigWizard::name() + "\u2026", config_wizard_tooltip); local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots\u2026")), _(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 + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index dc3b95d798..939be863c4 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -114,8 +115,6 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe { std::cerr << "get_file(): " << url << " -> " << target_path << std::endl; - // TODO: Proper caching - bool res = false; fs::path tmp_path = target_path; tmp_path += TMP_EXTENSION; @@ -251,7 +250,8 @@ Updates PresetUpdater::priv::config_update() const const auto ver_current = idx.find(vp.config_version); if (ver_current == idx.end()) { - // TODO: throw / ignore ? + BOOST_LOG_TRIVIAL(warning) << boost::format("Preset bundle (`%1%`) version not found in index: %2%") % idx.vendor() % vp.config_version.to_string(); + continue; } const auto recommended = idx.recommended(); From 3bedcf441390a12e10a3d6e1c9c94421c1dbb9e7 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 17 Apr 2018 15:04:14 +0200 Subject: [PATCH 034/145] Tweaks in generation of rendering geometry for preview toolpaths. Fixes #240 and #348 --- xs/src/slic3r/GUI/3DScene.cpp | 178 ++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 72 deletions(-) diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index aa86ae203f..7f5e632bba 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -788,15 +788,14 @@ static void thick_lines_to_indexed_vertex_array( #define TOP 2 #define BOTTOM 3 - Line prev_line; // right, left, top, bottom int idx_prev[4] = { -1, -1, -1, -1 }; double bottom_z_prev = 0.; Pointf b1_prev; - Pointf b2_prev; Vectorf v_prev; int idx_initial[4] = { -1, -1, -1, -1 }; double width_initial = 0.; + double bottom_z_initial = 0.0; // loop once more in case of closed loops size_t lines_end = closed ? (lines.size() + 1) : lines.size(); @@ -804,13 +803,18 @@ static void thick_lines_to_indexed_vertex_array( size_t i = (ii == lines.size()) ? 0 : ii; const Line &line = lines[i]; double len = unscale(line.length()); + double inv_len = 1.0 / len; double bottom_z = top_z - heights[i]; - double middle_z = (top_z + bottom_z) / 2.; + double middle_z = 0.5 * (top_z + bottom_z); double width = widths[i]; - + + bool is_first = (ii == 0); + bool is_last = (ii == lines_end - 1); + bool is_closing = closed && is_last; + Vectorf v = Vectorf::new_unscale(line.vector()); - v.scale(1. / len); - + v.scale(inv_len); + Pointf a = Pointf::new_unscale(line.a); Pointf b = Pointf::new_unscale(line.b); Pointf a1 = a; @@ -818,17 +822,19 @@ static void thick_lines_to_indexed_vertex_array( Pointf b1 = b; Pointf b2 = b; { - double dist = width / 2.; // scaled - a1.translate(+dist*v.y, -dist*v.x); - a2.translate(-dist*v.y, +dist*v.x); - b1.translate(+dist*v.y, -dist*v.x); - b2.translate(-dist*v.y, +dist*v.x); + double dist = 0.5 * width; // scaled + double dx = dist * v.x; + double dy = dist * v.y; + a1.translate(+dy, -dx); + a2.translate(-dy, +dx); + b1.translate(+dy, -dx); + b2.translate(-dy, +dx); } // calculate new XY normals Vector n = line.normal(); Vectorf3 xy_right_normal = Vectorf3::new_unscale(n.x, n.y, 0); - xy_right_normal.scale(1.f / len); + xy_right_normal.scale(inv_len); int idx_a[4]; int idx_b[4]; @@ -837,14 +843,21 @@ static void thick_lines_to_indexed_vertex_array( bool bottom_z_different = bottom_z_prev != bottom_z; bottom_z_prev = bottom_z; + if (!is_first && bottom_z_different) + { + // Found a change of the layer thickness -> Add a cap at the end of the previous segment. + volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]); + } + // Share top / bottom vertices if possible. - if (ii == 0) { - idx_a[TOP] = idx_last ++; + if (is_first) { + idx_a[TOP] = idx_last++; volume.push_geometry(a.x, a.y, top_z , 0., 0., 1.); } else { idx_a[TOP] = idx_prev[TOP]; } - if (ii == 0 || bottom_z_different) { + + if (is_first || bottom_z_different) { // Start of the 1st line segment or a change of the layer thickness while maintaining the print_z. idx_a[BOTTOM] = idx_last ++; volume.push_geometry(a.x, a.y, bottom_z, 0., 0., -1.); @@ -852,13 +865,15 @@ static void thick_lines_to_indexed_vertex_array( volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); idx_a[RIGHT] = idx_last ++; volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); - } else { + } + else { idx_a[BOTTOM] = idx_prev[BOTTOM]; } - if (ii == 0) { + if (is_first) { // Start of the 1st line segment. width_initial = width; + bottom_z_initial = bottom_z; memcpy(idx_initial, idx_a, sizeof(int) * 4); } else { // Continuing a previous segment. @@ -866,43 +881,54 @@ static void thick_lines_to_indexed_vertex_array( double v_dot = dot(v_prev, v); bool sharp = v_dot < 0.707; // sin(45 degrees) if (sharp) { - // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. - idx_a[RIGHT] = idx_last ++; - volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); - idx_a[LEFT ] = idx_last ++; - volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); + if (!bottom_z_different) + { + // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. + idx_a[RIGHT] = idx_last++; + volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); + idx_a[LEFT] = idx_last++; + volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); + } } if (v_dot > 0.9) { - // The two successive segments are nearly collinear. - idx_a[LEFT ] = idx_prev[LEFT]; - idx_a[RIGHT] = idx_prev[RIGHT]; - } else if (! sharp) { - // Create a sharp corner with an overshot and average the left / right normals. - // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. - Pointf intersection; - Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection); - a1 = intersection; - a2 = 2. * a - intersection; - assert(length(a1.vector_to(a)) < width); - assert(length(a2.vector_to(a)) < width); - float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6; - float *p_left_prev = n_left_prev + 3; - float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; - float *p_right_prev = n_right_prev + 3; - p_left_prev [0] = float(a2.x); - p_left_prev [1] = float(a2.y); - p_right_prev[0] = float(a1.x); - p_right_prev[1] = float(a1.y); - xy_right_normal.x += n_right_prev[0]; - xy_right_normal.y += n_right_prev[1]; - xy_right_normal.scale(1. / length(xy_right_normal)); - n_left_prev [0] = float(-xy_right_normal.x); - n_left_prev [1] = float(-xy_right_normal.y); - n_right_prev[0] = float( xy_right_normal.x); - n_right_prev[1] = float( xy_right_normal.y); - idx_a[LEFT ] = idx_prev[LEFT ]; - idx_a[RIGHT] = idx_prev[RIGHT]; - } else if (cross(v_prev, v) > 0.) { + if (!bottom_z_different) + { + // The two successive segments are nearly collinear. + idx_a[LEFT ] = idx_prev[LEFT]; + idx_a[RIGHT] = idx_prev[RIGHT]; + } + } + else if (!sharp) { + if (!bottom_z_different) + { + // Create a sharp corner with an overshot and average the left / right normals. + // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. + Pointf intersection; + Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection); + a1 = intersection; + a2 = 2. * a - intersection; + assert(length(a1.vector_to(a)) < width); + assert(length(a2.vector_to(a)) < width); + float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6; + float *p_left_prev = n_left_prev + 3; + float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; + float *p_right_prev = n_right_prev + 3; + p_left_prev [0] = float(a2.x); + p_left_prev [1] = float(a2.y); + p_right_prev[0] = float(a1.x); + p_right_prev[1] = float(a1.y); + xy_right_normal.x += n_right_prev[0]; + xy_right_normal.y += n_right_prev[1]; + xy_right_normal.scale(1. / length(xy_right_normal)); + n_left_prev [0] = float(-xy_right_normal.x); + n_left_prev [1] = float(-xy_right_normal.y); + n_right_prev[0] = float( xy_right_normal.x); + n_right_prev[1] = float( xy_right_normal.y); + idx_a[LEFT ] = idx_prev[LEFT ]; + idx_a[RIGHT] = idx_prev[RIGHT]; + } + } + else if (cross(v_prev, v) > 0.) { // Right turn. Fill in the right turn wedge. volume.push_triangle(idx_prev[RIGHT], idx_a [RIGHT], idx_prev[TOP] ); volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a [RIGHT] ); @@ -911,18 +937,21 @@ static void thick_lines_to_indexed_vertex_array( volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a [LEFT] ); volume.push_triangle(idx_prev[LEFT], idx_a [LEFT], idx_prev[BOTTOM]); } - if (ii == lines.size()) { - if (! sharp) { - // Closing a loop with smooth transition. Unify the closing left / right vertices. - memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT ] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6, sizeof(float) * 6); - memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6); - volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end()); - // Replace the left / right vertex indices to point to the start of the loop. - for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++ u) { - if (volume.quad_indices[u] == idx_prev[LEFT]) - volume.quad_indices[u] = idx_initial[LEFT]; - else if (volume.quad_indices[u] == idx_prev[RIGHT]) - volume.quad_indices[u] = idx_initial[RIGHT]; + if (is_closing) { + if (!sharp) { + if (!bottom_z_different) + { + // Closing a loop with smooth transition. Unify the closing left / right vertices. + memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT ] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6, sizeof(float) * 6); + memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6); + volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end()); + // Replace the left / right vertex indices to point to the start of the loop. + for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++ u) { + if (volume.quad_indices[u] == idx_prev[LEFT]) + volume.quad_indices[u] = idx_initial[LEFT]; + else if (volume.quad_indices[u] == idx_prev[RIGHT]) + volume.quad_indices[u] = idx_initial[RIGHT]; + } } } // This is the last iteration, only required to solve the transition. @@ -931,13 +960,14 @@ static void thick_lines_to_indexed_vertex_array( } // Only new allocate top / bottom vertices, if not closing a loop. - if (closed && ii + 1 == lines.size()) { + if (is_closing) { idx_b[TOP] = idx_initial[TOP]; } else { idx_b[TOP] = idx_last ++; volume.push_geometry(b.x, b.y, top_z , 0., 0., 1.); } - if (closed && ii + 1 == lines.size() && width == width_initial) { + + if (is_closing && (width == width_initial) && (bottom_z == bottom_z_initial)) { idx_b[BOTTOM] = idx_initial[BOTTOM]; } else { idx_b[BOTTOM] = idx_last ++; @@ -949,22 +979,26 @@ static void thick_lines_to_indexed_vertex_array( idx_b[RIGHT ] = idx_last ++; volume.push_geometry(b1.x, b1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); - prev_line = line; memcpy(idx_prev, idx_b, 4 * sizeof(int)); bottom_z_prev = bottom_z; b1_prev = b1; - b2_prev = b2; - v_prev = v; + v_prev = v; + + if (bottom_z_different) + { + // Found a change of the layer thickness -> Add a cap at the beginning of this segment. + volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); + } if (! closed) { // Terminate open paths with caps. - if (i == 0) + if (is_first && !bottom_z_different) volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); // We don't use 'else' because both cases are true if we have only one line. - if (i + 1 == lines.size()) + if (is_last && !bottom_z_different) volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]); } - + // Add quads for a straight hollow tube-like segment. // bottom-right face volume.push_quad(idx_a[BOTTOM], idx_b[BOTTOM], idx_b[RIGHT], idx_a[RIGHT]); From 61ee633cd2136528ee4190b78004a9966171520c Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 17 Apr 2018 16:16:25 +0200 Subject: [PATCH 035/145] Fixed color specular component in shaders --- lib/Slic3r/GUI/3DScene.pm | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 13460cfed2..75c19d89ef 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1753,8 +1753,8 @@ sub _vertex_shader_Gouraud { // normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); #define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.25 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 200.0 +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 // normalized values for (1./1.43, 0.2/1.43, 1./1.43) const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); @@ -1784,15 +1784,9 @@ varying vec3 delta_box_max; void main() { - vec3 eye = -normalize((gl_ModelViewMatrix * gl_Vertex).xyz); - // First transform the normal into camera space and normalize the result. vec3 normal = normalize(gl_NormalMatrix * gl_Normal); - // Now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space. - // Also since we're talking about a directional light, the position field is actually direction. - vec3 halfVector = normalize(LIGHT_TOP_DIR + eye); - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); @@ -1801,7 +1795,7 @@ void main() intensity.y = 0.0; if (NdotL > 0.0) - intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_TOP_SHININESS); + intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); // Perform the same lighting calculation for the 2nd light source (no specular applied). NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); @@ -1926,8 +1920,8 @@ sub _vertex_shader_variable_layer_height { const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); #define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.25 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 200.0 +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); #define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) @@ -1943,15 +1937,9 @@ varying float object_z; void main() { - vec3 eye = -normalize((gl_ModelViewMatrix * gl_Vertex).xyz); - // First transform the normal into camera space and normalize the result. vec3 normal = normalize(gl_NormalMatrix * gl_Normal); - // Now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space. - // Also since we're talking about a directional light, the position field is actually direction. - vec3 halfVector = normalize(LIGHT_TOP_DIR + eye); - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); @@ -1960,7 +1948,7 @@ void main() intensity.y = 0.0; if (NdotL > 0.0) - intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_TOP_SHININESS); + intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); // Perform the same lighting calculation for the 2nd light source (no specular) NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); From df03b8e4e891918b95d9accad90c2e071ce088dd Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 17 Apr 2018 16:59:53 +0200 Subject: [PATCH 036/145] PresetUpdater: Notify about Slic3r updates --- lib/Slic3r/GUI.pm | 11 ++- xs/src/slic3r/GUI/AppConfig.cpp | 29 +------ xs/src/slic3r/GUI/AppConfig.hpp | 6 -- xs/src/slic3r/GUI/ConfigWizard.cpp | 2 +- xs/src/slic3r/GUI/PresetBundle.cpp | 13 --- xs/src/slic3r/GUI/PresetBundle.hpp | 4 - xs/src/slic3r/Utils/PresetUpdater.cpp | 116 ++++++++++++++++++++++---- xs/src/slic3r/Utils/PresetUpdater.hpp | 9 +- xs/xsp/GUI_AppConfig.xsp | 1 - xs/xsp/GUI_Preset.xsp | 8 -- xs/xsp/Utils_PresetUpdater.xsp | 7 +- 11 files changed, 119 insertions(+), 87 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 473fc6b90b..5ff8fdde1e 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -87,6 +87,7 @@ sub OnInit { $self->{app_config} = Slic3r::GUI::AppConfig->new; $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; + Slic3r::GUI::set_app_config($self->{app_config}); # 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 @@ -102,16 +103,18 @@ sub OnInit { $self->{app_config}->save; # my $version_check = $self->{app_config}->get('version_check'); - $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT, $self->{app_config}); + $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT); Slic3r::GUI::set_preset_updater($self->{preset_updater}); - eval { $self->{preset_updater}->config_update($self->{app_config}) }; + eval { + $self->{preset_updater}->slic3r_update_notify(); + $self->{preset_updater}->config_update(); + }; if ($@) { warn $@ . "\n"; fatal_error(undef, $@); } # my $slic3r_update = $self->{app_config}->slic3r_update_avail; - Slic3r::GUI::set_app_config($self->{app_config}); Slic3r::GUI::load_language(); # Suppress the '- default -' presets. @@ -150,7 +153,7 @@ sub OnInit { Slic3r::GUI::config_wizard_startup($app_conf_exists); # TODO: call periodically? - $self->{preset_updater}->sync($self->{app_config}, $self->{preset_bundle}); + $self->{preset_updater}->sync($self->{preset_bundle}); }); # The following event is emited by the C++ menu implementation of application language change. diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 5104078b13..100b2d69b3 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -51,7 +51,7 @@ void AppConfig::set_defaults() set("version_check", "1"); // TODO: proper URL if (get("version_check_url").empty()) - set("version_check_url", "https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/e9187c3e15ceaf1a90f29b7c43cf3ccc746140f0/slic3rPE.version"); + set("version_check_url", "https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/2f05a64db19e45a7f8fe2cedeff555d544af679b/slic3rPE.version"); if (get("preset_update").empty()) set("preset_update", "1"); @@ -230,33 +230,6 @@ void AppConfig::reset_selections() } } -bool AppConfig::version_check_enabled() const -{ - return get("version_check") == "1"; -} - -bool AppConfig::slic3r_update_avail() const -{ - // FIXME: Update with Semver - // TODO: probably need to move semver to libslic3r - return version_check_enabled() && get("version_online") != SLIC3R_VERSION; -} - -Semver AppConfig::get_slic3r_version() const -{ - auto res = Semver::parse(get("version")); - if (! res) { - throw std::runtime_error(std::string("Could not parse Slic3r version string in application config.")); - } else { - return *res; - } -} - -void AppConfig::set_slic3r_version(const Semver &version) -{ - set("version", version.to_string()); -} - std::string AppConfig::config_path() { return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index 88ba0a6623..6dcfec0461 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -90,12 +90,6 @@ public: // the first non-default preset when called. void reset_selections(); - // Whether the Slic3r version available online differs from this one - bool version_check_enabled() const; - bool slic3r_update_avail() const; - Semver get_slic3r_version() const; - void set_slic3r_version(const Semver &version); - // Get the default config path from Slic3r::data_dir(). static std::string config_path(); diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 00f8399e68..c4ca7a886c 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -721,7 +721,7 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese install_bundles.emplace_back(vendor_rsrc.second); } if (install_bundles.size() > 0) { - updater->install_bundles_rsrc(app_config, std::move(install_bundles)); + updater->install_bundles_rsrc(std::move(install_bundles)); } app_config->set_vendors(appconfig_vendors); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index beec4fe075..6f0754a035 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -695,19 +695,6 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) flatten_configbundle_hierarchy(tree, "printer"); } -// Load a config bundle file, into presets and store the loaded presets into separate files -// of the local configuration directory. -void PresetBundle::install_vendor_configbundle(const std::string &src_path0) -{ - boost::filesystem::path src_path(src_path0); - install_vendor_configbundle(src_path); -} - -void PresetBundle::install_vendor_configbundle(const boost::filesystem::path &src_path) -{ - boost::filesystem::copy_file(src_path, (boost::filesystem::path(data_dir()) / "vendor" / src_path.filename()).make_preferred(), boost::filesystem::copy_option::overwrite_if_exists); -} - // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags) diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index 651b957016..27add21eec 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -92,10 +92,6 @@ public: // Load the config bundle, store it to the user profile directory by default. size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); - // Install the Vendor specific config bundle into user's directory. - void install_vendor_configbundle(const std::string &src_path); - static void install_vendor_configbundle(const boost::filesystem::path &src_path); - // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 939be863c4..439a619c6b 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -14,7 +14,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/PresetBundle.hpp" @@ -40,6 +48,63 @@ static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; +struct UpdateNotification : wxDialog +{ + // If this dialog gets any more complex, it should probably be factored out... + + enum { + CONTENT_WIDTH = 400, + BORDER = 30, + SPACING = 15, + }; + + wxCheckBox *cbox; + + UpdateNotification(const Semver &ver_current, const Semver &ver_online) : wxDialog(nullptr, wxID_ANY, _(L("Update available"))) + { + auto *topsizer = new wxBoxSizer(wxHORIZONTAL); + auto *sizer = new wxBoxSizer(wxVERTICAL); + + const auto url = wxString::Format("https://github.com/prusa3d/Slic3r/releases/tag/version_%s", ver_online.to_string()); + auto *link = new wxHyperlinkCtrl(this, wxID_ANY, url, url); + + auto *text = new wxStaticText(this, wxID_ANY, + _(L("New version of Slic3r PE is available. To download, follow the link below."))); + const auto link_width = link->GetSize().GetWidth(); + text->Wrap(CONTENT_WIDTH > link_width ? CONTENT_WIDTH : link_width); + sizer->Add(text); + sizer->AddSpacer(SPACING); + + auto *versions = new wxFlexGridSizer(2, 0, SPACING); + versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:")))); + versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string())); + versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:")))); + versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string())); + sizer->Add(versions); + sizer->AddSpacer(SPACING); + + sizer->Add(link); + sizer->AddSpacer(2*SPACING); + + cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new versions any more"))); + sizer->Add(cbox); + sizer->AddSpacer(SPACING); + + auto *ok = new wxButton(this, wxID_OK); + ok->SetFocus(); + sizer->Add(ok, 0, wxALIGN_CENTRE_HORIZONTAL); + + auto *logo = new wxStaticBitmap(this, wxID_ANY, wxBitmap(GUI::from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG)); + + topsizer->Add(logo, 0, wxALL, BORDER); + topsizer->Add(sizer, 0, wxALL, BORDER); + + SetSizerAndFit(topsizer); + } + + bool disable_version_check() const { return cbox->GetValue(); } +}; + struct Update { fs::path source; @@ -79,7 +144,7 @@ struct PresetUpdater::priv bool cancel; std::thread thread; - priv(int event, AppConfig *app_config); + priv(int version_online_event); void set_download_prefs(AppConfig *app_config); bool get_file(const std::string &url, const fs::path &target_path) const; @@ -89,17 +154,17 @@ struct PresetUpdater::priv void check_install_indices() const; Updates config_update() const; - void perform_updates(AppConfig *app_config, Updates &&updates) const; + void perform_updates(Updates &&updates) const; }; -PresetUpdater::priv::priv(int event, AppConfig *app_config) : - version_online_event(event), +PresetUpdater::priv::priv(int version_online_event) : + version_online_event(version_online_event), cache_path(fs::path(Slic3r::data_dir()) / "cache"), rsrc_path(fs::path(resources_dir()) / "profiles"), vendor_path(fs::path(Slic3r::data_dir()) / "vendor"), cancel(false) { - set_download_prefs(app_config); + set_download_prefs(GUI::get_app_config()); check_install_indices(); index_db = std::move(Index::load_db()); } @@ -281,9 +346,9 @@ Updates PresetUpdater::priv::config_update() const return updates; } -void PresetUpdater::priv::perform_updates(AppConfig *app_config, Updates &&updates) const +void PresetUpdater::priv::perform_updates(Updates &&updates) const { - SnapshotDB::singleton().take_snapshot(*app_config, Snapshot::SNAPSHOT_UPGRADE); + SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE); for (const auto &update : updates) { fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); @@ -302,8 +367,8 @@ void PresetUpdater::priv::perform_updates(AppConfig *app_config, Updates &&updat } -PresetUpdater::PresetUpdater(int version_online_event, AppConfig *app_config) : - p(new priv(version_online_event, app_config)) +PresetUpdater::PresetUpdater(int version_online_event) : + p(new priv(version_online_event)) {} @@ -317,9 +382,9 @@ PresetUpdater::~PresetUpdater() } } -void PresetUpdater::sync(AppConfig *app_config, PresetBundle *preset_bundle) +void PresetUpdater::sync(PresetBundle *preset_bundle) { - p->set_download_prefs(app_config); + p->set_download_prefs(GUI::get_app_config()); if (!p->enabled_version_check && !p->enabled_config_update) { return; } // Copy the whole vendors data for use in the background thread @@ -334,7 +399,28 @@ void PresetUpdater::sync(AppConfig *app_config, PresetBundle *preset_bundle) })); } -void PresetUpdater::config_update(AppConfig *app_config) +void PresetUpdater::slic3r_update_notify() +{ + if (! p->enabled_version_check) { return; } + + auto* app_config = GUI::get_app_config(); + const auto ver_slic3r = Semver::parse(SLIC3R_VERSION); + const auto ver_online = Semver::parse(app_config->get("version_online")); + if (! ver_slic3r) { + throw std::runtime_error("Could not parse Slic3r version string: " SLIC3R_VERSION); + } + + if (ver_online && *ver_online > *ver_slic3r) { + UpdateNotification notification(*ver_slic3r, *ver_online); + notification.ShowModal(); + if (notification.disable_version_check()) { + app_config->set("version_check", "0"); + p->enabled_version_check = false; + } + } +} + +void PresetUpdater::config_update() const { if (! p->enabled_config_update) { return; } @@ -362,12 +448,12 @@ void PresetUpdater::config_update(AppConfig *app_config) std::cerr << "After modal" << std::endl; if (res == wxID_YES) { // User gave clearance, updates are go - p->perform_updates(app_config, std::move(updates)); + p->perform_updates(std::move(updates)); } } } -void PresetUpdater::install_bundles_rsrc(AppConfig *app_config, std::vector &&bundles) +void PresetUpdater::install_bundles_rsrc(std::vector &&bundles) { Updates updates; @@ -377,7 +463,7 @@ void PresetUpdater::install_bundles_rsrc(AppConfig *app_config, std::vectorperform_updates(app_config, std::move(updates)); + p->perform_updates(std::move(updates)); } diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp index 1499570db7..a53ed86eb6 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -13,16 +13,17 @@ class PresetBundle; class PresetUpdater { public: - PresetUpdater(int version_online_event, AppConfig *app_config); + PresetUpdater(int version_online_event); PresetUpdater(PresetUpdater &&) = delete; PresetUpdater(const PresetUpdater &) = delete; PresetUpdater &operator=(PresetUpdater &&) = delete; PresetUpdater &operator=(const PresetUpdater &) = delete; ~PresetUpdater(); - void sync(AppConfig *app_config, PresetBundle *preset_bundle); - void config_update(AppConfig *app_config); - void install_bundles_rsrc(AppConfig *app_config, std::vector &&bundles); + void sync(PresetBundle *preset_bundle); + void slic3r_update_notify(); + void config_update() const; + void install_bundles_rsrc(std::vector &&bundles); private: struct priv; std::unique_ptr p; diff --git a/xs/xsp/GUI_AppConfig.xsp b/xs/xsp/GUI_AppConfig.xsp index de0e5a22bc..08a88883db 100644 --- a/xs/xsp/GUI_AppConfig.xsp +++ b/xs/xsp/GUI_AppConfig.xsp @@ -43,5 +43,4 @@ void update_last_output_dir(char *dir); void reset_selections(); - bool slic3r_update_avail() const; }; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index ecf6f5a22e..2c63db10c4 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -117,14 +117,6 @@ PresetCollection::arrayref() croak("Loading of a config bundle %s failed:\n%s\n", path, e.what()); } %}; - void install_vendor_configbundle(const char *path) - %code%{ - try { - THIS->install_vendor_configbundle(std::string(path)); - } catch (std::exception& e) { - croak("Installing a vendor config bundle %s failed:\n%s\n", path, e.what()); - } - %}; void export_configbundle(char *path) %code%{ try { diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp index 3a4d55a01e..53c3aa9855 100644 --- a/xs/xsp/Utils_PresetUpdater.xsp +++ b/xs/xsp/Utils_PresetUpdater.xsp @@ -6,7 +6,8 @@ %} %name{Slic3r::PresetUpdater} class PresetUpdater { - PresetUpdater(int version_online_event, AppConfig *app_config); - void sync(AppConfig *app_config, PresetBundle* preset_bundle); - void config_update(AppConfig *app_config); + PresetUpdater(int version_online_event); + void sync(PresetBundle* preset_bundle); + void slic3r_update_notify(); + void config_update(); }; From c884f3b213981b89d7f76a6532e961477705727c Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 17 Apr 2018 17:11:56 +0200 Subject: [PATCH 037/145] Display app update notification with the main frame --- lib/Slic3r/GUI.pm | 11 ++--------- xs/src/slic3r/Utils/PresetUpdater.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 5ff8fdde1e..10375a9dce 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -102,18 +102,13 @@ sub OnInit { $self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->save; - # my $version_check = $self->{app_config}->get('version_check'); $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT); Slic3r::GUI::set_preset_updater($self->{preset_updater}); - eval { - $self->{preset_updater}->slic3r_update_notify(); - $self->{preset_updater}->config_update(); - }; + eval { $self->{preset_updater}->config_update(); }; if ($@) { warn $@ . "\n"; fatal_error(undef, $@); } - # my $slic3r_update = $self->{app_config}->slic3r_update_avail; Slic3r::GUI::load_language(); @@ -139,7 +134,6 @@ sub OnInit { ); $self->SetTopWindow($frame); - #EVT_IDLE($frame, sub { EVT_IDLE($self->{mainframe}, sub { while (my $cb = shift @cb) { $cb->(); @@ -151,8 +145,7 @@ sub OnInit { # before the UI was up and running. $self->CallAfter(sub { Slic3r::GUI::config_wizard_startup($app_conf_exists); - - # TODO: call periodically? + $self->{preset_updater}->slic3r_update_notify(); $self->{preset_updater}->sync($self->{preset_bundle}); }); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 439a619c6b..2140a2c91e 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -86,7 +86,7 @@ struct UpdateNotification : wxDialog sizer->Add(link); sizer->AddSpacer(2*SPACING); - cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new versions any more"))); + cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more"))); sizer->Add(cbox); sizer->AddSpacer(SPACING); @@ -136,6 +136,7 @@ struct PresetUpdater::priv bool enabled_version_check; bool enabled_config_update; std::string version_check_url; + bool had_config_update; fs::path cache_path; fs::path rsrc_path; @@ -159,6 +160,7 @@ struct PresetUpdater::priv PresetUpdater::priv::priv(int version_online_event) : version_online_event(version_online_event), + had_config_update(false), cache_path(fs::path(Slic3r::data_dir()) / "cache"), rsrc_path(fs::path(resources_dir()) / "profiles"), vendor_path(fs::path(Slic3r::data_dir()) / "vendor"), @@ -401,7 +403,8 @@ void PresetUpdater::sync(PresetBundle *preset_bundle) void PresetUpdater::slic3r_update_notify() { - if (! p->enabled_version_check) { return; } + if (! p->enabled_version_check || p->had_config_update) { return; } + // ^ We don't want to bother the user with updates multiple times, put off till next time. auto* app_config = GUI::get_app_config(); const auto ver_slic3r = Semver::parse(SLIC3R_VERSION); @@ -450,6 +453,8 @@ void PresetUpdater::config_update() const // User gave clearance, updates are go p->perform_updates(std::move(updates)); } + + p->had_config_update = true; } } From 2ef164eeef2d72326469c115e4135c10bd94be1e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 18 Apr 2018 09:44:49 +0200 Subject: [PATCH 038/145] Fixed cut contours in cut dialog 3D view --- lib/Slic3r/GUI/3DScene.pm | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 75c19d89ef..da04747c5a 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1330,8 +1330,8 @@ sub Render { glEnable(GL_CULL_FACE) if ($self->enable_picking); } - # draw cutting plane if (defined $self->cutting_plane_z) { + # draw cutting plane my $plane_z = $self->cutting_plane_z; my $bb = $volumes_bb; glDisable(GL_CULL_FACE); @@ -1347,6 +1347,15 @@ sub Render { glEnd(); glEnable(GL_CULL_FACE); glDisable(GL_BLEND); + + # draw cutting contours + glEnableClientState(GL_VERTEX_ARRAY); + glLineWidth(2); + glColor3f(0, 0, 0); + glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr()); + glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3); + glVertexPointer_c(3, GL_FLOAT, 0, 0); + glDisableClientState(GL_VERTEX_ARRAY); } # draw warning message @@ -1393,18 +1402,10 @@ sub draw_volumes { $volume->render; } glDisableClientState(GL_NORMAL_ARRAY); - glDisable(GL_BLEND); - - glEnable(GL_CULL_FACE); - - if (defined $self->cutting_plane_z) { - glLineWidth(2); - glColor3f(0, 0, 0); - glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr()); - glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3); - glVertexPointer_c(3, GL_FLOAT, 0, 0); - } glDisableClientState(GL_VERTEX_ARRAY); + + glDisable(GL_BLEND); + glEnable(GL_CULL_FACE); } sub mark_volumes_for_layer_height { From c9e4c831c2c4f53b0e53f97493d1d77aca592f36 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 18 Apr 2018 10:17:22 +0200 Subject: [PATCH 039/145] Axes with fixed size in 3D previews --- lib/Slic3r/GUI/3DScene.pm | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index da04747c5a..d8af6f71cc 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1281,10 +1281,7 @@ sub Render { # disable depth testing so that axes are not covered by ground glDisable(GL_DEPTH_TEST); my $origin = $self->origin; - my $axis_len = max( - 0.3 * max(@{ $self->bed_bounding_box->size }), - 2 * max(@{ $volumes_bb->size }), - ); + my $axis_len = $self->use_plain_shader ? 0.3 * max(@{ $self->bed_bounding_box->size }) : 2 * max(@{ $volumes_bb->size }); glLineWidth(2); glBegin(GL_LINES); # draw line for x axis From 81c6ad3ab72be74752e802f83515a41112428eab Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 18 Apr 2018 11:40:43 +0200 Subject: [PATCH 040/145] ConfigWizard: Add reset option, fixes Fix mock vendors --- resources/profiles/BarBaz.ini | 44 +++++++++++----------- resources/profiles/Foobar.ini | 44 +++++++++++----------- xs/src/slic3r/GUI/ConfigWizard.cpp | 32 ++++++++++++---- xs/src/slic3r/GUI/ConfigWizard.hpp | 2 +- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 9 +++-- xs/src/slic3r/GUI/GUI.cpp | 33 ++++++++-------- xs/src/slic3r/GUI/GUI.hpp | 2 +- xs/src/slic3r/Utils/PresetUpdater.cpp | 12 +++--- xs/src/slic3r/Utils/PresetUpdater.hpp | 2 +- 9 files changed, 102 insertions(+), 78 deletions(-) diff --git a/resources/profiles/BarBaz.ini b/resources/profiles/BarBaz.ini index 7c8cd3a6bb..ed2686cdc4 100644 --- a/resources/profiles/BarBaz.ini +++ b/resources/profiles/BarBaz.ini @@ -1,4 +1,4 @@ -# Print profiles for the Prusa Research printers. +# Print profiles for the BarBaz Research printers. [vendor] # Vendor name will be shown by the Config Wizard. @@ -735,11 +735,11 @@ first_layer_temperature = 195 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 195 -[filament:Prusa ABS] +[filament:BarBaz ABS] inherits = *ABS* filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" -[filament:Prusa HIPS] +[filament:BarBaz HIPS] inherits = *ABS* bridge_fan_speed = 50 cooling = 1 @@ -755,11 +755,11 @@ min_fan_speed = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 -[filament:Prusa PET] +[filament:BarBaz PET] inherits = *PET* filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" -[filament:Prusa PLA] +[filament:BarBaz PLA] inherits = *PLA* filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" @@ -855,7 +855,7 @@ min_layer_height = 0.07 nozzle_diameter = 0.4 octoprint_apikey = octoprint_host = -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK2\n printer_settings_id = retract_before_travel = 1 retract_before_wipe = 0% @@ -882,7 +882,7 @@ z_offset = 0 printer_model = M2 printer_variant = 0.4 default_print_profile = 0.15mm OPTIMAL -default_filament_profile = Prusa PLA +default_filament_profile = BarBaz PLA [printer:*multimaterial*] inherits = *common* @@ -903,7 +903,7 @@ printer_model = M3 [printer:*mm-single*] inherits = *multimaterial* end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 [printer:*mm-multi*] @@ -911,14 +911,14 @@ inherits = *multimaterial* end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 nozzle_diameter = 0.4,0.4,0.4,0.4 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 variable_layer_height = 0 -[printer:Original Prusa i3 MK2] +[printer:BarBaz i3 MK2] inherits = *common* -[printer:Original Prusa i3 MK2 0.25 nozzle] +[printer:BarBaz i3 MK2 0.25 nozzle] inherits = *common* max_layer_height = 0.1 min_layer_height = 0.05 @@ -929,56 +929,56 @@ variable_layer_height = 0 printer_variant = 0.25 default_print_profile = 0.10mm DETAIL 0.25 nozzle -[printer:Original Prusa i3 MK2 0.6 nozzle] +[printer:BarBaz i3 MK2 0.6 nozzle] inherits = *common* max_layer_height = 0.35 min_layer_height = 0.1 nozzle_diameter = 0.6 printer_variant = 0.6 -[printer:Original Prusa i3 MK2 MM Single Mode] +[printer:BarBaz i3 MK2 MM Single Mode] inherits = *mm-single* -[printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] +[printer:BarBaz i3 MK2 MM Single Mode 0.6 nozzle] inherits = *mm-single* nozzle_diameter = 0.6 printer_variant = 0.6 -[printer:Original Prusa i3 MK2 MultiMaterial] +[printer:BarBaz i3 MK2 MultiMaterial] inherits = *mm-multi* nozzle_diameter = 0.4,0.4,0.4,0.4 -[printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] +[printer:BarBaz i3 MK2 MultiMaterial 0.6 nozzle] inherits = *mm-multi* nozzle_diameter = 0.6,0.6,0.6,0.6 printer_variant = 0.6 -[printer:Original Prusa i3 MK3] +[printer:BarBaz i3 MK3] inherits = *common* end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = M1 default_print_profile = 0.15mm OPTIMAL MK3 -[printer:Original Prusa i3 MK3 0.25 nozzle] +[printer:BarBaz i3 MK3 0.25 nozzle] inherits = *common* nozzle_diameter = 0.25 printer_variant = 0.25 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = M1 default_print_profile = 0.10mm DETAIL MK3 -[printer:Original Prusa i3 MK3 0.6 nozzle] +[printer:BarBaz i3 MK3 0.6 nozzle] inherits = *common* nozzle_diameter = 0.6 printer_variant = 0.6 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = M1 diff --git a/resources/profiles/Foobar.ini b/resources/profiles/Foobar.ini index 571aa8bb8b..6f31401ea7 100644 --- a/resources/profiles/Foobar.ini +++ b/resources/profiles/Foobar.ini @@ -1,4 +1,4 @@ -# Print profiles for the Prusa Research printers. +# Print profiles for the Foobar Research printers. [vendor] # Vendor name will be shown by the Config Wizard. @@ -735,11 +735,11 @@ first_layer_temperature = 195 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 195 -[filament:Prusa ABS] +[filament:Foobar ABS] inherits = *ABS* filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" -[filament:Prusa HIPS] +[filament:Foobar HIPS] inherits = *ABS* bridge_fan_speed = 50 cooling = 1 @@ -755,11 +755,11 @@ min_fan_speed = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 -[filament:Prusa PET] +[filament:Foobar PET] inherits = *PET* filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" -[filament:Prusa PLA] +[filament:Foobar PLA] inherits = *PLA* filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" @@ -855,7 +855,7 @@ min_layer_height = 0.07 nozzle_diameter = 0.4 octoprint_apikey = octoprint_host = -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK2\n printer_settings_id = retract_before_travel = 1 retract_before_wipe = 0% @@ -882,7 +882,7 @@ z_offset = 0 printer_model = M2 printer_variant = 0.4 default_print_profile = 0.15mm OPTIMAL -default_filament_profile = Prusa PLA +default_filament_profile = Foobar PLA [printer:*multimaterial*] inherits = *common* @@ -903,7 +903,7 @@ printer_model = M3 [printer:*mm-single*] inherits = *multimaterial* end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 [printer:*mm-multi*] @@ -911,14 +911,14 @@ inherits = *multimaterial* end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 nozzle_diameter = 0.4,0.4,0.4,0.4 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 variable_layer_height = 0 -[printer:Original Prusa i3 MK2] +[printer:Foobar i3 MK2] inherits = *common* -[printer:Original Prusa i3 MK2 0.25 nozzle] +[printer:Foobar i3 MK2 0.25 nozzle] inherits = *common* max_layer_height = 0.1 min_layer_height = 0.05 @@ -929,56 +929,56 @@ variable_layer_height = 0 printer_variant = 0.25 default_print_profile = 0.10mm DETAIL 0.25 nozzle -[printer:Original Prusa i3 MK2 0.6 nozzle] +[printer:Foobar i3 MK2 0.6 nozzle] inherits = *common* max_layer_height = 0.35 min_layer_height = 0.1 nozzle_diameter = 0.6 printer_variant = 0.6 -[printer:Original Prusa i3 MK2 MM Single Mode] +[printer:Foobar i3 MK2 MM Single Mode] inherits = *mm-single* -[printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] +[printer:Foobar i3 MK2 MM Single Mode 0.6 nozzle] inherits = *mm-single* nozzle_diameter = 0.6 printer_variant = 0.6 -[printer:Original Prusa i3 MK2 MultiMaterial] +[printer:Foobar i3 MK2 MultiMaterial] inherits = *mm-multi* nozzle_diameter = 0.4,0.4,0.4,0.4 -[printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] +[printer:Foobar i3 MK2 MultiMaterial 0.6 nozzle] inherits = *mm-multi* nozzle_diameter = 0.6,0.6,0.6,0.6 printer_variant = 0.6 -[printer:Original Prusa i3 MK3] +[printer:Foobar i3 MK3] inherits = *common* end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = M1 default_print_profile = 0.15mm OPTIMAL MK3 -[printer:Original Prusa i3 MK3 0.25 nozzle] +[printer:Foobar i3 MK3 0.25 nozzle] inherits = *common* nozzle_diameter = 0.25 printer_variant = 0.25 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = M1 default_print_profile = 0.10mm DETAIL MK3 -[printer:Original Prusa i3 MK3 0.6 nozzle] +[printer:Foobar i3 MK3 0.6 nozzle] inherits = *common* nozzle_diameter = 0.6 printer_variant = 0.6 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = M1 diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index c4ca7a886c..0eee234db7 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -17,7 +17,6 @@ #include "GUI.hpp" #include "slic3r/Utils/PresetUpdater.hpp" -// TODO: Wizard vs Assistant namespace Slic3r { namespace GUI { @@ -205,9 +204,18 @@ void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable) PageWelcome::PageWelcome(ConfigWizard *parent) : ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))), printer_picker(nullptr), - others_buttons(new wxPanel(parent)) + others_buttons(new wxPanel(parent)), + cbox_reset(new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")))) { - append_text(_(L("Hello, welcome to Slic3r Prusa Edition! TODO: This text."))); + if (wizard_p()->flag_startup && wizard_p()->flag_empty_datadir) { + wxString::Format(_(L("Run %s")), ConfigWizard::name()); + append_text(wxString::Format( + _(L("Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")), + ConfigWizard::name()) + ); + } else { + append(cbox_reset); + } const auto &vendors = wizard_p()->vendors; const auto vendor_prusa = vendors.find("PrusaResearch"); @@ -720,14 +728,22 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // This vendor needs to be installed install_bundles.emplace_back(vendor_rsrc.second); } + + // If the datadir was empty don't take a snapshot (it would just be an empty snapshot) + const bool snapshot = !flag_empty_datadir || page_welcome->reset_user_profile(); if (install_bundles.size() > 0) { - updater->install_bundles_rsrc(std::move(install_bundles)); + // Install bundles from resources. + updater->install_bundles_rsrc(std::move(install_bundles), snapshot); + } + + if (page_welcome->reset_user_profile()) { + preset_bundle->reset(true); } app_config->set_vendors(appconfig_vendors); app_config->set("version_check", page_update->version_check ? "1" : "0"); app_config->set("preset_update", page_update->preset_update ? "1" : "0"); - if (fresh_start) + if (flag_startup) app_config->reset_selections(); // ^ TODO: replace with appropriate printer selection preset_bundle->load_presets(*app_config); @@ -743,11 +759,13 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // Public -ConfigWizard::ConfigWizard(wxWindow *parent, bool fresh_start) : +ConfigWizard::ConfigWizard(wxWindow *parent, bool startup, bool empty_datadir) : wxDialog(parent, wxID_ANY, name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), p(new priv(this)) { - p->fresh_start = fresh_start; + p->flag_startup = startup; + p->flag_empty_datadir = empty_datadir; + p->load_vendors(); p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ "gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp index 66c8b3a95a..eeb64c57f6 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -16,7 +16,7 @@ namespace GUI { class ConfigWizard: public wxDialog { public: - ConfigWizard(wxWindow *parent, bool fresh_start); + ConfigWizard(wxWindow *parent, bool startup, bool empty_datadir); ConfigWizard(ConfigWizard &&) = delete; ConfigWizard(const ConfigWizard &) = delete; ConfigWizard &operator=(ConfigWizard &&) = delete; diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index faabca812c..8e43ac3ac7 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -100,12 +100,14 @@ struct PageWelcome: ConfigWizardPage { PrinterPicker *printer_picker; wxPanel *others_buttons; + wxCheckBox *cbox_reset; PageWelcome(ConfigWizard *parent); virtual wxPanel* extra_buttons() { return others_buttons; } virtual void on_page_set(); + bool reset_user_profile() const { return cbox_reset->GetValue(); } void on_variant_checked(); }; @@ -188,9 +190,10 @@ private: struct ConfigWizard::priv { ConfigWizard *q; - bool fresh_start; - AppConfig appconfig_vendors; // TODO: use order-preserving container - std::unordered_map vendors; // TODO: just set? + bool flag_startup; + bool flag_empty_datadir; + AppConfig appconfig_vendors; + std::unordered_map vendors; std::unordered_map vendors_rsrc; std::unique_ptr custom_config; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index bfdd46287f..7de82cb5f4 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -390,7 +390,7 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ switch (event.GetId() - config_id_base) { case ConfigMenuWizard: - config_wizard(0); + config_wizard(false, false); break; case ConfigMenuTakeSnapshot: // Take a configuration snapshot. @@ -465,33 +465,35 @@ bool check_unsaved_changes() void config_wizard_startup(bool app_config_exists) { if (! app_config_exists || g_PresetBundle->has_defauls_only()) { - config_wizard(true); + config_wizard(true, true); } else if (g_AppConfig->legacy_datadir()) { // Looks like user has legacy pre-vendorbundle data directory, // explain what this is and run the wizard const auto msg = _(L("Configuration update")); - const auto ext_msg = _(L( - "Slic3r PE now uses an updated configuration structure.\n\n" + const auto ext_msg = wxString::Format( + _(L( + "Slic3r PE now uses an updated configuration structure.\n\n" - "So called 'System presets' have been introduced, which hold the built-in default settings for various " - "printers. These System presets cannot be modified, instead, users now may create their" - "own presets inheriting settings from one of the System presets.\n" - "An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n" + "So called 'System presets' have been introduced, which hold the built-in default settings for various " + "printers. These System presets cannot be modified, instead, users now may create their" + "own presets inheriting settings from one of the System presets.\n" + "An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n" - // TODO: Assistant vs Wizard - "Please proceed with the Configuration wizard that follows to set up the new presets " - "and to choose whether to enable automatic preset updates." - )); + "Please proceed with the %s that follows to set up the new presets " + "and to choose whether to enable automatic preset updates." + )), + ConfigWizard::name() + ); wxMessageDialog dlg(NULL, msg, _(L("Configuration update")), wxOK|wxCENTRE); dlg.SetExtendedMessage(ext_msg); const auto res = dlg.ShowModal(); - config_wizard(true); + config_wizard(true, false); } } -void config_wizard(bool fresh_start) // TODO: fresh_start useful ? +void config_wizard(bool startup, bool empty_datadir) { if (g_wxMainFrame == nullptr) throw std::runtime_error("Main frame not set"); @@ -500,8 +502,7 @@ void config_wizard(bool fresh_start) // TODO: fresh_start useful ? if (! check_unsaved_changes()) return; - // TODO: Offer "reset user profile" ??? - ConfigWizard wizard(g_wxMainFrame, fresh_start); + ConfigWizard wizard(g_wxMainFrame, startup, empty_datadir); wizard.run(g_PresetBundle, g_PresetUpdater); // Load the currently selected preset into the GUI, update the preset selection box. diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 6ee9d4f54c..92a6e6ebb8 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -97,7 +97,7 @@ extern bool check_unsaved_changes(); extern void config_wizard_startup(bool app_config_exists); // Opens the configuration wizard, returns true if wizard is finished & accepted. -extern void config_wizard(bool fresh_start); +extern void config_wizard(bool startup, bool empty_datadir); // Create "Preferences" dialog after selecting menu "Preferences" in Perl part extern void open_preferences_dialog(int event_preferences); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 2140a2c91e..bf9e1eb44e 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -155,7 +155,7 @@ struct PresetUpdater::priv void check_install_indices() const; Updates config_update() const; - void perform_updates(Updates &&updates) const; + void perform_updates(Updates &&updates, bool snapshot = true) const; }; PresetUpdater::priv::priv(int version_online_event) : @@ -348,9 +348,11 @@ Updates PresetUpdater::priv::config_update() const return updates; } -void PresetUpdater::priv::perform_updates(Updates &&updates) const +void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const { - SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE); + if (snapshot) { + SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE); + } for (const auto &update : updates) { fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); @@ -458,7 +460,7 @@ void PresetUpdater::config_update() const } } -void PresetUpdater::install_bundles_rsrc(std::vector &&bundles) +void PresetUpdater::install_bundles_rsrc(std::vector &&bundles, bool snapshot) { Updates updates; @@ -468,7 +470,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector &&bundles) updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors)); } - p->perform_updates(std::move(updates)); + p->perform_updates(std::move(updates), snapshot); } diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp index a53ed86eb6..8fe3e021d5 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -23,7 +23,7 @@ public: void sync(PresetBundle *preset_bundle); void slic3r_update_notify(); void config_update() const; - void install_bundles_rsrc(std::vector &&bundles); + void install_bundles_rsrc(std::vector &&bundles, bool snapshot = true); private: struct priv; std::unique_ptr p; From 0711f84ea0277dd503abe49cf0b58ddeb2d030f2 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 18 Apr 2018 12:33:07 +0200 Subject: [PATCH 041/145] Add version check & preset update options to Preferences --- xs/src/slic3r/GUI/Preferences.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/Preferences.cpp b/xs/src/slic3r/GUI/Preferences.cpp index 6731cd394d..4c60577d56 100644 --- a/xs/src/slic3r/GUI/Preferences.cpp +++ b/xs/src/slic3r/GUI/Preferences.cpp @@ -9,7 +9,7 @@ void PreferencesDialog::build() { auto app_config = get_app_config(); m_optgroup = std::make_shared(this, _(L("General"))); - m_optgroup->label_width = 200; + m_optgroup->label_width = 400; m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -49,6 +49,22 @@ void PreferencesDialog::build() option = Option (def,"background_processing"); m_optgroup->append_single_option_line(option); + // Please keep in sync with ConfigWizard + def.label = L("Check for application updates"); + def.type = coBool; + def.tooltip = L("If enabled, Slic3r checks for new versions of Slic3r PE 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."); + def.default_value = new ConfigOptionBool(app_config->get("version_check") == "1"); + option = Option (def, "version_check"); + m_optgroup->append_single_option_line(option); + + // Please keep in sync with ConfigWizard + def.label = L("Update built-in Presets automatically"); + def.type = coBool; + def.tooltip = L("If enabled, Slic3r 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."); + def.default_value = new ConfigOptionBool(app_config->get("preset_update") == "1"); + option = Option (def, "preset_update"); + m_optgroup->append_single_option_line(option); + def.label = L("Disable USB/serial connection"); def.type = coBool; def.tooltip = L("Disable communication with the printer over a serial / USB cable. " From 3ca2dfbc1dc83d3b976573dba6aa065c6fed236c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 18 Apr 2018 13:32:21 +0200 Subject: [PATCH 042/145] Added some performance changes --- xs/src/libslic3r/Config.cpp | 38 +++ xs/src/libslic3r/Config.hpp | 3 + xs/src/slic3r/GUI/GUI.cpp | 6 + xs/src/slic3r/GUI/GUI.hpp | 1 + xs/src/slic3r/GUI/Preset.cpp | 8 +- xs/src/slic3r/GUI/Preset.hpp | 10 +- xs/src/slic3r/GUI/Tab.cpp | 529 ++++++++++++++++++++++++----------- xs/src/slic3r/GUI/Tab.hpp | 41 ++- 8 files changed, 455 insertions(+), 181 deletions(-) diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index a4eaf3072a..8c1349e085 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -206,6 +206,44 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const return diff; } +template +void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase *this_c) +{ + const T* opt_init = static_cast(other.option(opt_key)); + const T* opt_cur = static_cast(this_c->option(opt_key)); + int opt_init_max_id = opt_init->values.size() - 1; + for (int i = 0; i < opt_cur->values.size(); i++) + { + int init_id = i <= opt_init_max_id ? i : 0; + if (opt_cur->values[i] != opt_init->values[init_id]) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } +} + +t_config_option_keys ConfigBase::deep_diff(const ConfigBase &other) const +{ + t_config_option_keys diff; + for (const t_config_option_key &opt_key : this->keys()) { + const ConfigOption *this_opt = this->option(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) + { + if (opt_key == "bed_shape"){ diff.emplace_back(opt_key); continue; } + switch (other_opt->type()) + { + case coInts: add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coBools: add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coFloats: add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coStrings: add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coPercents:add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coPoints: add_correct_opts_to_diff(opt_key, diff, other, this); break; + default: diff.emplace_back(opt_key); break; + } + } + } + return diff; +} + t_config_option_keys ConfigBase::equal(const ConfigBase &other) const { t_config_option_keys equal; diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 6eb307c5ce..24a8871308 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -1046,6 +1046,9 @@ public: void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; + // Use deep_diff to correct return of changed options, + // considering individual options for each extruder + t_config_option_keys deep_diff(const ConfigBase &other) const; t_config_option_keys equal(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index cc135931aa..c4f07935c5 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -179,6 +179,7 @@ AppConfig *g_AppConfig = nullptr; PresetBundle *g_PresetBundle= nullptr; wxColour g_color_label_modified; wxColour g_color_label_sys; +wxColour g_color_label_default; std::vector g_tabs_list; @@ -198,6 +199,7 @@ static void init_label_colours() g_color_label_modified = wxColour(253, 111, 40); g_color_label_sys = wxColour(115, 220, 103); } + g_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } void set_wxapp(wxApp *app) @@ -548,6 +550,10 @@ const wxColour& get_sys_label_clr() { return g_color_label_sys; } +const wxColour& get_default_label_clr() { + return g_color_label_default; +} + unsigned get_colour_approx_luma(const wxColour &colour) { double r = colour.Red(); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 0b681f48f1..75600a57b1 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -83,6 +83,7 @@ wxApp* get_app(); const wxColour& get_modified_label_clr(); const wxColour& get_sys_label_clr(); +const wxColour& get_default_label_clr(); unsigned get_colour_approx_luma(const wxColour &colour); void add_debug_menu(wxMenuBar *menu, int event_language_change); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index d48c9bf8f5..30f49df08f 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -629,11 +629,13 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) return was_dirty != is_dirty; } -std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference) +std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type /*= false*/) { std::vector changed; - if (edited != nullptr && reference != nullptr) { - changed = reference->config.diff(edited->config); + if (edited != nullptr && reference != nullptr) { + changed = is_printer_type ? + reference->config.deep_diff(edited->config) : + reference->config.diff(edited->config); // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index c038160f4e..e12f18b885 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -253,11 +253,11 @@ public: // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. bool current_is_dirty() const { return ! this->current_dirty_options().empty(); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. - std::vector current_dirty_options() const - { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset()); } + std::vector current_dirty_options(const bool is_printer_type = false) const + { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), is_printer_type); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. - std::vector current_different_from_parent_options() const - { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent()); } + std::vector current_different_from_parent_options(const bool is_printer_type = false) const + { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), is_printer_type); } // Compare the content of get_selected_preset() with get_selected_preset_parent() configs, return the list of keys where they equal. std::vector system_equal_options() const; @@ -299,7 +299,7 @@ private: std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } - static std::vector dirty_options(const Preset *edited, const Preset *reference); + static std::vector dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false); // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. Preset::Type m_type; diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 8252437495..dcf61f5749 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -25,6 +25,8 @@ #include "wxExtensions.hpp" #include +#include + namespace Slic3r { namespace GUI { @@ -112,14 +114,14 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_bmp_value_revert .LoadFile(from_u8(var("action_undo.png")), wxBITMAP_TYPE_PNG); m_bmp_white_bullet .LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); m_undo_btn->SetBitmap(m_bmp_white_bullet); - m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ /*on_back_to_initial_value*/on_roll_back_value(); })); m_undo_to_sys_btn->SetBitmap(m_bmp_white_bullet); - m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ /*on_back_to_sys_value*/on_roll_back_value(true); })); // Colors for ui "decoration" m_sys_label_clr = get_sys_label_clr(); m_modified_label_clr = get_modified_label_clr(); - m_default_text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + m_default_text_clr = get_default_label_clr(); m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxBOTTOM, 3); @@ -246,45 +248,108 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo return page; } -template -void add_correct_opts_to_dirty_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) -{ - auto opt_init = static_cast(tab->m_presets->get_selected_preset().config.option(opt_key)); - auto opt_cur = static_cast(tab->m_config->option(opt_key)); - int opt_init_max_id = opt_init->values.size()-1; - for (int i = 0; i < opt_cur->values.size(); i++) - { - int init_id = i <= opt_init_max_id ? i : 0; - if (opt_cur->values[i] != opt_init->values[init_id]) - vec->emplace_back(opt_key + "#" + std::to_string(i)); - } -} - -template -void add_correct_opts_to_sys_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) -{ - const Preset* sys_preset = tab->m_presets->get_selected_preset_parent(); - if (sys_preset == nullptr) - return; - T *opt_cur = static_cast(tab->m_config->option(opt_key)); - const T *opt_sys = static_cast(sys_preset->config.option(opt_key)); - int opt_max_id = opt_sys->values.size()-1; - for (int i = 0; i < opt_cur->values.size(); i++) - { - int init_id = i <= opt_max_id ? i : 0; - if (opt_cur->values[i] == opt_sys->values[init_id]) - vec->emplace_back(opt_key + "#" + std::to_string(i)); - } -} +// template +// void add_correct_opts_to_dirty_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +// { +// auto opt_init = static_cast(tab->m_presets->get_selected_preset().config.option(opt_key)); +// auto opt_cur = static_cast(tab->m_config->option(opt_key)); +// int opt_init_max_id = opt_init->values.size()-1; +// for (int i = 0; i < opt_cur->values.size(); i++) +// { +// int init_id = i <= opt_init_max_id ? i : 0; +// if (opt_cur->values[i] != opt_init->values[init_id]) +// vec->emplace_back(opt_key + "#" + std::to_string(i)); +// } +// } +// +// template +// void add_correct_opts_to_sys_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +// { +// const Preset* sys_preset = tab->m_presets->get_selected_preset_parent(); +// if (sys_preset == nullptr) +// return; +// T *opt_cur = static_cast(tab->m_config->option(opt_key)); +// const T *opt_sys = static_cast(sys_preset->config.option(opt_key)); +// int opt_max_id = opt_sys->values.size()-1; +// for (int i = 0; i < opt_cur->values.size(); i++) +// { +// int init_id = i <= opt_max_id ? i : 0; +// if (opt_cur->values[i] == opt_sys->values[init_id]) +// vec->emplace_back(opt_key + "#" + std::to_string(i)); +// } +// } // Update UI according to changes void Tab::update_changed_ui() { if (m_postpone_update_ui) return; - auto dirty_options = m_presets->current_dirty_options(); - if (name() == "printer"){ + const bool is_printer_type = (name() == "printer"); + auto m_dirty_options = m_presets->current_dirty_options(is_printer_type); + auto m_nonsys_options = m_presets->current_different_from_parent_options(is_printer_type); + if (is_printer_type){ + TabPrinter* tab = static_cast(this); + if (tab->m_initial_extruders_count != tab->m_extruders_count) + m_dirty_options.emplace_back("extruders_count"); + if (tab->m_sys_extruders_count != tab->m_extruders_count) + m_nonsys_options.emplace_back("extruders_count"); + } + + for (auto& it : m_options_list) + it.second = m_opt_status_value; + +// auto m_options_list = m_options_list; + for (auto opt_key : m_dirty_options) m_options_list[opt_key] &= ~osInitValue; + for (auto opt_key : m_nonsys_options) m_options_list[opt_key] &= ~osSystemValue; + + Freeze(); + //update options "decoration" + for (const auto opt : m_options_list/*m_options_list*/) + { + bool is_nonsys_value = false; + bool is_modified_value = true; + const wxBitmap *sys_icon = &m_bmp_value_lock; + const wxBitmap *icon = &m_bmp_value_revert; + const wxColour *color = &m_sys_label_clr; + + if ((opt.second & osSystemValue) == 0){ +// if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { + is_nonsys_value = true; + sys_icon = m_bmp_non_system; + if ((opt.second & osInitValue) != 0) +// if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) + color = &m_default_text_clr; + else + color = &m_modified_label_clr; + } + if ((opt.second & osInitValue) != 0) +// if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) + { + is_modified_value = false; + icon = &m_bmp_white_bullet; + } + if (opt.first == "bed_shape" || opt.first == "compatible_printers") { + if (m_colored_Label != nullptr) { + m_colored_Label->SetForegroundColour(*color); + m_colored_Label->Refresh(true); + } + continue; + } + + Field* field = get_field(opt.first); + if (field == nullptr) continue; + field->m_is_nonsys_value = is_nonsys_value; + field->m_is_modified_value = is_modified_value; + field->set_undo_bitmap(icon); + field->set_undo_to_sys_bitmap(sys_icon); + field->set_label_colour(color); + } + Thaw(); +// m_options_list = m_options_list; +/* auto dirty_options = m_presets->current_dirty_options(); + + if (){ // Update dirty_options in case changes of Extruder's options TabPrinter* tab = static_cast(this); m_dirty_options.resize(0); @@ -381,71 +446,113 @@ void Tab::update_changed_ui() field->set_label_colour(color); } Thaw(); - +*/ wxTheApp->CallAfter([this]() { update_changed_tree_ui(); }); } +void Tab::init_options_list() +{ + if (!m_options_list.empty()) + m_options_list.clear(); + + for (const auto opt_key : m_config->keys()) + m_options_list.emplace(opt_key, m_opt_status_value); +} + template -void add_correct_opts_to_full_options_list(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, TabPrinter *tab, const int& value) { T *opt_cur = static_cast(tab->m_config->option(opt_key)); for (int i = 0; i < opt_cur->values.size(); i++) - vec->emplace_back(opt_key + "#" + std::to_string(i)); + map.emplace(opt_key + "#" + std::to_string(i), value); } -void Tab::update_full_options_list() +void TabPrinter::init_options_list() { - if (!m_full_options_list.empty()) - m_full_options_list.resize(0); + if (!m_options_list.empty()) + m_options_list.clear(); - if (m_name != "printer"){ - m_full_options_list = m_config->keys(); - return; - } - - TabPrinter* tab = static_cast(this); for (const auto opt_key : m_config->keys()) { if (opt_key == "bed_shape"){ - m_full_options_list.emplace_back(opt_key); + m_options_list.emplace(opt_key, m_opt_status_value); continue; } switch (m_config->option(opt_key)->type()) { - case coInts: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coBools: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coFloats: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coStrings: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coPercents:add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coPoints: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - default: m_full_options_list.emplace_back(opt_key); break; + case coInts: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coBools: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + default: m_options_list.emplace(opt_key, m_opt_status_value); break; } } - m_full_options_list.emplace_back("extruders_count"); + m_options_list.emplace("extruders_count", m_opt_status_value); } -void Tab::update_sys_ui_after_sel_preset() -{ - const wxColour* clr = &m_default_text_clr; - for (const auto opt_key : m_full_options_list){ - Field* field = get_field(opt_key); - if (field != nullptr){ - field->set_undo_to_sys_bitmap(m_bmp_non_system); - field->m_is_nonsys_value = true; - field->set_label_colour(clr); - } - } - m_sys_options.resize(0); -} + +// template +// void add_correct_opts_to_full_options_list(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +// { +// T *opt_cur = static_cast(tab->m_config->option(opt_key)); +// for (int i = 0; i < opt_cur->values.size(); i++) +// vec->emplace_back(opt_key + "#" + std::to_string(i)); +// } + +// void Tab::update_full_options_list() +// { +// if (!m_full_options_list.empty()) +// m_full_options_list.resize(0); +// +// if (m_name != "printer"){ +// m_full_options_list = m_config->keys(); +// return; +// } +// +// TabPrinter* tab = static_cast(this); +// for (const auto opt_key : m_config->keys()) +// { +// if (opt_key == "bed_shape"){ +// m_full_options_list.emplace_back(opt_key); +// continue; +// } +// switch (m_config->option(opt_key)->type()) +// { +// case coInts: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; +// case coBools: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; +// case coFloats: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; +// case coStrings: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; +// case coPercents:add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; +// case coPoints: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; +// default: m_full_options_list.emplace_back(opt_key); break; +// } +// } +// m_full_options_list.emplace_back("extruders_count"); +// } + +// void Tab::update_sys_ui_after_sel_preset() +// { +// const wxColour* clr = &m_default_text_clr; +// for (const auto opt_key : m_full_options_list){ +// Field* field = get_field(opt_key); +// if (field != nullptr){ +// field->set_undo_to_sys_bitmap(m_bmp_non_system); +// field->m_is_nonsys_value = true; +// field->set_label_colour(clr); +// } +// } +// m_sys_options.resize(0); +// } void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) { - if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) - sys_page = false; - if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) - modified_page = true; + auto opt = m_options_list.find(opt_key); + if (sys_page) sys_page = (opt->second & osSystemValue) != 0; + if (!modified_page) modified_page = (opt->second & osInitValue) == 0; } void Tab::update_changed_tree_ui() @@ -466,7 +573,7 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } - if (title == _("Dependencies")){ + if (title == _("Dependencies") && name() != "printer"){ get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); } for (auto group : page->m_optgroups) @@ -478,12 +585,13 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } - if (sys_page) - m_treectrl->SetItemTextColour(cur_item, get_sys_label_clr()); - else if (modified_page) - m_treectrl->SetItemTextColour(cur_item, get_modified_label_clr()); - else - m_treectrl->SetItemTextColour(cur_item, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + + const wxColor *clr = sys_page ? &m_sys_label_clr : + modified_page ? &m_modified_label_clr : + &m_default_text_clr; + + if (page->set_item_colour(clr)) + m_treectrl->SetItemTextColour(cur_item, *clr); page->m_is_nonsys_values = !sys_page; page->m_is_modified_values = modified_page; @@ -506,9 +614,17 @@ void Tab::update_undo_buttons() m_undo_to_sys_btn->SetBitmap(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); } -void Tab::on_back_to_initial_value() +void Tab::on_roll_back_value(const bool to_sys /*= true*/) { - if (!m_is_modified_values) return; + int os; + if (to_sys) { + if (!m_is_nonsys_values) return; + os = osSystemValue; + } + else { + if (!m_is_modified_values) return; + os = osInitValue; + } m_postpone_update_ui = true; @@ -517,19 +633,19 @@ void Tab::on_back_to_initial_value() if (page->title() == selection) { for (auto group : page->m_optgroups){ if (group->title == _("Capabilities")){ - if (find(m_dirty_options.begin(), m_dirty_options.end(), "extruders_count") != m_dirty_options.end()) - group->back_to_initial_value("extruders_count"); + if ((m_options_list["extruders_count"] & os) == 0) + to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); } if (group->title == _("Size and coordinates")){ - if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()){ - group->back_to_initial_value("bed_shape"); + if ((m_options_list["bed_shape"] & os) == 0){ + to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); load_key_value("bed_shape", true/*some value*/, true); } } - if (group->title == _("Profile dependencies")){ - if (find(m_dirty_options.begin(), m_dirty_options.end(), "compatible_printers") != m_dirty_options.end()){ - group->back_to_initial_value("compatible_printers"); + if (group->title == _("Profile dependencies") && name() != "printer"){ + if ((m_options_list["compatible_printers"] & os) == 0){ + to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); load_key_value("compatible_printers", true/*some value*/, true); bool is_empty = m_config->option("compatible_printers")->values.empty(); @@ -539,8 +655,8 @@ void Tab::on_back_to_initial_value() } for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { const std::string& opt_key = it->first; - if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) - group->back_to_initial_value(opt_key); + if ((m_options_list[opt_key] & os) == 0) + to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); } } break; @@ -550,48 +666,111 @@ void Tab::on_back_to_initial_value() update_changed_ui(); } -void Tab::on_back_to_sys_value() -{ - if (!m_is_nonsys_values) return; - - m_postpone_update_ui = true; - - auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - for (auto page : m_pages) - if (page->title() == selection) { - for (auto group : page->m_optgroups) { - if (group->title == _("Capabilities")){ - if (find(m_sys_options.begin(), m_sys_options.end(), "extruders_count") == m_sys_options.end()) - group->back_to_sys_value("extruders_count"); - } - if (group->title == _("Size and coordinates")){ - if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()){ - group->back_to_sys_value("bed_shape"); - load_key_value("bed_shape", true/*some value*/, true); - } - } - if (group->title == _("Profile dependencies")){ - if (find(m_sys_options.begin(), m_sys_options.end(), "compatible_printers") == m_sys_options.end()){ - group->back_to_sys_value("compatible_printers"); - load_key_value("compatible_printers", true/*some value*/, true); - - bool is_empty = m_config->option("compatible_printers")->values.empty(); - m_compatible_printers_checkbox->SetValue(is_empty); - is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); - } - } - for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { - const std::string& opt_key = it->first; - if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) - group->back_to_sys_value(opt_key); - } - } - break; - } - - m_postpone_update_ui = false; - update_changed_ui(); -} +// void Tab::on_back_to_initial_value() +// { +// if (!m_is_modified_values) return; +// std::chrono::milliseconds ms1 = std::chrono::duration_cast( +// std::chrono::system_clock::now().time_since_epoch() +// ); +// +// m_postpone_update_ui = true; +// +// auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); +// for (auto page : m_pages) +// if (page->title() == selection) { +// for (auto group : page->m_optgroups){ +// if (group->title == _("Capabilities")){ +// if (find(m_dirty_options.begin(), m_dirty_options.end(), "extruders_count") != m_dirty_options.end()) +// group->back_to_initial_value("extruders_count"); +// } +// if (group->title == _("Size and coordinates")){ +// if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()){ +// group->back_to_initial_value("bed_shape"); +// load_key_value("bed_shape", true/*some value*/, true); +// } +// +// } +// if (group->title == _("Profile dependencies")){ +// if (find(m_dirty_options.begin(), m_dirty_options.end(), "compatible_printers") != m_dirty_options.end()){ +// group->back_to_initial_value("compatible_printers"); +// load_key_value("compatible_printers", true/*some value*/, true); +// +// bool is_empty = m_config->option("compatible_printers")->values.empty(); +// m_compatible_printers_checkbox->SetValue(is_empty); +// is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); +// } +// } +// for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { +// const std::string& opt_key = it->first; +// if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) +// group->back_to_initial_value(opt_key); +// } +// } +// break; +// } +// +// std::chrono::milliseconds ms2 = std::chrono::duration_cast( +// std::chrono::system_clock::now().time_since_epoch() +// ); +// +// m_postpone_update_ui = false; +// update_changed_ui(); +// +// std::chrono::milliseconds ms3 = std::chrono::duration_cast( +// std::chrono::system_clock::now().time_since_epoch() +// ); +// +// auto roll_back_duration = std::chrono::microseconds(ms2 - ms1).count(); +// auto update_ui_duration = std::chrono::microseconds(ms3 - ms2).count(); +// printf("back_to init_duration duration = %lld ms \n", roll_back_duration); +// printf("update_ui_duration duration = %lld ms \n", update_ui_duration); +// +// // m_postpone_update_ui = false; +// // update_changed_ui(); +// } +// +// void Tab::on_back_to_sys_value() +// { +// if (!m_is_nonsys_values) return; +// +// m_postpone_update_ui = true; +// +// auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); +// for (auto page : m_pages) +// if (page->title() == selection) { +// for (auto group : page->m_optgroups) { +// if (group->title == _("Capabilities")){ +// if (find(m_sys_options.begin(), m_sys_options.end(), "extruders_count") == m_sys_options.end()) +// group->back_to_sys_value("extruders_count"); +// } +// if (group->title == _("Size and coordinates")){ +// if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()){ +// group->back_to_sys_value("bed_shape"); +// load_key_value("bed_shape", true/*some value*/, true); +// } +// } +// if (group->title == _("Profile dependencies")){ +// if (find(m_sys_options.begin(), m_sys_options.end(), "compatible_printers") == m_sys_options.end()){ +// group->back_to_sys_value("compatible_printers"); +// load_key_value("compatible_printers", true/*some value*/, true); +// +// bool is_empty = m_config->option("compatible_printers")->values.empty(); +// m_compatible_printers_checkbox->SetValue(is_empty); +// is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); +// } +// } +// for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { +// const std::string& opt_key = it->first; +// if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) +// group->back_to_sys_value(opt_key); +// } +// } +// break; +// } +// +// m_postpone_update_ui = false; +// update_changed_ui(); +// } // Update the combo box label of the selected preset based on its "dirty" state, // comparing the selected preset config with $self->{config}. @@ -1698,12 +1877,34 @@ void TabPrinter::extruders_count_changed(size_t extruders_count){ } void TabPrinter::build_extruder_pages(){ - for (auto extruder_idx = m_extruder_pages.size(); extruder_idx < m_extruders_count; ++extruder_idx){ + if (m_extruders_count_old == m_extruders_count) + { + // if we have a single extruder MM setup, add a page with configuration options: + for (int i = 0; i < m_pages.size(); ++i) // first make sure it's not there already + if (m_pages[i]->title().find(_(L("Single extruder MM setup"))) != std::string::npos) { + m_pages.erase(m_pages.begin() + i); + break; + } + if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material")) { + // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves + auto page = add_options_page(_(L("Single extruder MM setup")), "printer_empty.png", true); + auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); + optgroup->append_single_option_line("cooling_tube_retraction"); + optgroup->append_single_option_line("cooling_tube_length"); + optgroup->append_single_option_line("parking_pos_retraction"); + m_pages.insert(m_pages.end()-2, page); + } + rebuild_page_tree(); + return; + } + + for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx){ //# build page char buf[MIN_BUF_LENGTH_FOR_L]; sprintf(buf, _CHB(L("Extruder %d")), extruder_idx + 1); auto page = add_options_page(from_u8(buf), "funnel.png", true); - m_extruder_pages.push_back(page); + m_pages.insert(m_pages.begin() + 2+extruder_idx, page); +// m_extruder_pages.push_back(page); auto optgroup = page->new_optgroup(_(L("Size"))); optgroup->append_single_option_line("nozzle_diameter", extruder_idx); @@ -1741,36 +1942,31 @@ void TabPrinter::build_extruder_pages(){ } // # remove extra pages - if (m_extruders_count <= m_extruder_pages.size()) { - m_extruder_pages.resize(m_extruders_count); - } +// if (m_extruders_count <= m_extruders_count_old) { +// m_extruder_pages.resize(m_extruders_count); +// } + + if (m_extruders_count < m_extruders_count_old) + m_pages.erase(m_pages.begin() + 2 + m_extruders_count, m_pages.begin() + 2 + m_extruders_count_old); + + m_extruders_count_old = m_extruders_count; // # rebuild page list - PageShp page_note = m_pages.back(); - m_pages.pop_back(); - while (m_pages.back()->title().find(_(L("Extruder"))) != std::string::npos) - m_pages.pop_back(); - for (auto page_extruder : m_extruder_pages) - m_pages.push_back(page_extruder); - m_pages.push_back(page_note); - - { - // if we have a single extruder MM setup, add a page with configuration options: - for (int i=0;ititle().find(_(L("Single extruder MM setup"))) != std::string::npos) { - m_pages.erase(m_pages.begin()+i); - break; - } - if ( m_extruder_pages.size()>1 && m_config->opt_bool("single_extruder_multi_material")) { - // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves - auto page = add_options_page(_(L("Single extruder MM setup")), "printer_empty.png",true); - auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); - optgroup->append_single_option_line("cooling_tube_retraction"); - optgroup->append_single_option_line("cooling_tube_length"); - optgroup->append_single_option_line("parking_pos_retraction"); - m_pages.insert(m_pages.begin()+1,page); - } - } +// PageShp page_note = m_pages.back(); +// m_pages.pop_back(); +// PageShp page_depend = m_pages.back(); +// m_pages.pop_back(); +// auto counter = m_extruders_count_old; +// while (counter > m_extruders_count/*m_pages.back()->title().find(_(L("Extruder"))) != std::string::npos*/){ +// m_pages.pop_back(); +// --counter; +// } +// // for (auto page_extruder : m_extruder_pages) +// // m_pages.push_back(page_extruder); +// for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) +// m_pages.push_back(m_extruder_pages[extruder_idx]); +// m_pages.push_back(page_note); +// m_pages.push_back(page_depend); rebuild_page_tree(); } @@ -1899,8 +2095,10 @@ void Tab::load_current_preset() static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); } - update_sys_ui_after_sel_preset(); - update_full_options_list(); +// update_sys_ui_after_sel_preset(); +// update_full_options_list(); + m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; + init_options_list(); update_changed_ui(); }); } @@ -1917,6 +2115,7 @@ void Tab::rebuild_page_tree() for (auto p : m_pages) { auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); if (p->title() == selected) { m_disable_tree_sel_changed_event = 1; m_treectrl->SelectItem(itemId); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 4668c1238a..2ac1e969b5 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -51,6 +51,7 @@ public: { Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_vsizer = new wxBoxSizer(wxVERTICAL); + m_item_color = &get_default_label_clr(); SetSizer(m_vsizer); } ~Page(){} @@ -71,6 +72,22 @@ public: Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; bool set_value(const t_config_option_key& opt_key, const boost::any& value); ConfigOptionsGroupShp new_optgroup(const wxString& title, int noncommon_label_width = -1); + + bool set_item_colour(const wxColour *clr) { + if (m_item_color != clr) { + m_item_color = clr; + return true; + } + return false; + } + + const wxColour get_item_colour() { + return *m_item_color; + } + +protected: + // Color of TreeCtrlItem. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. + const wxColour* m_item_color; }; // Slic3r::GUI::Tab; @@ -125,9 +142,13 @@ protected: bool m_no_controller; std::vector m_reload_dependent_tabs = {}; - std::vector m_dirty_options = {}; - std::vector m_sys_options = {}; - std::vector m_full_options_list = {}; +// std::vector m_dirty_options = {}; +// std::vector m_nonsys_options = {}; +// std::vector m_sys_options = {}; +// std::vector m_full_options_list = {}; + enum OptStatus { osSystemValue = 1, osInitValue = 2 }; + std::map m_options_list; + int m_opt_status_value; // The two following two event IDs are generated at Plater.pm by calling Wx::NewEventType. wxEventType m_event_value_change = 0; @@ -182,14 +203,15 @@ public: void update_show_hide_incompatible_button(); void update_ui_from_settings(); void update_changed_ui(); - void update_full_options_list(); - void update_sys_ui_after_sel_preset(); +// void update_full_options_list(); +// void update_sys_ui_after_sel_preset(); void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page); void update_changed_tree_ui(); void update_undo_buttons(); - void on_back_to_initial_value(); - void on_back_to_sys_value(); + void on_roll_back_value(const bool to_sys = false); +// void on_back_to_initial_value(); +// void on_back_to_sys_value(); PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); @@ -197,6 +219,7 @@ public: virtual void on_preset_loaded(){} virtual void build() = 0; virtual void update() = 0; + virtual void init_options_list(); void load_initial_data(); void update_dirty(); void update_tab_ui(); @@ -265,9 +288,10 @@ public: wxButton* m_octoprint_host_test_btn; size_t m_extruders_count; + size_t m_extruders_count_old = 0; size_t m_initial_extruders_count; size_t m_sys_extruders_count; - std::vector m_extruder_pages; +// std::vector m_extruder_pages; TabPrinter() {} TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {} @@ -279,6 +303,7 @@ public: void extruders_count_changed(size_t extruders_count); void build_extruder_pages(); void on_preset_loaded() override; + void init_options_list() override; }; class SavePresetWindow :public wxDialog From fa97a867519ef5dc91bf7fab4c7c5d3a104f0c50 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 18 Apr 2018 13:35:51 +0200 Subject: [PATCH 043/145] Implemented merging of system profiles from various vendors. --- xs/src/slic3r/GUI/Preset.cpp | 23 +++++++++++++++++++++ xs/src/slic3r/GUI/Preset.hpp | 3 +++ xs/src/slic3r/GUI/PresetBundle.cpp | 33 +++++++++++++++++++++++++++++- xs/src/slic3r/GUI/PresetBundle.hpp | 4 +++- 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 7fa9c998d7..15dcb2fbbc 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -790,6 +790,29 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name) return false; } +// Merge one vendor's presets with the other vendor's presets, report duplicates. +std::vector PresetCollection::merge_presets(PresetCollection &&other, const std::set &new_vendors) +{ + std::vector duplicates; + for (Preset &preset : other.m_presets) { + if (preset.is_default || preset.is_external) + continue; + Preset key(m_type, preset.name); + auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key); + if (it == m_presets.end() || it->name != preset.name) { + if (preset.vendor != nullptr) { + // Re-assign a pointer to the vendor structure in the new PresetBundle. + auto it = new_vendors.find(*preset.vendor); + assert(it != new_vendors.end()); + preset.vendor = &(*it); + } + this->m_presets.emplace(it, std::move(preset)); + } else + duplicates.emplace_back(std::move(preset.name)); + } + return duplicates; +} + std::string PresetCollection::name() const { switch (this->type()) { diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 480d6b1787..e80f138ee8 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -336,6 +336,9 @@ protected: // This is a temporary state, which shall be fixed immediately by the following step. bool select_preset_by_name_strict(const std::string &name); + // Merge one vendor's presets with the other vendor's presets, report duplicates. + std::vector merge_presets(PresetCollection &&other, const std::set &new_vendors); + private: PresetCollection(); PresetCollection(const PresetCollection &other); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 6f0754a035..717e7a6f04 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -178,6 +178,7 @@ std::string PresetBundle::load_system_presets() // Here the vendor specific read only Config Bundles are stored. boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred(); std::string errors_cummulative; + bool first = true; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) { std::string name = dir_entry.path().filename().string(); @@ -185,7 +186,25 @@ std::string PresetBundle::load_system_presets() name.erase(name.size() - 4); try { // Load the config bundle, flatten it. - this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + if (first) { + // Reset this PresetBundle and load the first vendor config. + this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + first = false; + } else { + // Load the other vendor configs, merge them with this PresetBundle. + // Report duplicate profiles. + PresetBundle other; + other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + std::vector duplicates = this->merge_presets(std::move(other)); + if (! duplicates.empty()) { + errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: "; + for (size_t i = 0; i < duplicates.size(); ++ i) { + if (i > 0) + errors_cummulative += ", "; + errors_cummulative += duplicates[i]; + } + } + } } catch (const std::runtime_error &err) { errors_cummulative += err.what(); errors_cummulative += "\n"; @@ -194,6 +213,18 @@ std::string PresetBundle::load_system_presets() return errors_cummulative; } +// Merge one vendor's presets with the other vendor's presets, report duplicates. +std::vector PresetBundle::merge_presets(PresetBundle &&other) +{ + this->vendors.insert(other.vendors.begin(), other.vendors.end()); + std::vector duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors); + std::vector duplicate_filaments = this->filaments.merge_presets(std::move(other.filaments), this->vendors); + std::vector duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors); + append(duplicate_prints, std::move(duplicate_filaments)); + append(duplicate_prints, std::move(duplicate_printers)); + return duplicate_prints; +} + static inline std::string remove_ini_suffix(const std::string &name) { std::string out = name; diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index 27add21eec..9f2afbead2 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -116,10 +116,12 @@ public: // preset if the current print or filament preset is not compatible. void update_compatible_with_printer(bool select_other_if_incompatible); - static bool parse_color(const std::string &scolor, unsigned char *rgb_out); + static bool parse_color(const std::string &scolor, unsigned char *rgb_out); private: std::string load_system_presets(); + // Merge one vendor's presets with the other vendor's presets, report duplicates. + std::vector merge_presets(PresetBundle &&other); // Set the "enabled" flag for printer vendors, printer models and printer variants // based on the user configuration. From f38f0edaaf3dc2b7c748e3b7c642ecbeaf140113 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 18 Apr 2018 14:15:13 +0200 Subject: [PATCH 044/145] Cleaned code from commented code --- xs/src/slic3r/GUI/Tab.cpp | 333 +------------------------------------- xs/src/slic3r/GUI/Tab.hpp | 9 -- 2 files changed, 7 insertions(+), 335 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index dcf61f5749..2cc51e0b5f 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -248,37 +248,6 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo return page; } -// template -// void add_correct_opts_to_dirty_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) -// { -// auto opt_init = static_cast(tab->m_presets->get_selected_preset().config.option(opt_key)); -// auto opt_cur = static_cast(tab->m_config->option(opt_key)); -// int opt_init_max_id = opt_init->values.size()-1; -// for (int i = 0; i < opt_cur->values.size(); i++) -// { -// int init_id = i <= opt_init_max_id ? i : 0; -// if (opt_cur->values[i] != opt_init->values[init_id]) -// vec->emplace_back(opt_key + "#" + std::to_string(i)); -// } -// } -// -// template -// void add_correct_opts_to_sys_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) -// { -// const Preset* sys_preset = tab->m_presets->get_selected_preset_parent(); -// if (sys_preset == nullptr) -// return; -// T *opt_cur = static_cast(tab->m_config->option(opt_key)); -// const T *opt_sys = static_cast(sys_preset->config.option(opt_key)); -// int opt_max_id = opt_sys->values.size()-1; -// for (int i = 0; i < opt_cur->values.size(); i++) -// { -// int init_id = i <= opt_max_id ? i : 0; -// if (opt_cur->values[i] == opt_sys->values[init_id]) -// vec->emplace_back(opt_key + "#" + std::to_string(i)); -// } -// } - // Update UI according to changes void Tab::update_changed_ui() { @@ -286,26 +255,25 @@ void Tab::update_changed_ui() return; const bool is_printer_type = (name() == "printer"); - auto m_dirty_options = m_presets->current_dirty_options(is_printer_type); - auto m_nonsys_options = m_presets->current_different_from_parent_options(is_printer_type); + auto dirty_options = m_presets->current_dirty_options(is_printer_type); + auto nonsys_options = m_presets->current_different_from_parent_options(is_printer_type); if (is_printer_type){ TabPrinter* tab = static_cast(this); if (tab->m_initial_extruders_count != tab->m_extruders_count) - m_dirty_options.emplace_back("extruders_count"); + dirty_options.emplace_back("extruders_count"); if (tab->m_sys_extruders_count != tab->m_extruders_count) - m_nonsys_options.emplace_back("extruders_count"); + nonsys_options.emplace_back("extruders_count"); } for (auto& it : m_options_list) it.second = m_opt_status_value; -// auto m_options_list = m_options_list; - for (auto opt_key : m_dirty_options) m_options_list[opt_key] &= ~osInitValue; - for (auto opt_key : m_nonsys_options) m_options_list[opt_key] &= ~osSystemValue; + for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; + for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; Freeze(); //update options "decoration" - for (const auto opt : m_options_list/*m_options_list*/) + for (const auto opt : m_options_list) { bool is_nonsys_value = false; bool is_modified_value = true; @@ -314,17 +282,14 @@ void Tab::update_changed_ui() const wxColour *color = &m_sys_label_clr; if ((opt.second & osSystemValue) == 0){ -// if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { is_nonsys_value = true; sys_icon = m_bmp_non_system; if ((opt.second & osInitValue) != 0) -// if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) color = &m_default_text_clr; else color = &m_modified_label_clr; } if ((opt.second & osInitValue) != 0) -// if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) { is_modified_value = false; icon = &m_bmp_white_bullet; @@ -346,107 +311,7 @@ void Tab::update_changed_ui() field->set_label_colour(color); } Thaw(); -// m_options_list = m_options_list; -/* auto dirty_options = m_presets->current_dirty_options(); - if (){ - // Update dirty_options in case changes of Extruder's options - TabPrinter* tab = static_cast(this); - m_dirty_options.resize(0); - for (auto opt_key : dirty_options) - { - if (opt_key == "bed_shape"){ m_dirty_options.emplace_back(opt_key); continue; } - switch (m_config->option(opt_key)->type()) - { - case coInts: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coBools: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coFloats: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coStrings: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coPercents:add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coPoints: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - default: m_dirty_options.emplace_back(opt_key); break; - } - } - if (tab->m_initial_extruders_count != tab->m_extruders_count) - m_dirty_options.emplace_back("extruders_count"); - - m_sys_options.resize(0); - const auto sys_preset = m_presets->get_selected_preset_parent(); - if (sys_preset){ - for (auto opt_key : m_config->keys()) - { - if (opt_key == "bed_shape"){ - if (*tab->m_config->option(opt_key) == *sys_preset->config.option(opt_key)) - m_sys_options.emplace_back(opt_key); - continue; - } - switch (m_config->option(opt_key)->type()) - { - case coInts: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coBools: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coFloats: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coStrings: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coPercents:add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coPoints: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - default:{ - const ConfigOption *opt_cur = tab->m_config->option(opt_key); - const ConfigOption *opt_sys = sys_preset->config.option(opt_key); - if (opt_cur != nullptr && opt_sys != nullptr && *opt_cur == *opt_sys) - m_sys_options.emplace_back(opt_key); - break; - } - } - } - - if (tab->m_sys_extruders_count == tab->m_extruders_count) - m_sys_options.emplace_back("extruders_count"); - } - } - else{ - m_sys_options = m_presets->system_equal_options(); - m_dirty_options = dirty_options; - } - - Freeze(); - //update options "decoration" - for (const auto opt_key : m_full_options_list) - { - bool is_nonsys_value = false; - bool is_modified_value = true; - const wxBitmap *sys_icon = &m_bmp_value_lock; - const wxBitmap *icon = &m_bmp_value_revert; - const wxColour *color = &m_sys_label_clr; - if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { - is_nonsys_value = true; - sys_icon = m_bmp_non_system; - if(find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) - color = &m_default_text_clr; - else - color = &m_modified_label_clr; - } - if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) - { - is_modified_value = false; - icon = &m_bmp_white_bullet; - } - if (opt_key == "bed_shape" || opt_key == "compatible_printers") { - if (m_colored_Label != nullptr) { - m_colored_Label->SetForegroundColour(*color); - m_colored_Label->Refresh(true); - } - continue; - } - - Field* field = get_field(opt_key); - if (field == nullptr) continue; - field->m_is_nonsys_value = is_nonsys_value; - field->m_is_modified_value = is_modified_value; - field->set_undo_bitmap(icon); - field->set_undo_to_sys_bitmap(sys_icon); - field->set_label_colour(color); - } - Thaw(); -*/ wxTheApp->CallAfter([this]() { update_changed_tree_ui(); }); @@ -494,60 +359,6 @@ void TabPrinter::init_options_list() m_options_list.emplace("extruders_count", m_opt_status_value); } - -// template -// void add_correct_opts_to_full_options_list(const std::string &opt_key, std::vector *vec, TabPrinter *tab) -// { -// T *opt_cur = static_cast(tab->m_config->option(opt_key)); -// for (int i = 0; i < opt_cur->values.size(); i++) -// vec->emplace_back(opt_key + "#" + std::to_string(i)); -// } - -// void Tab::update_full_options_list() -// { -// if (!m_full_options_list.empty()) -// m_full_options_list.resize(0); -// -// if (m_name != "printer"){ -// m_full_options_list = m_config->keys(); -// return; -// } -// -// TabPrinter* tab = static_cast(this); -// for (const auto opt_key : m_config->keys()) -// { -// if (opt_key == "bed_shape"){ -// m_full_options_list.emplace_back(opt_key); -// continue; -// } -// switch (m_config->option(opt_key)->type()) -// { -// case coInts: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; -// case coBools: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; -// case coFloats: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; -// case coStrings: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; -// case coPercents:add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; -// case coPoints: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; -// default: m_full_options_list.emplace_back(opt_key); break; -// } -// } -// m_full_options_list.emplace_back("extruders_count"); -// } - -// void Tab::update_sys_ui_after_sel_preset() -// { -// const wxColour* clr = &m_default_text_clr; -// for (const auto opt_key : m_full_options_list){ -// Field* field = get_field(opt_key); -// if (field != nullptr){ -// field->set_undo_to_sys_bitmap(m_bmp_non_system); -// field->m_is_nonsys_value = true; -// field->set_label_colour(clr); -// } -// } -// m_sys_options.resize(0); -// } - void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) { auto opt = m_options_list.find(opt_key); @@ -666,112 +477,6 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) update_changed_ui(); } -// void Tab::on_back_to_initial_value() -// { -// if (!m_is_modified_values) return; -// std::chrono::milliseconds ms1 = std::chrono::duration_cast( -// std::chrono::system_clock::now().time_since_epoch() -// ); -// -// m_postpone_update_ui = true; -// -// auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); -// for (auto page : m_pages) -// if (page->title() == selection) { -// for (auto group : page->m_optgroups){ -// if (group->title == _("Capabilities")){ -// if (find(m_dirty_options.begin(), m_dirty_options.end(), "extruders_count") != m_dirty_options.end()) -// group->back_to_initial_value("extruders_count"); -// } -// if (group->title == _("Size and coordinates")){ -// if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()){ -// group->back_to_initial_value("bed_shape"); -// load_key_value("bed_shape", true/*some value*/, true); -// } -// -// } -// if (group->title == _("Profile dependencies")){ -// if (find(m_dirty_options.begin(), m_dirty_options.end(), "compatible_printers") != m_dirty_options.end()){ -// group->back_to_initial_value("compatible_printers"); -// load_key_value("compatible_printers", true/*some value*/, true); -// -// bool is_empty = m_config->option("compatible_printers")->values.empty(); -// m_compatible_printers_checkbox->SetValue(is_empty); -// is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); -// } -// } -// for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { -// const std::string& opt_key = it->first; -// if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) -// group->back_to_initial_value(opt_key); -// } -// } -// break; -// } -// -// std::chrono::milliseconds ms2 = std::chrono::duration_cast( -// std::chrono::system_clock::now().time_since_epoch() -// ); -// -// m_postpone_update_ui = false; -// update_changed_ui(); -// -// std::chrono::milliseconds ms3 = std::chrono::duration_cast( -// std::chrono::system_clock::now().time_since_epoch() -// ); -// -// auto roll_back_duration = std::chrono::microseconds(ms2 - ms1).count(); -// auto update_ui_duration = std::chrono::microseconds(ms3 - ms2).count(); -// printf("back_to init_duration duration = %lld ms \n", roll_back_duration); -// printf("update_ui_duration duration = %lld ms \n", update_ui_duration); -// -// // m_postpone_update_ui = false; -// // update_changed_ui(); -// } -// -// void Tab::on_back_to_sys_value() -// { -// if (!m_is_nonsys_values) return; -// -// m_postpone_update_ui = true; -// -// auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); -// for (auto page : m_pages) -// if (page->title() == selection) { -// for (auto group : page->m_optgroups) { -// if (group->title == _("Capabilities")){ -// if (find(m_sys_options.begin(), m_sys_options.end(), "extruders_count") == m_sys_options.end()) -// group->back_to_sys_value("extruders_count"); -// } -// if (group->title == _("Size and coordinates")){ -// if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()){ -// group->back_to_sys_value("bed_shape"); -// load_key_value("bed_shape", true/*some value*/, true); -// } -// } -// if (group->title == _("Profile dependencies")){ -// if (find(m_sys_options.begin(), m_sys_options.end(), "compatible_printers") == m_sys_options.end()){ -// group->back_to_sys_value("compatible_printers"); -// load_key_value("compatible_printers", true/*some value*/, true); -// -// bool is_empty = m_config->option("compatible_printers")->values.empty(); -// m_compatible_printers_checkbox->SetValue(is_empty); -// is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); -// } -// } -// for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { -// const std::string& opt_key = it->first; -// if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) -// group->back_to_sys_value(opt_key); -// } -// } -// break; -// } -// -// m_postpone_update_ui = false; -// update_changed_ui(); -// } - // Update the combo box label of the selected preset based on its "dirty" state, // comparing the selected preset config with $self->{config}. void Tab::update_dirty(){ @@ -1904,7 +1609,6 @@ void TabPrinter::build_extruder_pages(){ sprintf(buf, _CHB(L("Extruder %d")), extruder_idx + 1); auto page = add_options_page(from_u8(buf), "funnel.png", true); m_pages.insert(m_pages.begin() + 2+extruder_idx, page); -// m_extruder_pages.push_back(page); auto optgroup = page->new_optgroup(_(L("Size"))); optgroup->append_single_option_line("nozzle_diameter", extruder_idx); @@ -1942,32 +1646,11 @@ void TabPrinter::build_extruder_pages(){ } // # remove extra pages -// if (m_extruders_count <= m_extruders_count_old) { -// m_extruder_pages.resize(m_extruders_count); -// } - if (m_extruders_count < m_extruders_count_old) m_pages.erase(m_pages.begin() + 2 + m_extruders_count, m_pages.begin() + 2 + m_extruders_count_old); m_extruders_count_old = m_extruders_count; - // # rebuild page list -// PageShp page_note = m_pages.back(); -// m_pages.pop_back(); -// PageShp page_depend = m_pages.back(); -// m_pages.pop_back(); -// auto counter = m_extruders_count_old; -// while (counter > m_extruders_count/*m_pages.back()->title().find(_(L("Extruder"))) != std::string::npos*/){ -// m_pages.pop_back(); -// --counter; -// } -// // for (auto page_extruder : m_extruder_pages) -// // m_pages.push_back(page_extruder); -// for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) -// m_pages.push_back(m_extruder_pages[extruder_idx]); -// m_pages.push_back(page_note); -// m_pages.push_back(page_depend); - rebuild_page_tree(); } @@ -2095,8 +1778,6 @@ void Tab::load_current_preset() static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); } -// update_sys_ui_after_sel_preset(); -// update_full_options_list(); m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; init_options_list(); update_changed_ui(); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 2ac1e969b5..2eef29500f 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -142,10 +142,6 @@ protected: bool m_no_controller; std::vector m_reload_dependent_tabs = {}; -// std::vector m_dirty_options = {}; -// std::vector m_nonsys_options = {}; -// std::vector m_sys_options = {}; -// std::vector m_full_options_list = {}; enum OptStatus { osSystemValue = 1, osInitValue = 2 }; std::map m_options_list; int m_opt_status_value; @@ -203,15 +199,11 @@ public: void update_show_hide_incompatible_button(); void update_ui_from_settings(); void update_changed_ui(); -// void update_full_options_list(); -// void update_sys_ui_after_sel_preset(); void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page); void update_changed_tree_ui(); void update_undo_buttons(); void on_roll_back_value(const bool to_sys = false); -// void on_back_to_initial_value(); -// void on_back_to_sys_value(); PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); @@ -291,7 +283,6 @@ public: size_t m_extruders_count_old = 0; size_t m_initial_extruders_count; size_t m_sys_extruders_count; -// std::vector m_extruder_pages; TabPrinter() {} TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {} From 8ab62d702c4a97579a7afbb455982c4d006ac911 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 18 Apr 2018 18:06:07 +0200 Subject: [PATCH 045/145] Fixed memory leak of TabIface. Added documentation of the XS interface on how the Ref<> and Clone<> wrappers work. --- xs/src/xsinit.h | 26 ++++++++++++++++++++++++-- xs/xsp/GUI.xsp | 2 +- xs/xsp/typemap.xspt | 1 - 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 96c4b74d74..9cb60384f6 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -85,8 +85,12 @@ namespace Slic3r { template struct ClassTraits { + // Name of a Perl alias of a C++ class type, owned by Perl, reference counted. static const char* name; - static const char* name_ref; + // Name of a Perl alias of a C++ class type, owned by the C++ code. + // The references shall be enumerated at the end of XS.pm, where the desctructor is undefined with sub DESTROY {}, + // so Perl will never delete the object instance. + static const char* name_ref; }; // use this for typedefs for which the forward prototype @@ -99,11 +103,16 @@ struct ClassTraits { class cname; \ __REGISTER_CLASS(cname, perlname); +// Return Perl alias to a C++ class name. template const char* perl_class_name(const T*) { return ClassTraits::name; } +// Return Perl alias to a C++ class name, suffixed with ::Ref. +// Such a C++ class instance will not be destroyed by Perl, the instance destruction is left to the C++ code. template const char* perl_class_name_ref(const T*) { return ClassTraits::name_ref; } +// Mark the Perl SV (Scalar Value) as owning a "blessed" pointer to an object reference. +// Perl will never release the C++ instance. template SV* perl_to_SV_ref(T &t) { SV* sv = newSV(0); @@ -111,6 +120,8 @@ SV* perl_to_SV_ref(T &t) { return sv; } +// Mark the Perl SV (Scalar Value) as owning a "blessed" pointer to an object instance. +// Perl will own the C++ instance, therefore it will also release it. template SV* perl_to_SV_clone_ref(const T &t) { SV* sv = newSV(0); @@ -118,6 +129,8 @@ SV* perl_to_SV_clone_ref(const T &t) { return sv; } +// Reference wrapper to provide a C++ instance to Perl while keeping Perl from destroying the instance. +// The instance is created temporarily by XS.cpp just to provide Perl with a CLASS name and a object instance pointer. template class Ref { T* val; @@ -125,10 +138,15 @@ public: Ref() : val(NULL) {} Ref(T* t) : val(t) {} Ref(const T* t) : val(const_cast(t)) {} + // Called by XS.cpp to convert the referenced object instance to a Perl SV, before it is blessed with the name + // returned by CLASS() operator T*() const { return val; } + // Name to bless the Perl SV with. The name ends with a "::Ref" suffix to keep Perl from destroying the object instance. static const char* CLASS() { return ClassTraits::name_ref; } }; - + +// Wrapper to clone a C++ object instance before passing it to Perl for ownership. +// This wrapper instance is created temporarily by XS.cpp to provide Perl with a CLASS name and a object instance pointer. template class Clone { T* val; @@ -136,7 +154,11 @@ public: Clone() : val(NULL) {} Clone(T* t) : val(new T(*t)) {} Clone(const T& t) : val(new T(t)) {} + // Called by XS.cpp to convert the cloned object instance to a Perl SV, before it is blessed with the name + // returned by CLASS() operator T*() const { return val; } + // Name to bless the Perl SV with. If there is a destructor registered in the XSP file for this class, then Perl will + // call this destructor when the reference counter of this SV drops to zero. static const char* CLASS() { return ClassTraits::name; } }; diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 1376ff164b..406d718286 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -39,7 +39,7 @@ void add_debug_menu(SV *ui, int event_language_change) void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) %code%{ Slic3r::GUI::create_preset_tabs(no_controller, event_value_change, event_presets_changed); %}; -Ref get_preset_tab(char *name) +TabIface* get_preset_tab(char *name) %code%{ RETVAL=Slic3r::GUI::get_preset_tab_iface(name); %}; bool load_language() diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 0214a158d6..797f3c19f1 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -211,7 +211,6 @@ %typemap{PresetHints*}; %typemap{Ref}{simple}; %typemap{TabIface*}; -%typemap{Ref}{simple}; %typemap{PrintRegionPtrs*}; %typemap{PrintObjectPtrs*}; From c3c9ebdd12616aaa79ea0e6d2a06d3db06d36f31 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 18 Apr 2018 18:48:32 +0200 Subject: [PATCH 046/145] Fix wxPerl warning annoyance --- lib/Slic3r.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index e55e24a7ad..19915ddd86 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -277,5 +277,6 @@ sub system_info # this package declaration prevents an ugly fatal warning to be emitted when # spawning a new thread package GLUquadricObjPtr; +package Wx::Printout; 1; From d1580f67dfd21420e2b813dba4b682602f0c8973 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 18 Apr 2018 18:57:34 +0200 Subject: [PATCH 047/145] Fix of the previous commit. Once the Slic3r::GUI::Tab was rewritten from Ref to TabIface*, Perl takes ownership and the Tab is being incorrectly deleted by the background threads. --- lib/Slic3r.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index e55e24a7ad..5e91f56ce6 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -162,6 +162,7 @@ sub thread_cleanup { *Slic3r::TriangleMesh::DESTROY = sub {}; *Slic3r::GUI::AppConfig::DESTROY = sub {}; *Slic3r::GUI::PresetBundle::DESTROY = sub {}; + *Slic3r::GUI::Tab::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } From 7083f58326c07050349fe8c0d999a1ee49702e5c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 19 Apr 2018 12:08:59 +0200 Subject: [PATCH 048/145] Added lock icons to system presets in ComboBoxes. Added right event handling if informative string is selected in ComboBoxes --- xs/src/slic3r/GUI/Preset.cpp | 109 ++++++++++++++++++++--------- xs/src/slic3r/GUI/Preset.hpp | 8 ++- xs/src/slic3r/GUI/PresetBundle.cpp | 25 ++++--- xs/src/slic3r/GUI/Tab.cpp | 10 ++- xs/src/slic3r/GUI/Tab.hpp | 2 + 5 files changed, 106 insertions(+), 48 deletions(-) diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 30f49df08f..04c5865b84 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -2,6 +2,7 @@ #include #include "Preset.hpp" +#include "BitmapCache.hpp" #include #include @@ -252,7 +253,8 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectorFreeze(); ui->Clear(); - std::map nonsys_presets; + + const Preset &selected_preset = this->get_selected_preset(); + // Show wide icons if the currently selected preset is not compatible with the current printer, + // and draw a red flag in front of the selected preset. + bool wide_icons = !selected_preset.is_compatible && m_bitmap_incompatible != nullptr; + + std::map nonsys_presets; wxString selected = ""; - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { + if (!this->m_presets.front().is_visible) + ui->Append("------- System presets -------", wxNullBitmap); + for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) continue; - const wxBitmap *bmp = (i == 0 || preset.is_compatible) ? m_bitmap_main_frame : m_bitmap_incompatible; -// ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), -// (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); -// if (i == m_idx_selected) -// ui->SetSelection(ui->GetCount() - 1); + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(16, 16) : *m_bitmap_incompatible); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(4, 16)); + bmps.emplace_back(*m_bitmap_main_frame); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(6, 16)); + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(16, 16)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } if (preset.is_default || preset.is_system){ ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), @@ -531,20 +559,18 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) } else { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), preset.is_compatible); + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } if (preset.is_default) - ui->Append("------------------------------------", wxNullBitmap); + ui->Append("------- System presets -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------------------------------------", wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - const wxBitmap *bmp = it->second ? m_bitmap_compatible : m_bitmap_incompatible; - ui->Append(it->first, - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + ui->Append("------- User presets -------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); if (it->first == selected) ui->SetSelection(ui->GetCount() - 1); } @@ -552,51 +578,63 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) ui->Thaw(); } -void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) +size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) { if (ui == nullptr) - return; + return 0; ui->Freeze(); ui->Clear(); - std::map nonsys_presets; + size_t selected_preset_item = 0; + + std::map nonsys_presets; wxString selected = ""; - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { + if (!this->m_presets.front().is_visible) + ui->Append("------- System presets -------", wxNullBitmap); + for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) continue; - const wxBitmap *bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; -// ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), -// (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); -// if (i == m_idx_selected) -// ui->SetSelection(ui->GetCount() - 1); + std::string bitmap_key = "tab"; + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; + bmps.emplace_back((tmp_bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *tmp_bmp); + // Paint a lock at the system presets. + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(16, 16)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } if (preset.is_default || preset.is_system){ ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); if (i == m_idx_selected) - ui->SetSelection(ui->GetCount() - 1); + selected_preset_item = ui->GetCount() - 1; } else { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), preset.is_compatible); + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } if (preset.is_default) - ui->Append("------------------------------------", wxNullBitmap); + ui->Append("------- System presets -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------------------------------------", wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - const wxBitmap *bmp = it->second ? m_bitmap_compatible : m_bitmap_incompatible; - ui->Append(it->first, - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + ui->Append("------- User presets -------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); if (it->first == selected) - ui->SetSelection(ui->GetCount() - 1); + selected_preset_item = ui->GetCount() - 1; } } + ui->SetSelection(selected_preset_item); ui->Thaw(); + return selected_preset_item; } // Update a dirty floag of the current preset, update the labels of the UI component accordingly. @@ -692,6 +730,11 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b // If the first visible preset was not found, return the 0th element, which is the default preset. } + // Temporary decision + if (name_w_suffix == "------- System presets -------" || + name_w_suffix == "------- User presets -------") + return true; + // 2) Select the new preset. if (m_idx_selected != idx || force) { this->select_preset(idx); diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index e12f18b885..cc409addc4 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -13,6 +13,10 @@ class wxItemContainer; namespace Slic3r { +namespace GUI { + class BitmapCache; +} + enum ConfigFileType { CONFIG_FILE_TYPE_UNKNOWN, @@ -264,7 +268,7 @@ public: // Update the choice UI from the list of presets. // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. - void update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible); + size_t update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible); // Update the choice UI from the list of presets. // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. @@ -324,6 +328,8 @@ private: wxBitmap *m_bitmap_main_frame; // Path to the directory to store the config files into. std::string m_dir_path; + // Caching color bitmaps for the filament combo box. + GUI::BitmapCache *m_bitmap_cache = nullptr; }; } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 1c3a6dcac8..d6cde84a89 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -1025,9 +1025,11 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma // and draw a red flag in front of the selected preset. bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr; assert(selected_preset != nullptr); - std::map nonsys_presets; + std::map nonsys_presets; wxString selected_str = ""; - for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++ i) { + if (!this->filaments().front().is_visible) + ui->Append("------- System presets -------", wxNullBitmap); + for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++i) { const Preset &preset = this->filaments.preset(i); bool selected = this->filament_presets[idx_extruder] == preset.name; if (! preset.is_visible || (! preset.is_compatible && ! selected)) @@ -1059,14 +1061,11 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma bmps.emplace_back(m_bitmapCache->mksolid(8, 16, rgb)); } // Paint a lock at the system presets. - bmps.emplace_back(m_bitmapCache->mkclear(4, 16)); - bmps.emplace_back((preset.is_system || preset.is_default) ? - (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); + bmps.emplace_back(m_bitmapCache->mkclear(2, 16)); + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmapLock : m_bitmapCache->mkclear(16, 16)); +// (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); bitmap = m_bitmapCache->insert(bitmap_key, bmps); } -// ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); -// if (selected) -// ui->SetSelection(ui->GetCount() - 1); if (preset.is_default || preset.is_system){ ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), @@ -1077,19 +1076,19 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma else { nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - (bitmap == 0) ? wxNullBitmap : *bitmap); + (bitmap == 0) ? &wxNullBitmap : bitmap); if (selected) selected_str = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } if (preset.is_default) - ui->Append("------------------------------------", wxNullBitmap); + ui->Append("------- System presets -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------------------------------------", wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, it->second); + ui->Append("------- User presets -------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); if (it->first == selected_str) ui->SetSelection(ui->GetCount() - 1); } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 2cc51e0b5f..810ee6e141 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -198,8 +198,16 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) //! select_preset(m_presets_choice->GetStringSelection().ToStdString()); //! we doing next: int selected_item = m_presets_choice->GetSelection(); + if (m_selected_preset_item == selected_item) + return; if (selected_item >= 0){ std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); + if (selected_string == "------- System presets -------" || + selected_string == "------- User presets -------"){ + m_presets_choice->SetSelection(m_selected_preset_item); + return; + } + m_selected_preset_item = selected_item; select_preset(selected_string); } })); @@ -488,7 +496,7 @@ void Tab::update_dirty(){ void Tab::update_tab_ui() { - m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets); + m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets); // update_tab_presets(m_cc_presets_choice, m_show_incompatible_presets); // update_presetsctrl(m_presetctrl, m_show_incompatible_presets); } diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 2eef29500f..d5a679564f 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -154,6 +154,8 @@ protected: bool m_is_nonsys_values{ true }; bool m_postpone_update_ui {false}; + size_t m_selected_preset_item{ 0 }; + public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; From ad4cd058505ae33ce7e0b23fc513384f2de44805 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 19 Apr 2018 13:31:50 +0200 Subject: [PATCH 049/145] Fixed hovering while panning/rotating camera --- lib/Slic3r/GUI/3DScene.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index d8af6f71cc..65abf850ec 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -70,6 +70,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init _legend_enabled _warning_enabled _apply_zoom_to_volumes_filter + _mouse_dragging ) ); @@ -146,6 +147,7 @@ sub new { $self->_warning_enabled(0); $self->use_plain_shader(0); $self->_apply_zoom_to_volumes_filter(0); + $self->_mouse_dragging(0); # Collection of GLVolume objects $self->volumes(Slic3r::GUI::_3DScene::GLVolume::Collection->new); @@ -381,6 +383,8 @@ sub mouse_event { my $pos = Slic3r::Pointf->new($e->GetPositionXY); my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id_for_variable_layer_height_editing : -1; + $self->_mouse_dragging($e->Dragging); + if ($e->Entering && &Wx::wxMSW) { # wxMSW needs focus in order to catch mouse wheel events $self->SetFocus; @@ -1182,7 +1186,7 @@ sub Render { # Head light glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); - if ($self->enable_picking) { + if ($self->enable_picking && !$self->_mouse_dragging) { if (my $pos = $self->_mouse_pos) { # Render the object for picking. # FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing. From 88dadcec78185c00bef759643c3ccd427f9e0d06 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 19 Apr 2018 16:20:30 +0200 Subject: [PATCH 050/145] Added tooltips for reverse buttons * Corrected default size of undo buttons for GTK * Experiment with rich tooltips --- xs/src/slic3r/GUI/Field.cpp | 8 +++++-- xs/src/slic3r/GUI/Tab.cpp | 42 +++++++++++++++++++++++++++++++++++-- xs/src/slic3r/GUI/Tab.hpp | 16 ++++++++++++-- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index e959c7966c..ff17a2ef8f 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -20,8 +20,12 @@ namespace Slic3r { namespace GUI { void Field::PostInitialize(){ auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(16, 16), wxNO_BORDER); - m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(16, 16), wxNO_BORDER); + auto sz = 16; + #ifdef __WXGTK__ + sz = 28 + #endif // __WXGTK__ + m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); + m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); if (wxMSW) { m_Undo_btn->SetBackgroundColour(color); m_Undo_to_sys_btn->SetBackgroundColour(color); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 810ee6e141..0491965990 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -102,10 +102,42 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_undo_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); m_undo_to_sys_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_question_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_undo_btns_legent = new wxStaticText(panel, wxID_ANY, _(L("For more information about buttons hover the mouse cursor over them.")), wxDefaultPosition, wxDefaultSize); if (wxMSW) { m_undo_btn->SetBackgroundColour(color); m_undo_to_sys_btn->SetBackgroundColour(color); + m_question_btn->SetBackgroundColour(color); } + +#ifdef __WXMSW__ + m_undo_to_sys_btn->SetToolTip(_(L( "Unlocked lock icon indicates about some value changes compared with system values " + "in current option group.\n" + "Locked lock icon indicates about same values compared with system values " + "in current option group.\n" + "White bullet icon indicates about nonsystem preset.\n" + "Click the unlocked lock to revert all values in current option group to system values."))); +#else + // ToolTips to undo buttons + m_undo_btn_tip = new wxRichToolTip("Information about current button", + _(L("Unlocked lock icon indicates about some value changes compared with system values " + "in current option group.\n" + "Locked lock icon indicates about same values compared with system values " + "in current option group.\n" + "White bullet icon indicates about nonsystem preset.\n" + "Click the unlocked lock to revert all values in current option group to system values."))); + m_undo_btn_tip->SetIcon(wxICON_INFORMATION); + m_undo_btn_tip->ShowFor(m_undo_btn); +#endif //__WXMSW__ + + m_undo_btn->SetToolTip(_(L( "Back arrow icon indicates about some value changes compared with last saved preset values " + "in current option group.\n" + "White bullet icon indicates about same values compared with last saved preset values " + "in current option group.\n" + "Click the Back arrow to revert all values in current option group to last saved preset values."))); + + m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information."))); + // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. m_bmp_value_lock .LoadFile(from_u8(var("sys_lock.png")), wxBITMAP_TYPE_PNG); m_bmp_value_unlock .LoadFile(from_u8(var("sys_unlock.png")), wxBITMAP_TYPE_PNG); @@ -113,10 +145,13 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) // Bitmaps to be shown on the "Undo user changes" button next to each input field. m_bmp_value_revert .LoadFile(from_u8(var("action_undo.png")), wxBITMAP_TYPE_PNG); m_bmp_white_bullet .LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); + m_bmp_question .LoadFile(from_u8(var("question_mark_01.png")), wxBITMAP_TYPE_PNG); + m_undo_btn->SetBitmap(m_bmp_white_bullet); - m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ /*on_back_to_initial_value*/on_roll_back_value(); })); + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(); })); m_undo_to_sys_btn->SetBitmap(m_bmp_white_bullet); - m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ /*on_back_to_sys_value*/on_roll_back_value(true); })); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(true); })); + m_question_btn->SetBitmap(m_bmp_question); // Colors for ui "decoration" m_sys_label_clr = get_sys_label_clr(); @@ -135,6 +170,9 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer->AddSpacer(64); m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->AddSpacer(16); + m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_undo_btns_legent, 0, wxALIGN_CENTER_VERTICAL); // m_hsizer->AddSpacer(64); // m_hsizer->Add(m_cc_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index d5a679564f..364608dc10 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -110,7 +111,11 @@ protected: wxCheckBox* m_compatible_printers_checkbox; wxButton* m_compatible_printers_btn; wxButton* m_undo_btn; - wxButton* m_undo_to_sys_btn; + wxButton* m_undo_to_sys_btn; + wxButton* m_question_btn; + wxStaticText* m_undo_btns_legent; + wxRichToolTip* m_undo_btn_tip; + wxComboCtrl* m_cc_presets_choice; wxDataViewTreeCtrl* m_presetctrl; wxImageList* m_preset_icons; @@ -128,6 +133,7 @@ protected: // Bitmaps to be shown on the "Undo user changes" button next to each input field. wxBitmap m_bmp_value_revert; wxBitmap m_bmp_value_unmodified; + wxBitmap m_bmp_question; // Colors for ui "decoration" wxColour m_sys_label_clr; @@ -171,7 +177,13 @@ public: Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); get_tabs_list().push_back(this); } - ~Tab() { delete_tab_from_list(this); } + ~Tab(){ + delete_tab_from_list(this); + if (m_undo_btn_tip){ + delete m_undo_btn_tip; + m_undo_btn_tip = nullptr; + } + } wxWindow* parent() const { return m_parent; } wxString title() const { return m_title; } From bdaf1b01bedea5cac8b0d3d15dd48f87f531651c Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 19 Apr 2018 16:49:22 +0200 Subject: [PATCH 051/145] ConfigWizard: Fix reset checkbox --- xs/src/slic3r/GUI/ConfigWizard.cpp | 3 ++- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 0eee234db7..a1ee5a7485 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -205,7 +205,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent) : ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))), printer_picker(nullptr), others_buttons(new wxPanel(parent)), - cbox_reset(new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")))) + cbox_reset(nullptr) { if (wizard_p()->flag_startup && wizard_p()->flag_empty_datadir) { wxString::Format(_(L("Run %s")), ConfigWizard::name()); @@ -214,6 +214,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent) : ConfigWizard::name()) ); } else { + cbox_reset = new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)"))); append(cbox_reset); } diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index 8e43ac3ac7..cdab2eb3ca 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -107,7 +107,7 @@ struct PageWelcome: ConfigWizardPage virtual wxPanel* extra_buttons() { return others_buttons; } virtual void on_page_set(); - bool reset_user_profile() const { return cbox_reset->GetValue(); } + bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; } void on_variant_checked(); }; From d671e06c32c53a2e38d5dc34c1fdbee49ecbc1b8 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 19 Apr 2018 18:29:19 +0200 Subject: [PATCH 052/145] Fix alpha legacy dir detection, Fix SemVer value ctor --- xs/src/semver/semver.c | 2 +- xs/src/semver/semver.h | 3 +++ xs/src/slic3r/GUI/AppConfig.cpp | 8 +++++++- xs/src/slic3r/Utils/Semver.hpp | 17 +++++++++++++---- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/xs/src/semver/semver.c b/xs/src/semver/semver.c index a193544036..527738644d 100644 --- a/xs/src/semver/semver.c +++ b/xs/src/semver/semver.c @@ -620,7 +620,7 @@ semver_numeric (semver_t *x) { return num; } -static char *semver_strdup(const char *src) { +char *semver_strdup(const char *src) { if (src == NULL) return NULL; size_t len = strlen(src) + 1; char *res = malloc(len); diff --git a/xs/src/semver/semver.h b/xs/src/semver/semver.h index 7251f51e3a..01a15fc43e 100644 --- a/xs/src/semver/semver.h +++ b/xs/src/semver/semver.h @@ -98,6 +98,9 @@ semver_is_valid (const char *s); int semver_clean (char *s); +char * +semver_strdup(const char *src); + semver_t semver_copy(const semver_t *ver); diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 100b2d69b3..965e8185d4 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -100,7 +100,13 @@ void AppConfig::load() // Figure out if datadir has legacy presets auto ini_ver = Semver::parse(get("version")); - m_legacy_datadir = ini_ver ? *ini_ver < Semver(1, 40, 0) : true; + m_legacy_datadir = false; + if (ini_ver) { + // Make 1.40.0 alphas compare well + ini_ver->set_metadata(boost::none); + ini_ver->set_prerelease(boost::none); + m_legacy_datadir = ini_ver < Semver(1, 40, 0); + } // Override missing or keys with their defaults. this->set_defaults(); diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index bf3c78964c..87396d8124 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -22,14 +22,15 @@ public: Semver() : ver(semver_zero()) {} Semver(int major, int minor, int patch, - boost::optional metadata = boost::none, - boost::optional prerelease = boost::none) + boost::optional metadata = boost::none, + boost::optional prerelease = boost::none) + : ver(semver_zero()) { ver.major = major; ver.minor = minor; ver.patch = patch; - ver.metadata = metadata ? std::strcpy(ver.metadata, metadata->c_str()) : nullptr; - ver.prerelease = prerelease ? std::strcpy(ver.prerelease, prerelease->c_str()) : nullptr; + set_metadata(metadata); + set_prerelease(prerelease); } static boost::optional parse(const std::string &str) @@ -82,6 +83,13 @@ public: int patch() const { return ver.patch; } const char* prerelease() const { return ver.prerelease; } const char* metadata() const { return ver.metadata; } + + // Setters + void set_maj(int maj) { ver.major = maj; } + void set_min(int min) { ver.minor = min; } + void set_patch(int patch) { ver.patch = patch; } + void set_metadata(boost::optional meta) { meta ? strdup(*meta) : nullptr; } + void set_prerelease(boost::optional pre) { pre ? strdup(*pre) : nullptr; } // Comparison bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; } @@ -124,6 +132,7 @@ private: Semver(semver_t ver) : ver(ver) {} static semver_t semver_zero() { return { 0, 0, 0, nullptr, nullptr }; } + static char * strdup(const std::string &str) { return ::semver_strdup(const_cast(str.c_str())); } }; From 2e61420747b6e240fff989e8851c13edc6466375 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 19 Apr 2018 18:31:14 +0200 Subject: [PATCH 053/145] Sync index file --- resources/profiles/PrusaResearch.idx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index b43e266635..5dd09868fa 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,6 +1,8 @@ # This is an example configuration version index. # The index contains version numbers min_slic3r_version =1.39.0 +1.1.3 +1.1.2 1.1.1 1.1.0 0.2.0-alpha "some test comment" From ab397e5ce1d27b82006516876fa84022b4b5cd92 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 20 Apr 2018 10:26:23 +0200 Subject: [PATCH 054/145] Added SnapshotDB::snapshot_with_vendor_preset() utility function to find out whether there has ever been a snapshot taken with a given configuration version. Implemented an "on snapshot" flag, which indicates, whether the current state equals to some snapshot. If so, a new snapshot is not taken in upgrade / downgrade case. --- xs/src/slic3r/Config/Snapshot.cpp | 124 ++++++++++++++++++++- xs/src/slic3r/Config/Snapshot.hpp | 17 ++- xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp | 38 +++---- xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp | 4 +- xs/src/slic3r/GUI/GUI.cpp | 15 ++- 5 files changed, 168 insertions(+), 30 deletions(-) diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index c82169308e..18329aa5c8 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -81,6 +82,8 @@ void Snapshot::load_ini(const std::string &path) this->reason = SNAPSHOT_UPGRADE; else if (rsn == "downgrade") this->reason = SNAPSHOT_DOWNGRADE; + else if (rsn == "before_rollback") + this->reason = SNAPSHOT_BEFORE_ROLLBACK; else if (rsn == "user") this->reason = SNAPSHOT_USER; else @@ -131,6 +134,9 @@ void Snapshot::load_ini(const std::string &path) this->vendor_configs.emplace_back(std::move(vc)); } } + // Sort the vendors lexicographically. + std::sort(this->vendor_configs.begin(), this->vendor_configs.begin(), + [](const VendorConfig &cfg1, const VendorConfig &cfg2) { return cfg1.name < cfg2.name; }); } static std::string reason_string(const Snapshot::Reason reason) @@ -140,6 +146,8 @@ static std::string reason_string(const Snapshot::Reason reason) return "upgrade"; case Snapshot::SNAPSHOT_DOWNGRADE: return "downgrade"; + case Snapshot::SNAPSHOT_BEFORE_ROLLBACK: + return "before_rollback"; case Snapshot::SNAPSHOT_USER: return "user"; case Snapshot::SNAPSHOT_UNKNOWN: @@ -210,6 +218,74 @@ void Snapshot::export_vendor_configs(AppConfig &config) const config.set_vendors(std::move(vendors)); } +// Perform a deep compare of the active print / filament / printer / vendor directories. +// Return true if the content of the current print / filament / printer / vendor directories +// matches the state stored in this snapshot. +bool Snapshot::equal_to_active(const AppConfig &app_config) const +{ + // 1) Check, whether this snapshot contains the same set of active vendors, printer models and variants + // as app_config. + { + std::set matched; + for (const VendorConfig &vc : this->vendor_configs) { + auto it_vendor_models_variants = app_config.vendors().find(vc.name); + if (it_vendor_models_variants == app_config.vendors().end() || + it_vendor_models_variants->second != vc.models_variants_installed) + // There are more vendors enabled in the snapshot than currently installed. + return false; + matched.insert(vc.name); + } + for (const std::pair>> &v : app_config.vendors()) + if (matched.find(v.first) == matched.end() && ! v.second.empty()) + // There are more vendors currently installed than enabled in the snapshot. + return false; + } + + // 2) Check, whether this snapshot references the same set of ini files as the current state. + boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); + boost::filesystem::path snapshot_dir = boost::filesystem::path(Slic3r::data_dir()) / SLIC3R_SNAPSHOTS_DIR / this->id; + for (const char *subdir : { "print", "filament", "printer", "vendor" }) { + boost::filesystem::path path1 = data_dir / subdir; + boost::filesystem::path path2 = snapshot_dir / subdir; + std::vector files1, files2; + for (auto &dir_entry : boost::filesystem::directory_iterator(path1)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + files1.emplace_back(dir_entry.path().filename().string()); + for (auto &dir_entry : boost::filesystem::directory_iterator(path2)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + files2.emplace_back(dir_entry.path().filename().string()); + std::sort(files1.begin(), files1.end()); + std::sort(files2.begin(), files2.end()); + if (files1 != files2) + return false; + for (const std::string &filename : files1) { + FILE *f1 = boost::nowide::fopen((path1 / filename).string().c_str(), "rb"); + FILE *f2 = boost::nowide::fopen((path2 / filename).string().c_str(), "rb"); + bool same = true; + if (f1 && f2) { + char buf1[4096]; + char buf2[4096]; + do { + size_t r1 = fread(buf1, 1, 4096, f1); + size_t r2 = fread(buf2, 1, 4096, f2); + if (r1 != r2 || memcmp(buf1, buf2, r1)) { + same = false; + break; + } + } while (! feof(f1) || ! feof(f2)); + } else + same = false; + if (f1) + fclose(f1); + if (f2) + fclose(f2); + if (! same) + return false; + } + } + return true; +} + size_t SnapshotDB::load_db() { boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir(); @@ -347,12 +423,12 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: return m_snapshots.back(); } -void SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config) +const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config) { for (const Snapshot &snapshot : m_snapshots) if (snapshot.id == id) { this->restore_snapshot(snapshot, app_config); - return; + return snapshot; } throw std::runtime_error(std::string("Snapshot with id " + id + " was not found.")); } @@ -373,6 +449,50 @@ void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_confi snapshot.export_vendor_configs(app_config); } +bool SnapshotDB::is_on_snapshot(AppConfig &app_config) const +{ + // Is the "on_snapshot" configuration value set? + std::string on_snapshot = app_config.get("on_snapshot"); + if (on_snapshot.empty()) + // No, we are not on a snapshot. + return false; + // Is the "on_snapshot" equal to the current configuration state? + auto it_snapshot = this->snapshot(on_snapshot); + if (it_snapshot != this->end() && it_snapshot->equal_to_active(app_config)) + // Yes, we are on the snapshot. + return true; + // No, we are no more on a snapshot. Reset the state. + app_config.set("on_snapshot", ""); + return false; +} + +SnapshotDB::const_iterator SnapshotDB::snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version) +{ + auto it_found = m_snapshots.end(); + Snapshot::VendorConfig key; + key.name = vendor_name; + for (auto it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) { + const Snapshot &snapshot = *it; + auto it_vendor_config = std::lower_bound(snapshot.vendor_configs.begin(), snapshot.vendor_configs.end(), + key, [](const Snapshot::VendorConfig &cfg1, const Snapshot::VendorConfig &cfg2) { return cfg1.name < cfg2.name; }); + if (it_vendor_config != snapshot.vendor_configs.end() && it_vendor_config->name == vendor_name && + config_version == it_vendor_config->version) { + // Vendor config found with the correct version. + // Save it, but continue searching, as we want the newest snapshot. + it_found = it; + } + } + return it_found; +} + +SnapshotDB::const_iterator SnapshotDB::snapshot(const std::string &id) const +{ + for (const_iterator it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) + if (it->id == id) + return it; + return m_snapshots.end(); +} + boost::filesystem::path SnapshotDB::create_db_dir() { boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp index a7e70bb4bb..77aee3e21a 100644 --- a/xs/src/slic3r/Config/Snapshot.hpp +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -34,6 +34,7 @@ public: SNAPSHOT_UNKNOWN, SNAPSHOT_UPGRADE, SNAPSHOT_DOWNGRADE, + SNAPSHOT_BEFORE_ROLLBACK, SNAPSHOT_USER, }; @@ -47,6 +48,11 @@ public: void export_selections(AppConfig &config) const; void export_vendor_configs(AppConfig &config) const; + // Perform a deep compare of the active print / filament / printer / vendor directories. + // Return true if the content of the current print / filament / printer / vendor directories + // matches the state stored in this snapshot. + bool equal_to_active(const AppConfig &app_config) const; + // ID of a snapshot should equal to the name of the snapshot directory. // The ID contains the date/time, reason and comment to be human readable. std::string id; @@ -79,7 +85,7 @@ public: // Which printer models of this vendor were installed, and which variants of the models? std::map> models_variants_installed; }; - // List of vendor configs contained in this snapshot. + // List of vendor configs contained in this snapshot, sorted lexicographically. std::vector vendor_configs; }; @@ -100,17 +106,24 @@ public: // Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles, // create an index. const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment = ""); - void restore_snapshot(const std::string &id, AppConfig &app_config); + const Snapshot& restore_snapshot(const std::string &id, AppConfig &app_config); void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config); + // Test whether the AppConfig's on_snapshot variable points to an existing snapshot, and the existing snapshot + // matches the current state. If it does not match the current state, the AppConfig's "on_snapshot" ID is reset. + bool is_on_snapshot(AppConfig &app_config) const; + // Finds the newest snapshot, which contains a config bundle for vendor_name with config_version. + const_iterator snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version); const_iterator begin() const { return m_snapshots.begin(); } const_iterator end() const { return m_snapshots.end(); } + const_iterator snapshot(const std::string &id) const; const std::vector& snapshots() const { return m_snapshots; } private: // Create the snapshots directory if it does not exist yet. static boost::filesystem::path create_db_dir(); + // Snapshots are sorted by their date/time, oldest first. std::vector m_snapshots; }; diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 0e0df15f24..99af707e1c 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -15,6 +15,8 @@ static std::string format_reason(const Config::Snapshot::Reason reason) return std::string(_(L("Upgrade"))); case Config::Snapshot::SNAPSHOT_DOWNGRADE: return std::string(_(L("Downgrade"))); + case Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK: + return std::string(_(L("Before roll back"))); case Config::Snapshot::SNAPSHOT_USER: return std::string(_(L("User"))); case Config::Snapshot::SNAPSHOT_UNKNOWN: @@ -23,34 +25,35 @@ static std::string format_reason(const Config::Snapshot::Reason reason) } } -static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_even) +static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active) { // Start by declaring a row with an alternating background color. std::string text = "
"; // Format the row header. - text += std::string("") + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason); + text += std::string("") + (snapshot_active ? _(L("Active: ")) : "") + + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason); if (! snapshot.comment.empty()) text += " (" + snapshot.comment + ")"; text += "
"; // End of row header. // text += _(L("ID:")) + " " + snapshot.id + "
"; // text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "
"; - text += _(L("slic3r version:")) + " " + snapshot.slic3r_version_captured.to_string() + "
"; + text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "
"; // text += "reason: " + snapshot.reason + "
"; - text += "print: " + snapshot.print + "
"; - text += "filaments: " + snapshot.filaments.front() + "
"; - text += "printer: " + snapshot.printer + "
"; + text += _(L("print")) + ": " + snapshot.print + "
"; + text += _(L("filaments")) + ": " + snapshot.filaments.front() + "
"; + text += _(L("printer")) + ": " + snapshot.printer + "
"; for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) { - text += "vendor: " + vc.name + ", ver: " + vc.version.to_string() + ", min slic3r ver: " + vc.min_slic3r_version.to_string(); + text += _(L("vendor")) + ": " + vc.name + ", ver: " + vc.version.to_string() + ", min slic3r ver: " + vc.min_slic3r_version.to_string(); if (vc.max_slic3r_version != Semver::inf()) text += ", max slic3r ver: " + vc.max_slic3r_version.to_string(); text += "
"; for (const std::pair> &model : vc.models_variants_installed) { - text += "model: " + model.first + ", variants: "; + text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": "; for (const std::string &variant : model.second) { if (&variant != &*model.second.begin()) text += ", "; @@ -60,13 +63,14 @@ static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_ } } - text += "

Activate

"; + if (! snapshot_active) + text += "

" + _(L("Activate")) + "

"; text += "
"; for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) { const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1]; - text += generate_html_row(snapshot, i_row & 1); + text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot); } text += "
" @@ -85,7 +89,7 @@ static std::string generate_html_page(const Config::SnapshotDB &snapshot_db) return text; } -ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db) +ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const std::string &on_snapshot) : wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) { this->SetBackgroundColour(*wxWHITE); @@ -104,7 +108,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db #endif html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); html->SetBorders(2); - std::string text = generate_html_page(snapshot_db); + std::string text = generate_html_page(snapshot_db, on_snapshot); html->SetPage(text.c_str()); vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0); html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this); @@ -114,12 +118,6 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db this->SetEscapeId(wxID_CLOSE); this->Bind(wxEVT_BUTTON, &ConfigSnapshotDialog::onCloseDialog, this, wxID_CLOSE); vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); - -/* - this->Bind(wxEVT_LEFT_DOWN, &ConfigSnapshotDialog::onCloseDialog, this); - logo->Bind(wxEVT_LEFT_DOWN, &ConfigSnapshotDialog::onCloseDialog, this); - html->Bind(wxEVT_LEFT_DOWN, &ConfigSnapshotDialog::onCloseDialog, this); -*/ } void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event) diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp index 0d11096159..943601e731 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp @@ -17,14 +17,14 @@ namespace Config { class ConfigSnapshotDialog : public wxDialog { public: - ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db); - + ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const std::string &id); const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; } private: void onLinkClicked(wxHtmlLinkEvent &event); void onCloseDialog(wxEvent &); + // If set, it contains a snapshot ID to be restored after the dialog closes. std::string m_snapshot_to_activate; }; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 7de82cb5f4..88c3f421b2 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -397,16 +397,23 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l if (check_unsaved_changes()) { wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name"))); if (dlg.ShowModal() == wxID_OK) - Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot( - *g_AppConfig, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); + g_AppConfig->set("on_snapshot", + Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot( + *g_AppConfig, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id); } break; case ConfigMenuSnapshots: if (check_unsaved_changes()) { - ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton()); + std::string on_snapshot; + if (Config::SnapshotDB::singleton().is_on_snapshot(*g_AppConfig)) + on_snapshot = g_AppConfig->get("on_snapshot"); + ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); dlg.ShowModal(); if (! dlg.snapshot_to_activate().empty()) { - Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *g_AppConfig); + if (! Config::SnapshotDB::singleton().is_on_snapshot(*g_AppConfig)) + Config::SnapshotDB::singleton().take_snapshot(*g_AppConfig, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK); + g_AppConfig->set("on_snapshot", + Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *g_AppConfig).id); g_PresetBundle->load_presets(*g_AppConfig); // Load the currently selected preset into the GUI, update the preset selection box. for (Tab *tab : g_tabs_list) From 9b5480b7ba13ad3448fb92299da2c4ffc59333cc Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 20 Apr 2018 11:05:00 +0200 Subject: [PATCH 055/145] PresetUpdater: Use PID in cache tmp filenames --- xs/src/libslic3r/Utils.hpp | 3 +++ xs/src/libslic3r/utils.cpp | 15 +++++++++++++++ xs/src/slic3r/Utils/PresetUpdater.cpp | 7 ++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index f900137d3e..0066aa5e69 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -62,6 +62,9 @@ extern std::string timestamp_str(); // to be placed at the top of Slic3r generated files. inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); } +// getpid platform wrapper +extern unsigned get_current_pid(); + // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html template diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 733757e25a..f2415ac072 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -1,6 +1,12 @@ #include #include +#ifdef WIN32 +#include +#else +#include +#endif + #include #include #include @@ -271,4 +277,13 @@ std::string timestamp_str() return buf; } +unsigned get_current_pid() +{ +#ifdef WIN32 + return GetCurrentProcessId(); +#else + return ::getpid(); +#endif +} + }; // namespace Slic3r diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index bf9e1eb44e..9b271492c8 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -180,11 +180,12 @@ void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &target_path) const { - std::cerr << "get_file(): " << url << " -> " << target_path << std::endl; - bool res = false; fs::path tmp_path = target_path; - tmp_path += TMP_EXTENSION; + tmp_path += (boost::format(".%1%%2%") % get_current_pid() % TMP_EXTENSION).str(); + + std::cerr << "get_file(): " << url << " -> " << target_path << std::endl + << "\ttmp_path: " << tmp_path << std::endl; Http::get(url) .on_progress([this](Http::Progress, bool &cancel) { From 93a902a75771d193cc69417a133b03a58d43aafb Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 20 Apr 2018 11:06:12 +0200 Subject: [PATCH 056/145] PresetUpdater: Fix double free from Perl --- lib/Slic3r.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 19915ddd86..d249bd10b1 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -162,6 +162,7 @@ sub thread_cleanup { *Slic3r::TriangleMesh::DESTROY = sub {}; *Slic3r::GUI::AppConfig::DESTROY = sub {}; *Slic3r::GUI::PresetBundle::DESTROY = sub {}; + *Slic3r::PresetUpdater::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } From 1e1bf03f61c5fe3aab4a9396b1fd56f4a76b6bc1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 20 Apr 2018 12:58:07 +0200 Subject: [PATCH 057/145] Added reset buttons description --- resources/icons/question_mark_01.png | Bin 0 -> 523 bytes xs/src/slic3r/GUI/2DBed.hpp | 5 +++ xs/src/slic3r/GUI/BedShapeDialog.hpp | 5 +++ xs/src/slic3r/GUI/Field.cpp | 2 +- xs/src/slic3r/GUI/OptionsGroup.hpp | 5 +++ xs/src/slic3r/GUI/Preferences.hpp | 5 +++ xs/src/slic3r/GUI/Tab.cpp | 59 ++++++++++++++++----------- xs/src/slic3r/GUI/Tab.hpp | 19 +++++---- xs/src/slic3r/GUI/TabIface.hpp | 7 +++- 9 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 resources/icons/question_mark_01.png diff --git a/resources/icons/question_mark_01.png b/resources/icons/question_mark_01.png new file mode 100644 index 0000000000000000000000000000000000000000..25814a61d4e7aa408640e206b6c58b10122962d5 GIT binary patch literal 523 zcmV+m0`&cfP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0ia1lK~y+Tol(y# z1yLBilvrRR*`Z9Cd*7^3Qc6i7C589y^&>^u*|79Gmi`ADTWecUEU+ZKNNj8{b1#u! zGtM1fjqvnxYMQ#|eCIpg_pz^t7%K=esX8tBav;T?R{RYAY@o!BR$|3uQap-S0m*+t zE3X9wNBG`R5PkjZTOj$dFGag;L?sE#fKv(4c3cn}@B{o;1I5>Dio}TTS}m0&Fv@L% z_@26*-lkp8c8V5Rp_XnB%ru#6qeLr}B?zR~1cNJx3`8@wkSJz`g#dR==1&hQ)k#!> z!^y@XEU!5j;vLbTD5Yl>jn$N3i3^DSLL*!n<+()v4c+Hf#CwoPMcrXAXPc2EH}0iZ zg+v>O{`@&Rzr$je!twetlH`V+KaQ>VD|J4_`3ACGPBBUThMgPTwmv&!`%?7&diz|6 zPp8B*VxJqu*KdZMa-#A_IOP=knfluBKgBk#pLQFthk2t?{TCRCsQh3rfW_^@AjH{x zo#EX?<%eKII9TFkd;uli{2@L;RDK9?E}U={CMQU+`8X=7iTVb{J^=r2>{O*7e`o*z N002ovPDHLkV1kV- #include "Config.hpp" @@ -45,3 +48,5 @@ public: } // GUI } // Slic3r + +#endif /* slic3r_2DBed_hpp_ */ diff --git a/xs/src/slic3r/GUI/BedShapeDialog.hpp b/xs/src/slic3r/GUI/BedShapeDialog.hpp index f4614c3426..5ff4880637 100644 --- a/xs/src/slic3r/GUI/BedShapeDialog.hpp +++ b/xs/src/slic3r/GUI/BedShapeDialog.hpp @@ -1,3 +1,5 @@ +#ifndef slic3r_BedShapeDialog_hpp_ +#define slic3r_BedShapeDialog_hpp_ // The bed shape dialog. // The dialog opens from Print Settins tab->Bed Shape : Set... @@ -49,3 +51,6 @@ public: } // GUI } // Slic3r + + +#endif /* slic3r_BedShapeDialog_hpp_ */ diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index ff17a2ef8f..2527202879 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -22,7 +22,7 @@ namespace Slic3r { namespace GUI { auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); auto sz = 16; #ifdef __WXGTK__ - sz = 28 + sz = 28; #endif // __WXGTK__ m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 92ebb44881..6a00cbe0d7 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_OptionsGroup_hpp_ +#define slic3r_OptionsGroup_hpp_ + #include #include #include @@ -204,3 +207,5 @@ public: }; }} + +#endif /* slic3r_OptionsGroup_hpp_ */ diff --git a/xs/src/slic3r/GUI/Preferences.hpp b/xs/src/slic3r/GUI/Preferences.hpp index c30e94d225..f94a1db69e 100644 --- a/xs/src/slic3r/GUI/Preferences.hpp +++ b/xs/src/slic3r/GUI/Preferences.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_Preferences_hpp_ +#define slic3r_Preferences_hpp_ + #include "GUI.hpp" #include @@ -25,3 +28,5 @@ public: } // GUI } // Slic3r + +#endif /* slic3r_Preferences_hpp_ */ diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 0491965990..48521461c3 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -8,6 +8,7 @@ #include "slic3r/Utils/OctoPrint.hpp" #include "BonjourDialog.hpp" #include "WipeTowerDialog.hpp" +#include "ButtonsDescription.hpp" #include #include @@ -103,38 +104,25 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_undo_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); m_undo_to_sys_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); m_question_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - m_undo_btns_legent = new wxStaticText(panel, wxID_ANY, _(L("For more information about buttons hover the mouse cursor over them.")), wxDefaultPosition, wxDefaultSize); +// m_undo_btns_legent = new wxStaticText(panel, wxID_ANY, _(L("For more information about buttons hover the mouse cursor over them.")), wxDefaultPosition, wxDefaultSize); if (wxMSW) { m_undo_btn->SetBackgroundColour(color); m_undo_to_sys_btn->SetBackgroundColour(color); m_question_btn->SetBackgroundColour(color); } -#ifdef __WXMSW__ - m_undo_to_sys_btn->SetToolTip(_(L( "Unlocked lock icon indicates about some value changes compared with system values " + m_undo_to_sys_btn->SetToolTip(_(L( "UNLOCKED LOCK icon indicates about some value changes compared with system values " "in current option group.\n" - "Locked lock icon indicates about same values compared with system values " + "LOCKED LOCK icon indicates about same values compared with system values " "in current option group.\n" - "White bullet icon indicates about nonsystem preset.\n" - "Click the unlocked lock to revert all values in current option group to system values."))); -#else - // ToolTips to undo buttons - m_undo_btn_tip = new wxRichToolTip("Information about current button", - _(L("Unlocked lock icon indicates about some value changes compared with system values " - "in current option group.\n" - "Locked lock icon indicates about same values compared with system values " - "in current option group.\n" - "White bullet icon indicates about nonsystem preset.\n" - "Click the unlocked lock to revert all values in current option group to system values."))); - m_undo_btn_tip->SetIcon(wxICON_INFORMATION); - m_undo_btn_tip->ShowFor(m_undo_btn); -#endif //__WXMSW__ + "WHITE BULLET icon indicates about nonsystem preset.\n\n" + "Click the UNLOCKED LOCK to revert all values in current option group to system values."))); - m_undo_btn->SetToolTip(_(L( "Back arrow icon indicates about some value changes compared with last saved preset values " + m_undo_btn->SetToolTip(_(L( "BACK ARROW icon indicates about some value changes compared with last saved preset values " "in current option group.\n" - "White bullet icon indicates about same values compared with last saved preset values " - "in current option group.\n" - "Click the Back arrow to revert all values in current option group to last saved preset values."))); + "WHITE BULLET icon indicates about same values compared with last saved preset values " + "in current option group.\n\n" + "Click the BACK ARROW to revert all values in current option group to last saved preset values."))); m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information."))); @@ -147,11 +135,18 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_bmp_white_bullet .LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); m_bmp_question .LoadFile(from_u8(var("question_mark_01.png")), wxBITMAP_TYPE_PNG); + fill_icon_descriptions(); + m_undo_btn->SetBitmap(m_bmp_white_bullet); m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(); })); m_undo_to_sys_btn->SetBitmap(m_bmp_white_bullet); m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(true); })); m_question_btn->SetBitmap(m_bmp_question); + m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) + { + auto dlg = new ButtonsDescription(parent(), &m_icon_descriptions); + dlg->ShowModal(); + })); // Colors for ui "decoration" m_sys_label_clr = get_sys_label_clr(); @@ -170,9 +165,9 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer->AddSpacer(64); m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); - m_hsizer->AddSpacer(16); + m_hsizer->AddSpacer(32); m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); - m_hsizer->Add(m_undo_btns_legent, 0, wxALIGN_CENTER_VERTICAL); +// m_hsizer->Add(m_undo_btns_legent, 0, wxALIGN_CENTER_VERTICAL); // m_hsizer->AddSpacer(64); // m_hsizer->Add(m_cc_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); @@ -2354,6 +2349,22 @@ void Tab::update_tab_presets(wxComboCtrl* ui, bool show_incompatible) ui->Thaw(); } +void Tab::fill_icon_descriptions() +{ + m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_lock, L("LOCKED LOCK;" + "indicates about same values compared with system values in current option group"))); + + m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_unlock, L("UNLOCKED LOCK;" + "indicates about some value changes compared with system values in current option group"))); + + m_icon_descriptions.push_back(t_icon_description(&m_bmp_white_bullet, L("WHITE BULLET;" + "indicates about : \n - nonsystem preset (on left button)" + "\n - same values compared with last saved preset values in current option group(on right button)"))); + + m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_revert, L("BACK ARROW;" + "indicates about some value changes compared with last saved preset values in current option group"))); +} + void Page::reload_config() { for (auto group : m_optgroups) diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 364608dc10..ba449c8c7e 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_Tab_hpp_ +#define slic3r_Tab_hpp_ + // The "Expert" tab at the right of the main tabbed window. // // This file implements following packages: @@ -22,7 +25,6 @@ #include #include #include -#include #include #include @@ -35,6 +37,9 @@ namespace Slic3r { namespace GUI { +typedef std::pair t_icon_description; +typedef std::vector> t_icon_descriptions; + // Single Tab page containing a{ vsizer } of{ optgroups } // package Slic3r::GUI::Tab::Page; using ConfigOptionsGroupShp = std::shared_ptr; @@ -113,8 +118,7 @@ protected: wxButton* m_undo_btn; wxButton* m_undo_to_sys_btn; wxButton* m_question_btn; - wxStaticText* m_undo_btns_legent; - wxRichToolTip* m_undo_btn_tip; +// wxStaticText* m_undo_btns_legent; wxComboCtrl* m_cc_presets_choice; wxDataViewTreeCtrl* m_presetctrl; @@ -152,6 +156,8 @@ protected: std::map m_options_list; int m_opt_status_value; + t_icon_descriptions m_icon_descriptions = {}; + // The two following two event IDs are generated at Plater.pm by calling Wx::NewEventType. wxEventType m_event_value_change = 0; wxEventType m_event_presets_changed = 0; @@ -179,10 +185,6 @@ public: } ~Tab(){ delete_tab_from_list(this); - if (m_undo_btn_tip){ - delete m_undo_btn_tip; - m_undo_btn_tip = nullptr; - } } wxWindow* parent() const { return m_parent; } @@ -249,6 +251,7 @@ protected: void update_frequently_changed_parameters(); void update_wiping_button_visibility(); void update_tab_presets(wxComboCtrl* ui, bool show_incompatible); + void fill_icon_descriptions(); }; //Slic3r::GUI::Tab::Print; @@ -327,3 +330,5 @@ public: } // GUI } // Slic3r + +#endif /* slic3r_Tab_hpp_ */ diff --git a/xs/src/slic3r/GUI/TabIface.hpp b/xs/src/slic3r/GUI/TabIface.hpp index 0325c855c7..5622634e8f 100644 --- a/xs/src/slic3r/GUI/TabIface.hpp +++ b/xs/src/slic3r/GUI/TabIface.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_TabIface_hpp_ +#define slic3r_TabIface_hpp_ + #include #include @@ -30,6 +33,8 @@ public: protected: GUI::Tab *m_tab; -}; +}; // namespace GUI }; // namespace Slic3r + +#endif /* slic3r_TabIface_hpp_ */ From 669b0b68ab112972d711e0b01efc9588925ece2a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 20 Apr 2018 13:27:25 +0200 Subject: [PATCH 058/145] Added missed files to commit --- xs/src/slic3r/GUI/ButtonsDescription.cpp | 42 ++++++++++++++++++++++++ xs/src/slic3r/GUI/ButtonsDescription.hpp | 27 +++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 xs/src/slic3r/GUI/ButtonsDescription.cpp create mode 100644 xs/src/slic3r/GUI/ButtonsDescription.hpp diff --git a/xs/src/slic3r/GUI/ButtonsDescription.cpp b/xs/src/slic3r/GUI/ButtonsDescription.cpp new file mode 100644 index 0000000000..6831d0dddb --- /dev/null +++ b/xs/src/slic3r/GUI/ButtonsDescription.cpp @@ -0,0 +1,42 @@ +#include "ButtonsDescription.hpp" +#include +#include +#include + +#include "GUI.hpp" + +namespace Slic3r { +namespace GUI { + +ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions) : + wxDialog(parent, wxID_ANY, "Buttons Description", wxDefaultPosition, wxDefaultSize), + m_icon_descriptions(icon_descriptions) +{ + auto grid_sizer = new wxFlexGridSizer(3, 20, 20); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(grid_sizer, 0, wxEXPAND | wxALL, 20); + + for (auto pair : *m_icon_descriptions) + { + auto icon = new wxStaticBitmap(this, wxID_ANY, *pair.first); + grid_sizer->Add(icon, -1, wxALIGN_CENTRE_VERTICAL); + + std::istringstream f(pair.second); + std::string s; + while (getline(f, s, ';')) { + auto description = new wxStaticText(this, wxID_ANY, _(s)); + grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL); + } + } + + auto button = CreateStdDialogButtonSizer(wxOK); + main_sizer->Add(button, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); +} + +} // GUI +} // Slic3r + diff --git a/xs/src/slic3r/GUI/ButtonsDescription.hpp b/xs/src/slic3r/GUI/ButtonsDescription.hpp new file mode 100644 index 0000000000..4858eaaea3 --- /dev/null +++ b/xs/src/slic3r/GUI/ButtonsDescription.hpp @@ -0,0 +1,27 @@ +#ifndef slic3r_ButtonsDescription_hpp +#define slic3r_ButtonsDescription_hpp + +#include +#include + +namespace Slic3r { +namespace GUI { + +using t_icon_descriptions = std::vector>; + +class ButtonsDescription : public wxDialog +{ + t_icon_descriptions* m_icon_descriptions; +public: + ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions); + ~ButtonsDescription(){} + + +}; + +} // GUI +} // Slic3r + + +#endif + From e0421a3ba6ca566dab584da32ecd32329ae45b1e Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 20 Apr 2018 14:53:11 +0200 Subject: [PATCH 059/145] PresetUpdater: Don't display new Slic3r version notifications multiple times for the same version --- xs/src/slic3r/Utils/PresetUpdater.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 9b271492c8..473fcf84f7 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -411,18 +411,24 @@ void PresetUpdater::slic3r_update_notify() auto* app_config = GUI::get_app_config(); const auto ver_slic3r = Semver::parse(SLIC3R_VERSION); - const auto ver_online = Semver::parse(app_config->get("version_online")); + const auto ver_online_str = app_config->get("version_online"); + const auto ver_online = Semver::parse(ver_online_str); + const auto ver_online_seen = Semver::parse(app_config->get("version_online_seen")); if (! ver_slic3r) { throw std::runtime_error("Could not parse Slic3r version string: " SLIC3R_VERSION); } - if (ver_online && *ver_online > *ver_slic3r) { - UpdateNotification notification(*ver_slic3r, *ver_online); - notification.ShowModal(); - if (notification.disable_version_check()) { - app_config->set("version_check", "0"); - p->enabled_version_check = false; + if (ver_online) { + // Only display the notification if the version available online is newer AND if we haven't seen it before + if (*ver_online > *ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) { + UpdateNotification notification(*ver_slic3r, *ver_online); + notification.ShowModal(); + if (notification.disable_version_check()) { + app_config->set("version_check", "0"); + p->enabled_version_check = false; + } } + app_config->set("version_online_seen", ver_online_str); } } From 48bbd2d22adc14a2b373804129f78a5a58712dd0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 20 Apr 2018 15:02:54 +0200 Subject: [PATCH 060/145] Added new files to MackList --- xs/CMakeLists.txt | 2 ++ xs/src/slic3r/GUI/Tab.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index abd9c3617a..c20a1102a3 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -208,6 +208,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/RammingChart.hpp ${LIBDIR}/slic3r/GUI/BonjourDialog.cpp ${LIBDIR}/slic3r/GUI/BonjourDialog.hpp + ${LIBDIR}/slic3r/GUI/ButtonsDescription.cpp + ${LIBDIR}/slic3r/GUI/ButtonsDescription.hpp ${LIBDIR}/slic3r/Config/Snapshot.cpp ${LIBDIR}/slic3r/Config/Snapshot.hpp ${LIBDIR}/slic3r/Config/Version.cpp diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 48521461c3..e2b07a5569 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -144,7 +144,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_question_btn->SetBitmap(m_bmp_question); m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { - auto dlg = new ButtonsDescription(parent(), &m_icon_descriptions); + auto dlg = new ButtonsDescription(this, &m_icon_descriptions); dlg->ShowModal(); })); From 27f77c7680a23e6449d47b9cdb30c5c10c2e3bc2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 20 Apr 2018 15:40:43 +0200 Subject: [PATCH 061/145] Added little more information to ButtonsDescription dialog --- xs/src/slic3r/GUI/Tab.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index e2b07a5569..2b82694dbc 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -2355,14 +2355,16 @@ void Tab::fill_icon_descriptions() "indicates about same values compared with system values in current option group"))); m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_unlock, L("UNLOCKED LOCK;" - "indicates about some value changes compared with system values in current option group"))); + "indicates about some value changes compared with system values in current option group.\n" + "Click the UNLOCKED LOCK to revert all values in current option group to system values."))); m_icon_descriptions.push_back(t_icon_description(&m_bmp_white_bullet, L("WHITE BULLET;" "indicates about : \n - nonsystem preset (on left button)" "\n - same values compared with last saved preset values in current option group(on right button)"))); m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_revert, L("BACK ARROW;" - "indicates about some value changes compared with last saved preset values in current option group"))); + "indicates about some value changes compared with last saved preset values in current option group.\n" + "Click the BACK ARROW to revert all values in current option group to last saved preset values."))); } void Page::reload_config() From b8cb9369732757f427c264181d49e50a0f943237 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 20 Apr 2018 17:32:08 +0200 Subject: [PATCH 062/145] Added effective update of ComboBoxes on Plater --- lib/Slic3r/GUI/MainFrame.pm | 1 + lib/Slic3r/GUI/Plater.pm | 25 +++++++++++++++++++++++-- xs/src/slic3r/GUI/PresetBundle.cpp | 4 ++++ xs/src/slic3r/GUI/Tab.hpp | 7 +++---- xs/src/slic3r/GUI/TabIface.cpp | 1 + xs/src/slic3r/GUI/TabIface.hpp | 1 + xs/xsp/GUI_Tab.xsp | 1 + 7 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index b2f51b9e1d..f5d4660182 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -149,6 +149,7 @@ sub _init_tabpanel { if (defined $presets){ my $reload_dependent_tabs = $tab->get_dependent_tabs; $self->{plater}->update_presets($tab_name, $reload_dependent_tabs, $presets); + $self->{plater}->{"selected_item_$tab_name"} = $tab->get_selected_preset_item; if ($tab_name eq 'printer') { # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for my $tab_name_other (qw(print filament)) { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d59865491c..023ec4ae5a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -514,6 +514,13 @@ sub new { $self->SetSizer($sizer); } + # Last correct selected item for each preset + { + $self->{selected_item_print} = 0; + $self->{selected_item_filament} = 0; + $self->{selected_item_printer} = 0; + } + $self->update_ui_from_settings(); return $self; @@ -538,9 +545,23 @@ sub _on_select_preset { # Only update the platter UI for the 2nd and other filaments. wxTheApp->{preset_bundle}->update_platter_filament_ui($idx, $choice); } else { + my $selected_item = $choice->GetSelection(); + print ("selected_item = $selected_item\n"); + print ("selected_item_$group = ". $self->{"selected_item_$group"}. "\n"); + return if ($selected_item == $self->{"selected_item_$group"}); + + my $selected_string = $choice->GetString($selected_item); + if ($selected_string eq "------- System presets -------" || + $selected_string eq "------- User presets -------"){ + $choice->SetSelection($self->{"selected_item_$group"}); + return; + } + # call GetSelection() in scalar context as it's context-aware - $self->{on_select_preset}->($group, $choice->GetStringSelection) - if $self->{on_select_preset}; +# $self->{on_select_preset}->($group, $choice->GetStringSelection) + $self->{on_select_preset}->($group, $selected_string) + if $self->{on_select_preset}; + $self->{"selected_item_$group"} = $selected_item; } # Synchronize config.ini with the current selections. wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index d6cde84a89..92525fcbf9 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -977,6 +977,10 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) { + if (name == "------- System presets -------" || + name == "------- User presets -------") + return; + if (idx >= filament_presets.size()) filament_presets.resize(idx + 1, filaments.default_preset().name); filament_presets[idx] = Preset::remove_suffix_modified(name); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index ba449c8c7e..eda6727a85 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -237,12 +237,11 @@ public: bool set_value(const t_config_option_key& opt_key, const boost::any& value); wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText); bool current_preset_is_dirty(); + DynamicPrintConfig* get_config() { return m_config; } - PresetCollection* get_presets() - { - return m_presets; - } + PresetCollection* get_presets() { return m_presets; } std::vector get_dependent_tabs() { return m_reload_dependent_tabs; } + size_t get_selected_preset_item() { return m_selected_preset_item; } void on_value_change(const std::string& opt_key, const boost::any& value); diff --git a/xs/src/slic3r/GUI/TabIface.cpp b/xs/src/slic3r/GUI/TabIface.cpp index 4df0e72e53..29833322b8 100644 --- a/xs/src/slic3r/GUI/TabIface.cpp +++ b/xs/src/slic3r/GUI/TabIface.cpp @@ -11,6 +11,7 @@ void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*con void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast(value)); } bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();} void TabIface::OnActivate() { return m_tab->OnActivate();} +size_t TabIface::get_selected_preset_item() { return m_tab->get_selected_preset_item(); } std::string TabIface::title() { return m_tab->title().ToUTF8().data(); } DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); } PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; } diff --git a/xs/src/slic3r/GUI/TabIface.hpp b/xs/src/slic3r/GUI/TabIface.hpp index 5622634e8f..2f7f4e8e7f 100644 --- a/xs/src/slic3r/GUI/TabIface.hpp +++ b/xs/src/slic3r/GUI/TabIface.hpp @@ -30,6 +30,7 @@ public: DynamicPrintConfig* get_config(); PresetCollection* get_presets(); std::vector get_dependent_tabs(); + size_t get_selected_preset_item(); protected: GUI::Tab *m_tab; diff --git a/xs/xsp/GUI_Tab.xsp b/xs/xsp/GUI_Tab.xsp index 9cacac74f0..bcbdc0d9f8 100644 --- a/xs/xsp/GUI_Tab.xsp +++ b/xs/xsp/GUI_Tab.xsp @@ -16,6 +16,7 @@ bool current_preset_is_dirty(); void load_key_value(char* opt_key, char* value); void OnActivate(); + size_t get_selected_preset_item(); std::string title(); Ref get_config(); Ref get_presets(); From f8b1dc550685a8ca76c4d7a810151eabf08db2c1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Apr 2018 08:44:24 +0200 Subject: [PATCH 063/145] Tweaks to zooming logic --- lib/Slic3r/GUI/3DScene.pm | 124 ++++++++++++++++++++++++----- lib/Slic3r/GUI/Plater.pm | 1 + lib/Slic3r/GUI/Plater/3DPreview.pm | 21 +---- 3 files changed, 105 insertions(+), 41 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 65abf850ec..ff6d733997 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -201,6 +201,10 @@ sub new { $self->select_view('left'); } elsif ($key == ord('6')) { $self->select_view('right'); + } elsif ($key == ord('z')) { + $self->zoom_to_volumes; + } elsif ($key == ord('b')) { + $self->zoom_to_bed; } else { $event->Skip; } @@ -599,22 +603,23 @@ sub mouse_wheel_event { $zoom = $zoom_min if defined $zoom_min && $zoom < $zoom_min; $self->_zoom($zoom); - # In order to zoom around the mouse point we need to translate - # the camera target - my $size = Slic3r::Pointf->new($self->GetSizeWH); - my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- - $self->_camera_target->translate( - # ($pos - $size/2) represents the vector from the viewport center - # to the mouse point. By multiplying it by $zoom we get the new, - # transformed, length of such vector. - # Since we want that point to stay fixed, we move our camera target - # in the opposite direction by the delta of the length of such vector - # ($zoom - 1). We then scale everything by 1/$self->_zoom since - # $self->_camera_target is expressed in terms of model units. - -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, - -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, - 0, - ) if 0; +# # In order to zoom around the mouse point we need to translate +# # the camera target +# my $size = Slic3r::Pointf->new($self->GetSizeWH); +# my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- +# $self->_camera_target->translate( +# # ($pos - $size/2) represents the vector from the viewport center +# # to the mouse point. By multiplying it by $zoom we get the new, +# # transformed, length of such vector. +# # Since we want that point to stay fixed, we move our camera target +# # in the opposite direction by the delta of the length of such vector +# # ($zoom - 1). We then scale everything by 1/$self->_zoom since +# # $self->_camera_target is expressed in terms of model units. +# -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, +# -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, +# 0, +# ) if 0; + $self->on_viewport_changed->() if $self->on_viewport_changed; $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen; $self->Refresh; @@ -683,9 +688,82 @@ sub select_view { sub get_zoom_to_bounding_box_factor { my ($self, $bb) = @_; - return undef if ($bb->empty); - my $max_size = max(@{$bb->size}) * 2; - return ($max_size == 0) ? undef : min($self->GetSizeWH) / $max_size; + my $max_bb_size = max(@{ $bb->size }); + return undef if ($max_bb_size == 0); + + # project the bbox vertices on a plane perpendicular to the camera forward axis + # then calculates the vertices coordinate on this plane along the camera xy axes + + # we need the view matrix, we let opengl calculate it (same as done in render sub) + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + if (!TURNTABLE_MODE) { + # Shift the perspective camera. + my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); + glTranslatef(@$camera_pos); + } + + if (TURNTABLE_MODE) { + # Turntable mode is enabled by default. + glRotatef(-$self->_stheta, 1, 0, 0); # pitch + glRotatef($self->_sphi, 0, 0, 1); # yaw + } else { + # Shift the perspective camera. + my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); + glTranslatef(@$camera_pos); + my @rotmat = quat_to_rotmatrix($self->quat); + glMultMatrixd_p(@rotmat[0..15]); + } + glTranslatef(@{ $self->_camera_target->negative }); + + # get the view matrix back from opengl + my @matrix = glGetFloatv_p(GL_MODELVIEW_MATRIX); + + # camera axes + my $right = Slic3r::Pointf3->new($matrix[0], $matrix[4], $matrix[8]); + my $up = Slic3r::Pointf3->new($matrix[1], $matrix[5], $matrix[9]); + my $forward = Slic3r::Pointf3->new($matrix[2], $matrix[6], $matrix[10]); + + my $bb_min = $bb->min_point(); + my $bb_max = $bb->max_point(); + my $bb_center = $bb->center(); + + # bbox vertices in world space + my @vertices = (); + push(@vertices, $bb_min); + push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_min->z())); + push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_max->y(), $bb_min->z())); + push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_min->z())); + push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_min->y(), $bb_max->z())); + push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_max->z())); + push(@vertices, $bb_max); + push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_max->z())); + + my $max_x = 0.0; + my $max_y = 0.0; + + # margin factor to give some empty space around the bbox + my $margin_factor = 1.25; + + foreach my $v (@vertices) { + # project vertex on the plane perpendicular to camera forward axis + my $pos = Slic3r::Pointf3->new($v->x() - $bb_center->x(), $v->y() - $bb_center->y(), $v->z() - $bb_center->z()); + my $proj_on_normal = $pos->x() * $forward->x() + $pos->y() * $forward->y() + $pos->z() * $forward->z(); + my $proj_on_plane = Slic3r::Pointf3->new($pos->x() - $proj_on_normal * $forward->x(), $pos->y() - $proj_on_normal * $forward->y(), $pos->z() - $proj_on_normal * $forward->z()); + + # calculates vertex coordinate along camera xy axes + my $x_on_plane = $proj_on_plane->x() * $right->x() + $proj_on_plane->y() * $right->y() + $proj_on_plane->z() * $right->z(); + my $y_on_plane = $proj_on_plane->x() * $up->x() + $proj_on_plane->y() * $up->y() + $proj_on_plane->z() * $up->z(); + + $max_x = max($max_x, $margin_factor * 2 * abs($x_on_plane)); + $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane)); + } + + my ($cw, $ch) = $self->GetSizeWH; + my $min_ratio = min($cw / $max_x, $ch / $max_y); + + return $min_ratio; } sub zoom_to_bounding_box { @@ -697,6 +775,8 @@ sub zoom_to_bounding_box { # center view around bounding box center $self->_camera_target($bb->center); $self->on_viewport_changed->() if $self->on_viewport_changed; + $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen; + $self->Refresh; } } @@ -1031,8 +1111,8 @@ sub Resize { #FIXME setting the size of the box 10x larger than necessary # is only a workaround for an incorrectly set camera. # This workaround harms Z-buffer accuracy! -# my $depth = 1.05 * $self->max_bounding_box->radius(); - my $depth = 10.0 * $self->max_bounding_box->radius(); +# my $depth = 1.05 * $self->max_bounding_box->radius(); + my $depth = max(@{ $self->max_bounding_box->size }); glOrtho( -$x/2, $x/2, -$y/2, $y/2, -$depth, $depth, @@ -1162,7 +1242,7 @@ sub Render { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - { + if (!TURNTABLE_MODE) { # Shift the perspective camera. my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); glTranslatef(@$camera_pos); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d59865491c..5a29ab394e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1856,6 +1856,7 @@ sub object_cut_dialog { $self->remove($obj_idx); $self->load_model_objects(grep defined($_), @new_objects); $self->arrange; + $self->{canvas3D}->zoom_to_volumes if $self->{canvas3D}; } } diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm index 537cb0c8f2..bd49bedb6d 100644 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ b/lib/Slic3r/GUI/Plater/3DPreview.pm @@ -10,7 +10,7 @@ use base qw(Wx::Panel Class::Accessor); use Wx::Locale gettext => 'L'; -__PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer auto_zoom)); +__PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer)); sub new { my $class = shift; @@ -21,7 +21,6 @@ sub new { $self->{number_extruders} = 1; # Show by feature type by default. $self->{preferred_color_mode} = 'feature'; - $self->auto_zoom(1); # init GUI elements my $canvas = Slic3r::GUI::3DScene->new($self); @@ -207,41 +206,29 @@ sub new { my $selection = $choice_view_type->GetCurrentSelection(); $self->{preferred_color_mode} = ($selection == 4) ? 'tool' : 'feature'; $self->gcode_preview_data->set_type($selection); - $self->auto_zoom(0); $self->reload_print; - $self->auto_zoom(1); }); EVT_CHECKLISTBOX($self, $combochecklist_features, sub { my $flags = Slic3r::GUI::combochecklist_get_flags($combochecklist_features); $self->gcode_preview_data->set_extrusion_flags($flags); - $self->auto_zoom(0); $self->refresh_print; - $self->auto_zoom(1); }); EVT_CHECKBOX($self, $checkbox_travel, sub { $self->gcode_preview_data->set_travel_visible($checkbox_travel->IsChecked()); - $self->auto_zoom(0); $self->refresh_print; - $self->auto_zoom(1); }); EVT_CHECKBOX($self, $checkbox_retractions, sub { $self->gcode_preview_data->set_retractions_visible($checkbox_retractions->IsChecked()); - $self->auto_zoom(0); $self->refresh_print; - $self->auto_zoom(1); }); EVT_CHECKBOX($self, $checkbox_unretractions, sub { $self->gcode_preview_data->set_unretractions_visible($checkbox_unretractions->IsChecked()); - $self->auto_zoom(0); $self->refresh_print; - $self->auto_zoom(1); }); EVT_CHECKBOX($self, $checkbox_shells, sub { $self->gcode_preview_data->set_shells_visible($checkbox_shells->IsChecked()); - $self->auto_zoom(0); $self->refresh_print; - $self->auto_zoom(1); }); $self->SetSizer($main_sizer); @@ -374,7 +361,7 @@ sub load_print { } $self->show_hide_ui_elements('simple'); } else { - $self->{force_sliders_full_range} = (scalar(@{$self->canvas->volumes}) == 0) && $self->auto_zoom; + $self->{force_sliders_full_range} = (scalar(@{$self->canvas->volumes}) == 0); $self->canvas->load_gcode_preview($self->print, $self->gcode_preview_data, \@colors); $self->show_hide_ui_elements('full'); @@ -384,10 +371,6 @@ sub load_print { } $self->update_sliders($n_layers); - - if ($self->auto_zoom) { - $self->canvas->zoom_to_volumes; - } $self->_loaded(1); } } From 9a1dbfa6cf4fbef9c9ad92972f23aa246c9d6701 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Apr 2018 10:27:42 +0200 Subject: [PATCH 064/145] Updated action_undo.png icon and added sys_unlock_grey.png & action_undo_grey.png for dark themes of OS --- lib/Slic3r/GUI/Plater.pm | 2 -- resources/icons/action_undo.png | Bin 491 -> 510 bytes resources/icons/action_undo_grey.png | Bin 0 -> 480 bytes resources/icons/sys_unlock_grey.png | Bin 0 -> 423 bytes xs/src/slic3r/GUI/Tab.cpp | 8 +++++--- 5 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 resources/icons/action_undo_grey.png create mode 100644 resources/icons/sys_unlock_grey.png diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 023ec4ae5a..893cc366bb 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -546,8 +546,6 @@ sub _on_select_preset { wxTheApp->{preset_bundle}->update_platter_filament_ui($idx, $choice); } else { my $selected_item = $choice->GetSelection(); - print ("selected_item = $selected_item\n"); - print ("selected_item_$group = ". $self->{"selected_item_$group"}. "\n"); return if ($selected_item == $self->{"selected_item_$group"}); my $selected_string = $choice->GetString($selected_item); diff --git a/resources/icons/action_undo.png b/resources/icons/action_undo.png index 877f159868640a0399143631438313a31dacf1a1..06bb98b22a6d98e035059205bc4cc35f5164443f 100644 GIT binary patch delta 434 zcmV;j0Zsnv1O5Y$R)3R8L_t(IPnDCqO9Md=#$)0mf`WyextrNXE|AhR0g-^EwdoXW zt!!-dK(Me7dmH}=D?7Edw6IVD#zH|%p&-5zzgcr3xyvCA40}8G&2MM+<}zUzX6yBO zF7BBW>(@xG3)E`0(YTA)klL4Z*n4qzP#jRHRK_Gp`|8ZFYJdC}kSC%UtZIA-t9V$Y+zjoifGbbX@YMRcbW>K%YmhN6?)*X4=*|x7LP<} c9)@A|2OVI(RMldGuK)l507*qoM6N<$f{oS4Pyhe` delta 415 zcmV;Q0bu_A1M35jR)2s=L_t(IPqmRtO9D|8$4AW>N5}VDvzJgotHwuA(wC7a+NVXi zsW#HCT1k(!Eey1!jV3^}LQ6#~{1jtpi0*Ctt@Vh;5+Z_{ce|vqy3h+7u`u)Ckgl3}w1DTmE zN|wh&o{B|SNcFHkfZgq#i3J$PnGDRlfJiXJ{OU>+9&CdRbMjnH~V7dV1DxW9! z*0E9O)@j>xb}B|%|2cV-$NLB6B%4((&o7ksdVT6*{}>3zeF4rRxG}NPf+PR{002ov JPDHLkV1jTT#yPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0d+}4K~y+TwNgDx z!%!5BScQVKo0EgiIye@=(M2(vu37s71Ura>Lvi;vI6664)NYm1Av9?|nl@i3hz<%; zZ5z)C7c32>3VPrY?z`und)|Ff{teY?b-C4Qol5#UG#ZVKPN#Dd1c4_U!WYmu!+7H9G2n-&QaZxM%Bnp!HA<~2>5lTu3Nm(93dADn}X0H2K(LAWTF%Xtz;gB%KA$fspl z`xyb)J*NPdR;$(4#+xzq7~%tnJTuP-KshfHh9F)iNiriP-hUkqpEv+}x|HKA@Pymg ze2*fW1UEXqF3-fBBh<-&)lD>o5E-zE!T$hPyo0N6%hP~G75P)a@`+&>dvg5OP?RtC WJ3qPv{k%&60000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0X#`WK~y+TwNpJy z!%!5B;2?-Nb$0Rx2(FU9;nb-`aC8x;ppMxb#8q^V3>`DN1>BlIV!mU(J1H$X6l&CS z;T5C48iIJ>@E-4;d+yD>MEDmtj}M z?!a3ZjEbU|`M!V6WPIqlzJPcEGf|sQv_w zn+WaCZD3V>1S^`soV0(4hZyW@{T*)umiCD_j=vZnX=dB@CdIJY%hm?Czqzh^LNS<) z8x2sU_pO7J_i3X6ewJMXB9I36E3xVemiAW&!Uy&+mn{OD R)@T3#002ovPDHLkV1i$rw$T6p literal 0 HcmV?d00001 diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 2b82694dbc..e157673297 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -126,13 +126,15 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information."))); + // Determine the theme color of OS (dark or light) + auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. m_bmp_value_lock .LoadFile(from_u8(var("sys_lock.png")), wxBITMAP_TYPE_PNG); - m_bmp_value_unlock .LoadFile(from_u8(var("sys_unlock.png")), wxBITMAP_TYPE_PNG); + m_bmp_value_unlock .LoadFile(from_u8(var(luma >= 128 ? "sys_unlock.png" : "sys_unlock_grey.png")), wxBITMAP_TYPE_PNG); m_bmp_non_system = &m_bmp_white_bullet; // Bitmaps to be shown on the "Undo user changes" button next to each input field. - m_bmp_value_revert .LoadFile(from_u8(var("action_undo.png")), wxBITMAP_TYPE_PNG); - m_bmp_white_bullet .LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); + m_bmp_value_revert .LoadFile(from_u8(var(luma >= 128 ? "action_undo.png" : "action_undo_grey.png")), wxBITMAP_TYPE_PNG); + m_bmp_white_bullet .LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); m_bmp_question .LoadFile(from_u8(var("question_mark_01.png")), wxBITMAP_TYPE_PNG); fill_icon_descriptions(); From 33c0d1dca392db9ae852123834938b700104c2a1 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 23 Apr 2018 11:16:47 +0200 Subject: [PATCH 065/145] PresetUpdater: Add/fix logging, comments --- xs/src/slic3r/Utils/PresetUpdater.cpp | 115 +++++++++++++++++++++----- xs/src/slic3r/Utils/PresetUpdater.hpp | 9 +- 2 files changed, 102 insertions(+), 22 deletions(-) diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 473fcf84f7..a64d05eca3 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -1,9 +1,9 @@ #include "PresetUpdater.hpp" -#include // XXX #include #include #include +#include #include #include #include @@ -48,6 +48,7 @@ static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; +// A confirmation dialog listing configuration updates struct UpdateNotification : wxDialog { // If this dialog gets any more complex, it should probably be factored out... @@ -123,6 +124,11 @@ struct Update {} std::string name() { return source.stem().string(); } + + friend std::ostream& operator<<(std::ostream& os , const Update &update) { + os << "Update(" << update.source.string() << " -> " << update.target.string() << ')'; + return os; + } }; typedef std::vector Updates; @@ -171,6 +177,7 @@ PresetUpdater::priv::priv(int version_online_event) : index_db = std::move(Index::load_db()); } +// Pull relevant preferences from AppConfig void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) { enabled_version_check = app_config->get("version_check") == "1"; @@ -178,19 +185,29 @@ void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) enabled_config_update = app_config->get("preset_update") == "1"; } +// Downloads a file (http get operation). Cancels if the Updater is being destroyed. bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &target_path) const { bool res = false; fs::path tmp_path = target_path; tmp_path += (boost::format(".%1%%2%") % get_current_pid() % TMP_EXTENSION).str(); - std::cerr << "get_file(): " << url << " -> " << target_path << std::endl - << "\ttmp_path: " << tmp_path << std::endl; + BOOST_LOG_TRIVIAL(info) << boost::format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`") + % url + % target_path.string() + % tmp_path.string(); Http::get(url) .on_progress([this](Http::Progress, bool &cancel) { if (cancel) { cancel = true; } }) + .on_error([&](std::string body, std::string error, unsigned http_status) { + (void)body; + BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%") + % url + % http_status + % body; + }) .on_complete([&](std::string body, unsigned http_status) { fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); file.write(body.c_str(), body.size()); @@ -203,26 +220,39 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe return res; } +// Remove leftover paritally downloaded files, if any. void PresetUpdater::priv::prune_tmps() const { for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) { if (it->path().extension() == TMP_EXTENSION) { + BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << it->path().string(); fs::remove(it->path()); } } } +// Get Slic3rPE version available online, save in AppConfig. void PresetUpdater::priv::sync_version() const { if (! enabled_version_check) { return; } + BOOST_LOG_TRIVIAL(info) << boost::format("Downloading Slic3rPE online version from: `%1%`") % version_check_url; + Http::get(version_check_url) .size_limit(SLIC3R_VERSION_BODY_MAX) .on_progress([this](Http::Progress, bool &cancel) { cancel = this->cancel; }) + .on_error([&](std::string body, std::string error, unsigned http_status) { + (void)body; + BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%") + % version_check_url + % http_status + % body; + }) .on_complete([&](std::string body, unsigned http_status) { boost::trim(body); + BOOST_LOG_TRIVIAL(info) << boost::format("Got Slic3rPE online version: `%1%`. Sending to GUI thread...") % body; wxCommandEvent* evt = new wxCommandEvent(version_online_event); evt->SetString(body); GUI::get_app()->QueueEvent(evt); @@ -230,9 +260,11 @@ void PresetUpdater::priv::sync_version() const .perform_sync(); } +// 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 std::set vendors) const { - std::cerr << "sync_config()" << std::endl; + BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; if (!enabled_config_update) { return; } @@ -240,54 +272,65 @@ void PresetUpdater::priv::sync_config(const std::set vendors) con for (const auto &index : index_db) { if (cancel) { return; } - std::cerr << "Index: " << index.vendor() << std::endl; - const auto vendor_it = vendors.find(VendorProfile(index.vendor())); - if (vendor_it == vendors.end()) { continue; } + if (vendor_it == vendors.end()) { + BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor(); + continue; + } const VendorProfile &vendor = *vendor_it; - if (vendor.config_update_url.empty()) { continue; } + 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 auto idx_path = cache_path / (vendor.id + ".idx"); if (! get_file(idx_url, idx_path)) { continue; } if (cancel) { return; } - std::cerr << "Got a new index: " << idx_path << std::endl; - // Load the fresh index up Index new_index; new_index.load(idx_path); // See if a there's a new version to download const auto recommended_it = new_index.recommended(); - if (recommended_it == new_index.end()) { continue; } + if (recommended_it == new_index.end()) { + BOOST_LOG_TRIVIAL(error) << "No recommended version for vendor: " << vendor.name << ", invalid index?"; + continue; + } const auto recommended = recommended_it->config_version; - std::cerr << "Current vendor version: " << vendor.config_version.to_string() << std::endl; - std::cerr << "Recommended version:\t" << recommended.to_string() << std::endl; + BOOST_LOG_TRIVIAL(debug) << boost::format("New index for vendor: %1%: current version: %2%, recommended version: %3%") + % vendor.name + % vendor.config_version.to_string() + % recommended.to_string(); if (vendor.config_version >= recommended) { continue; } // Download a fresh bundle + BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name; const auto bundle_url = (boost::format("%1%/%2%.ini") % vendor.config_update_url % recommended.to_string()).str(); const auto bundle_path = cache_path / (vendor.id + ".ini"); if (! get_file(bundle_url, bundle_path)) { continue; } if (cancel) { return; } - - std::cerr << "Got a new bundle: " << bundle_path << std::endl; } } +// Install indicies from resources. Only installs those that are either missing or older than in resources. void PresetUpdater::priv::check_install_indices() const { + BOOST_LOG_TRIVIAL(info) << "Checking if indices need to be installed from resources..."; + for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) { const auto &path = it->path(); if (path.extension() == ".idx") { const auto path_in_cache = cache_path / path.filename(); if (! fs::exists(path_in_cache)) { + BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename(); fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists); } else { Index idx_rsrc, idx_cache; @@ -295,6 +338,7 @@ void PresetUpdater::priv::check_install_indices() const idx_cache.load(path_in_cache); if (idx_cache.version() < idx_rsrc.version()) { + BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename(); fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists); } } @@ -302,14 +346,18 @@ void PresetUpdater::priv::check_install_indices() const } } +// Generates a list of bundle updates that are to be performed Updates PresetUpdater::priv::config_update() const { Updates updates; + + BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates..."; for (const auto idx : index_db) { const auto bundle_path = vendor_path / (idx.vendor() + ".ini"); if (! fs::exists(bundle_path)) { + BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor(); continue; } @@ -318,7 +366,7 @@ Updates PresetUpdater::priv::config_update() const const auto ver_current = idx.find(vp.config_version); if (ver_current == idx.end()) { - BOOST_LOG_TRIVIAL(warning) << boost::format("Preset bundle (`%1%`) version not found in index: %2%") % idx.vendor() % vp.config_version.to_string(); + BOOST_LOG_TRIVIAL(error) << boost::format("Preset bundle (`%1%`) version not found in index: %2%") % idx.vendor() % vp.config_version.to_string(); continue; } @@ -327,7 +375,13 @@ Updates PresetUpdater::priv::config_update() const throw std::runtime_error((boost::format("Invalid index: `%1%`") % idx.vendor()).str()); } + BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%, version cached: %3%") + % vp.name + % recommended->config_version.to_string() + % ver_current->config_version.to_string(); + if (! ver_current->is_current_slic3r_supported()) { + BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); // TODO: Downgrade situation @@ -336,6 +390,7 @@ Updates PresetUpdater::priv::config_update() const auto path_in_cache = cache_path / (idx.vendor() + ".ini"); if (! fs::exists(path_in_cache)) { + BOOST_LOG_TRIVIAL(warning) << "Index indicates update, but new bundle not found in cache: " << path_in_cache.string(); continue; } @@ -351,11 +406,16 @@ Updates PresetUpdater::priv::config_update() const void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const { + BOOST_LOG_TRIVIAL(info) << boost::format("Performing %1% updates") % updates.size(); + if (snapshot) { + BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE); } for (const auto &update : updates) { + BOOST_LOG_TRIVIAL(info) << '\t' << update; + fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); PresetBundle bundle; @@ -382,6 +442,8 @@ PresetUpdater::PresetUpdater(int version_online_event) : PresetUpdater::~PresetUpdater() { 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(); } @@ -406,8 +468,12 @@ void PresetUpdater::sync(PresetBundle *preset_bundle) void PresetUpdater::slic3r_update_notify() { - if (! p->enabled_version_check || p->had_config_update) { return; } - // ^ We don't want to bother the user with updates multiple times, put off till next time. + if (! p->enabled_version_check) { return; } + + if (p->had_config_update) { + BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed"; + return; + } auto* app_config = GUI::get_app_config(); const auto ver_slic3r = Semver::parse(SLIC3R_VERSION); @@ -438,6 +504,8 @@ void PresetUpdater::config_update() const auto updates = p->config_update(); if (updates.size() > 0) { + BOOST_LOG_TRIVIAL(info) << boost::format("Update of %1% bundles available. Asking for confirmation ...") % updates.size(); + const auto msg = _(L("Configuration update is available. Would you like to install it?")); auto ext_msg = _(L( @@ -457,20 +525,25 @@ void PresetUpdater::config_update() const wxMessageDialog dlg(NULL, msg, _(L("Configuration update")), wxYES_NO|wxCENTRE); dlg.SetExtendedMessage(ext_msg); const auto res = dlg.ShowModal(); - std::cerr << "After modal" << std::endl; if (res == wxID_YES) { - // User gave clearance, updates are go + BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; p->perform_updates(std::move(updates)); + } else { + BOOST_LOG_TRIVIAL(info) << "User refused the update"; } p->had_config_update = true; + } else { + BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; } } -void PresetUpdater::install_bundles_rsrc(std::vector &&bundles, bool snapshot) +void PresetUpdater::install_bundles_rsrc(std::vector bundles, bool snapshot) { Updates updates; + BOOST_LOG_TRIVIAL(info) << boost::format("Installing %1% bundles from resources ...") % bundles.size(); + for (const auto &bundle : bundles) { auto path_in_rsrc = p->rsrc_path / bundle; auto path_in_vendors = p->vendor_path / bundle; diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp index 8fe3e021d5..287f20652f 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -20,10 +20,17 @@ public: PresetUpdater &operator=(const PresetUpdater &) = delete; ~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); + + // If version check is enabled, check if chaced online slic3r version is newer, notify if so. void slic3r_update_notify(); + + // If updating is enabled, check if updates are available in cache, if so, ask about installation. void config_update() const; - void install_bundles_rsrc(std::vector &&bundles, bool snapshot = true); + + // "Update" a list of bundles from resources (behaves like an online update). + void install_bundles_rsrc(std::vector bundles, bool snapshot = true); private: struct priv; std::unique_ptr p; From ec7e10e068f1b8d587ede2028aa43abd13869593 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Apr 2018 11:52:03 +0200 Subject: [PATCH 066/145] Fixed wrong behavior of options group Tree after extruders count change --- xs/src/slic3r/GUI/Tab.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index e157673297..3ad40d9f7a 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1348,6 +1348,9 @@ void TabPrinter::build() m_presets = &m_preset_bundle->printers; load_initial_data(); + // to avoid redundant memory allocation / deallocation during extruders count changing + m_pages.reserve(30); + auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); const Preset* parent_preset = m_presets->get_selected_preset_parent(); @@ -1625,7 +1628,7 @@ void TabPrinter::extruders_count_changed(size_t extruders_count){ } void TabPrinter::build_extruder_pages(){ - if (m_extruders_count_old == m_extruders_count) + if (m_extruders_count_old == m_extruders_count || m_extruders_count <= 2) { // if we have a single extruder MM setup, add a page with configuration options: for (int i = 0; i < m_pages.size(); ++i) // first make sure it's not there already @@ -1642,8 +1645,6 @@ void TabPrinter::build_extruder_pages(){ optgroup->append_single_option_line("parking_pos_retraction"); m_pages.insert(m_pages.end()-2, page); } - rebuild_page_tree(); - return; } for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx){ From a7a8030feaf5d22dfa05f6427879ec3e2613b211 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 23 Apr 2018 13:58:03 +0200 Subject: [PATCH 067/145] PresetUpdater: Don't install updates that are already present in a snapshot --- xs/src/slic3r/Utils/PresetUpdater.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index a64d05eca3..8dd974537e 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -160,7 +160,7 @@ struct PresetUpdater::priv void sync_config(const std::set vendors) const; void check_install_indices() const; - Updates config_update() const; + Updates get_config_updates() const; void perform_updates(Updates &&updates, bool snapshot = true) const; }; @@ -347,10 +347,10 @@ void PresetUpdater::priv::check_install_indices() const } // Generates a list of bundle updates that are to be performed -Updates PresetUpdater::priv::config_update() const +Updates PresetUpdater::priv::get_config_updates() const { Updates updates; - + BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates..."; for (const auto idx : index_db) { @@ -388,6 +388,16 @@ Updates PresetUpdater::priv::config_update() const } else if (recommended->config_version > ver_current->config_version) { // Config bundle update situation + // Check if the update is already present in a snapshot + const auto recommended_snap = SnapshotDB::singleton().snapshot_with_vendor_preset(vp.name, recommended->config_version); + if (recommended_snap != SnapshotDB::singleton().end()) { + BOOST_LOG_TRIVIAL(info) << boost::format("Bundle update %1% %2% already found in snapshot %3%, skipping...") + % vp.name + % recommended->config_version.to_string() + % recommended_snap->id; + continue; + } + auto path_in_cache = cache_path / (idx.vendor() + ".ini"); if (! fs::exists(path_in_cache)) { BOOST_LOG_TRIVIAL(warning) << "Index indicates update, but new bundle not found in cache: " << path_in_cache.string(); @@ -502,7 +512,7 @@ void PresetUpdater::config_update() const { if (! p->enabled_config_update) { return; } - auto updates = p->config_update(); + auto updates = p->get_config_updates(); if (updates.size() > 0) { BOOST_LOG_TRIVIAL(info) << boost::format("Update of %1% bundles available. Asking for confirmation ...") % updates.size(); From 6432ec8a9b91939b3e433e9a44c1f437a4b35b82 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Apr 2018 15:09:01 +0200 Subject: [PATCH 068/145] Experiment with extruders count change --- xs/src/slic3r/GUI/Tab.cpp | 86 ++++++++++++++++++++++++++++++++++----- xs/src/slic3r/GUI/Tab.hpp | 5 +++ 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 3ad40d9f7a..f0471cea08 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1306,6 +1306,7 @@ void TabFilament::reload_config(){ void TabFilament::update() { + Freeze(); wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); @@ -1319,6 +1320,7 @@ void TabFilament::update() for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); + Thaw(); } void TabFilament::OnActivate() @@ -1349,7 +1351,8 @@ void TabPrinter::build() load_initial_data(); // to avoid redundant memory allocation / deallocation during extruders count changing - m_pages.reserve(30); + m_pages.reserve(30); + m_extruders_tree_items.reserve(30); auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); @@ -1400,8 +1403,10 @@ void TabPrinter::build() size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); wxTheApp->CallAfter([this, opt_key, value, extruders_count](){ if (opt_key.compare("extruders_count")==0 || opt_key.compare("single_extruder_multi_material")==0) { + m_correct_treectrl = true; extruders_count_changed(extruders_count); update_dirty(); + m_correct_treectrl = false; if (opt_key.compare("single_extruder_multi_material")==0) // the single_extruder_multimaterial was added to force pages on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped } @@ -1835,19 +1840,80 @@ void Tab::rebuild_page_tree() // get label of the currently selected item auto selected = m_treectrl->GetItemText(m_treectrl->GetSelection()); auto rootItem = m_treectrl->GetRootItem(); - m_treectrl->DeleteChildren(rootItem); + auto have_selection = 0; - for (auto p : m_pages) + if (name() == "printer") { - auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); - m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - if (p->title() == selected) { - m_disable_tree_sel_changed_event = 1; - m_treectrl->SelectItem(itemId); - m_disable_tree_sel_changed_event = 0; - have_selection = 1; + TabPrinter* tab = dynamic_cast(this); + if (!tab->m_correct_treectrl){ + m_treectrl->DeleteChildren(rootItem); + tab->m_extruders_tree_items.resize(0); + tab->m_single_extruder_MM_item = nullptr; + for (auto p : m_pages) + { + auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); + + if (p->title().Contains(_("Extruder"))) + tab->m_extruders_tree_items.push_back(itemId); + + if (p->title() == _("Single extruder MM setup")) + tab->m_single_extruder_MM_item = itemId; + + if (p->title() == selected) { + m_disable_tree_sel_changed_event = 1; + m_treectrl->SelectItem(itemId); + m_disable_tree_sel_changed_event = 0; + have_selection = 1; + } + } + } + else + { + while (tab->m_extruders_tree_items.size() > tab->m_extruders_count){ + m_treectrl->Delete(tab->m_extruders_tree_items.back()); + tab->m_extruders_tree_items.pop_back(); + } + + size_t i = 2 + tab->m_extruders_tree_items.size(); + for (; i < 2 + tab->m_extruders_count; ++i) + { + auto p = m_pages[i]; + auto itemId = m_treectrl->InsertItem(rootItem, tab->m_extruders_tree_items.back(), p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); + tab->m_extruders_tree_items.push_back(itemId); + } + + if (tab->m_extruders_count == 1 || !m_config->opt_bool("single_extruder_multi_material") + && tab->m_single_extruder_MM_item) + { + m_treectrl->Delete(tab->m_single_extruder_MM_item); + tab->m_single_extruder_MM_item = nullptr; + } + else if (tab->m_single_extruder_MM_item == nullptr) + { + auto p = m_pages[i]; + auto itemId = tab->m_single_extruder_MM_item = + m_treectrl->InsertItem(rootItem, tab->m_extruders_tree_items.back(), p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); + } } } + else + { + m_treectrl->DeleteChildren(rootItem); + for (auto p : m_pages) + { + auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); + if (p->title() == selected) { + m_disable_tree_sel_changed_event = 1; + m_treectrl->SelectItem(itemId); + m_disable_tree_sel_changed_event = 0; + have_selection = 1; + } + } + } if (!have_selection) { // this is triggered on first load, so we don't disable the sel change event diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index eda6727a85..0961c71fb2 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -300,6 +300,11 @@ public: size_t m_initial_extruders_count; size_t m_sys_extruders_count; + std::vector m_extruders_tree_items; + wxTreeItemId m_single_extruder_MM_item = nullptr; + bool m_correct_treectrl = false; // m_correct_tree = false -> delete/create m_treectrl + // m_correct_tree = true -> just correct m_treectrl + TabPrinter() {} TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {} ~TabPrinter(){} From a3c3eb5d2af4a9e6e809377f78e3d211a2f00182 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Apr 2018 15:30:41 +0200 Subject: [PATCH 069/145] Fixed GCode Preview not invalidated when deleting an object --- lib/Slic3r/GUI/Plater.pm | 1 + lib/Slic3r/GUI/Plater/3DPreview.pm | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5a29ab394e..ce78eab8ec 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1677,6 +1677,7 @@ sub update { $self->{canvas}->reload_scene if $self->{canvas}; $self->{canvas3D}->reload_scene if $self->{canvas3D}; + $self->{preview3D}->reset_gcode_preview_data if $self->{preview3D}; $self->{preview3D}->reload_print if $self->{preview3D}; } diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm index bd49bedb6d..4834f7aed1 100644 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ b/lib/Slic3r/GUI/Plater/3DPreview.pm @@ -289,6 +289,12 @@ sub refresh_print { $self->load_print; } +sub reset_gcode_preview_data { + my ($self) = @_; + $self->gcode_preview_data->reset; + $self->canvas->reset_legend_texture(); +} + sub load_print { my ($self) = @_; From b0841f78f4f602858bdaf23c2d9b41fb3b4f1958 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Apr 2018 15:39:55 +0200 Subject: [PATCH 070/145] Experiment failed --- xs/src/slic3r/GUI/Tab.cpp | 95 ++++++++------------------------------- xs/src/slic3r/GUI/Tab.hpp | 5 --- 2 files changed, 18 insertions(+), 82 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index f0471cea08..fa6ed7ca64 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1352,7 +1352,6 @@ void TabPrinter::build() // to avoid redundant memory allocation / deallocation during extruders count changing m_pages.reserve(30); - m_extruders_tree_items.reserve(30); auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); @@ -1403,10 +1402,8 @@ void TabPrinter::build() size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); wxTheApp->CallAfter([this, opt_key, value, extruders_count](){ if (opt_key.compare("extruders_count")==0 || opt_key.compare("single_extruder_multi_material")==0) { - m_correct_treectrl = true; extruders_count_changed(extruders_count); update_dirty(); - m_correct_treectrl = false; if (opt_key.compare("single_extruder_multi_material")==0) // the single_extruder_multimaterial was added to force pages on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped } @@ -1633,6 +1630,9 @@ void TabPrinter::extruders_count_changed(size_t extruders_count){ } void TabPrinter::build_extruder_pages(){ + size_t n_before_extruders = 2; // Count of pages before Extruder pages + size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page + if (m_extruders_count_old == m_extruders_count || m_extruders_count <= 2) { // if we have a single extruder MM setup, add a page with configuration options: @@ -1648,16 +1648,16 @@ void TabPrinter::build_extruder_pages(){ optgroup->append_single_option_line("cooling_tube_retraction"); optgroup->append_single_option_line("cooling_tube_length"); optgroup->append_single_option_line("parking_pos_retraction"); - m_pages.insert(m_pages.end()-2, page); + m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); } } - + for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx){ //# build page char buf[MIN_BUF_LENGTH_FOR_L]; sprintf(buf, _CHB(L("Extruder %d")), extruder_idx + 1); auto page = add_options_page(from_u8(buf), "funnel.png", true); - m_pages.insert(m_pages.begin() + 2+extruder_idx, page); + m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); auto optgroup = page->new_optgroup(_(L("Size"))); optgroup->append_single_option_line("nozzle_diameter", extruder_idx); @@ -1696,7 +1696,8 @@ void TabPrinter::build_extruder_pages(){ // # remove extra pages if (m_extruders_count < m_extruders_count_old) - m_pages.erase(m_pages.begin() + 2 + m_extruders_count, m_pages.begin() + 2 + m_extruders_count_old); + m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count, + m_pages.begin() + n_before_extruders + m_extruders_count_old); m_extruders_count_old = m_extruders_count; @@ -1842,79 +1843,19 @@ void Tab::rebuild_page_tree() auto rootItem = m_treectrl->GetRootItem(); auto have_selection = 0; - if (name() == "printer") + m_treectrl->DeleteChildren(rootItem); + for (auto p : m_pages) { - TabPrinter* tab = dynamic_cast(this); - if (!tab->m_correct_treectrl){ - m_treectrl->DeleteChildren(rootItem); - tab->m_extruders_tree_items.resize(0); - tab->m_single_extruder_MM_item = nullptr; - for (auto p : m_pages) - { - auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); - m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - - if (p->title().Contains(_("Extruder"))) - tab->m_extruders_tree_items.push_back(itemId); - - if (p->title() == _("Single extruder MM setup")) - tab->m_single_extruder_MM_item = itemId; - - if (p->title() == selected) { - m_disable_tree_sel_changed_event = 1; - m_treectrl->SelectItem(itemId); - m_disable_tree_sel_changed_event = 0; - have_selection = 1; - } - } - } - else - { - while (tab->m_extruders_tree_items.size() > tab->m_extruders_count){ - m_treectrl->Delete(tab->m_extruders_tree_items.back()); - tab->m_extruders_tree_items.pop_back(); - } - - size_t i = 2 + tab->m_extruders_tree_items.size(); - for (; i < 2 + tab->m_extruders_count; ++i) - { - auto p = m_pages[i]; - auto itemId = m_treectrl->InsertItem(rootItem, tab->m_extruders_tree_items.back(), p->title(), p->iconID()); - m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - tab->m_extruders_tree_items.push_back(itemId); - } - - if (tab->m_extruders_count == 1 || !m_config->opt_bool("single_extruder_multi_material") - && tab->m_single_extruder_MM_item) - { - m_treectrl->Delete(tab->m_single_extruder_MM_item); - tab->m_single_extruder_MM_item = nullptr; - } - else if (tab->m_single_extruder_MM_item == nullptr) - { - auto p = m_pages[i]; - auto itemId = tab->m_single_extruder_MM_item = - m_treectrl->InsertItem(rootItem, tab->m_extruders_tree_items.back(), p->title(), p->iconID()); - m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - } + auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); + if (p->title() == selected) { + m_disable_tree_sel_changed_event = 1; + m_treectrl->SelectItem(itemId); + m_disable_tree_sel_changed_event = 0; + have_selection = 1; } } - else - { - m_treectrl->DeleteChildren(rootItem); - for (auto p : m_pages) - { - auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); - m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - if (p->title() == selected) { - m_disable_tree_sel_changed_event = 1; - m_treectrl->SelectItem(itemId); - m_disable_tree_sel_changed_event = 0; - have_selection = 1; - } - } - } - + if (!have_selection) { // this is triggered on first load, so we don't disable the sel change event m_treectrl->SelectItem(m_treectrl->GetFirstVisibleItem());//! (treectrl->GetFirstChild(rootItem)); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 0961c71fb2..eda6727a85 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -300,11 +300,6 @@ public: size_t m_initial_extruders_count; size_t m_sys_extruders_count; - std::vector m_extruders_tree_items; - wxTreeItemId m_single_extruder_MM_item = nullptr; - bool m_correct_treectrl = false; // m_correct_tree = false -> delete/create m_treectrl - // m_correct_tree = true -> just correct m_treectrl - TabPrinter() {} TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {} ~TabPrinter(){} From e31f5fc4b6d69a67f755cd8570d469b50237a6bb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 24 Apr 2018 08:49:37 +0200 Subject: [PATCH 071/145] Modified text for tooltips and ButtonsDescription --- xs/src/slic3r/GUI/ButtonsDescription.cpp | 10 +++--- xs/src/slic3r/GUI/Tab.cpp | 44 +++++++++++++----------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/xs/src/slic3r/GUI/ButtonsDescription.cpp b/xs/src/slic3r/GUI/ButtonsDescription.cpp index 6831d0dddb..b932985bf5 100644 --- a/xs/src/slic3r/GUI/ButtonsDescription.cpp +++ b/xs/src/slic3r/GUI/ButtonsDescription.cpp @@ -24,10 +24,12 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* ic std::istringstream f(pair.second); std::string s; - while (getline(f, s, ';')) { - auto description = new wxStaticText(this, wxID_ANY, _(s)); - grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL); - } + getline(f, s, ';'); + auto description = new wxStaticText(this, wxID_ANY, _(s)); + grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL); + getline(f, s, ';'); + description = new wxStaticText(this, wxID_ANY, _(s)); + grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); } auto button = CreateStdDialogButtonSizer(wxOK); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index fa6ed7ca64..c9c7ad6325 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -111,18 +111,20 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_question_btn->SetBackgroundColour(color); } - m_undo_to_sys_btn->SetToolTip(_(L( "UNLOCKED LOCK icon indicates about some value changes compared with system values " - "in current option group.\n" - "LOCKED LOCK icon indicates about same values compared with system values " - "in current option group.\n" - "WHITE BULLET icon indicates about nonsystem preset.\n\n" - "Click the UNLOCKED LOCK to revert all values in current option group to system values."))); + m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " + "for the current option group.\n" + "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " + "to the system values for the current option group.\n" + "WHITE BULLET icon indicates a nonsystem preset.\n\n" + "Click the UNLOCKED LOCK icon to reset all settings for current option group to " + "the system values."))); - m_undo_btn->SetToolTip(_(L( "BACK ARROW icon indicates about some value changes compared with last saved preset values " - "in current option group.\n" - "WHITE BULLET icon indicates about same values compared with last saved preset values " - "in current option group.\n\n" - "Click the BACK ARROW to revert all values in current option group to last saved preset values."))); + m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" + "preset for the current option group.\n" + "BACK ARROW icon indicates that the settings were changed and are not equal to " + "the last saved preset for the current option group.\n\n" + "Click the BACK ARROW icon to reset all settings for the current option group to " + "the last saved preset."))); m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information."))); @@ -2362,19 +2364,23 @@ void Tab::update_tab_presets(wxComboCtrl* ui, bool show_incompatible) void Tab::fill_icon_descriptions() { m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_lock, L("LOCKED LOCK;" - "indicates about same values compared with system values in current option group"))); + "indicates that the settings are the same as the system values for the current option group"))); m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_unlock, L("UNLOCKED LOCK;" - "indicates about some value changes compared with system values in current option group.\n" - "Click the UNLOCKED LOCK to revert all values in current option group to system values."))); + "indicates that some settings were changed and are not equal to the system values for " + "the current option group.\n" + "Click the UNLOCKED LOCK icon to reset all settings for current option group to " + "the system values."))); m_icon_descriptions.push_back(t_icon_description(&m_bmp_white_bullet, L("WHITE BULLET;" - "indicates about : \n - nonsystem preset (on left button)" - "\n - same values compared with last saved preset values in current option group(on right button)"))); + "for the left button: \tindicates a non-system preset,\n" + "for the right button: \tindicates that the settings hasn't been modified."))); m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_revert, L("BACK ARROW;" - "indicates about some value changes compared with last saved preset values in current option group.\n" - "Click the BACK ARROW to revert all values in current option group to last saved preset values."))); + "indicates that the settings were changed and are not equal to the last saved preset for " + "the current option group.\n" + "Click the BACK ARROW icon to reset all settings for the current option group to " + "the last saved preset."))); } void Page::reload_config() @@ -2435,8 +2441,6 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la return static_cast(GetParent())->m_presets->get_selected_preset_parent() != nullptr; }; -// optgroup->nonsys_btn_icon = m_bmp_non_system; - vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); m_optgroups.push_back(optgroup); From 1e8d302fd4e4028faa17deecf32bb811dd913bb1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 24 Apr 2018 10:33:11 +0200 Subject: [PATCH 072/145] Fixed wrong updating of "Top/Bottom fill pattern" --- xs/src/slic3r/GUI/Field.cpp | 91 ++++++++++++++++++++++++------------- xs/src/slic3r/GUI/Field.hpp | 23 ++++++---- 2 files changed, 73 insertions(+), 41 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 2527202879..d0a2ccec21 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -80,12 +80,13 @@ namespace Slic3r { namespace GUI { return std::regex_match(string, regex_pattern); } - boost::any Field::get_value_by_opt_type(wxString& str) +// boost::any Field::get_value_by_opt_type(wxString& str) + void Field::get_value_by_opt_type(wxString& str) { - boost::any ret_val; +// boost::any m_value; switch (m_opt.type){ case coInt: - ret_val = wxAtoi(str); + m_value = wxAtoi(str); break; case coPercent: case coPercents: @@ -95,18 +96,18 @@ namespace Slic3r { namespace GUI { str.RemoveLast(); double val; str.ToCDouble(&val); - ret_val = val; + m_value = val; break; } case coString: case coStrings: case coFloatOrPercent: - ret_val = str.ToStdString(); + m_value = str.ToStdString(); break; default: break; } - return ret_val; +// return m_value; } void TextCtrl::BUILD() { @@ -182,12 +183,12 @@ namespace Slic3r { namespace GUI { window = dynamic_cast(temp); } - boost::any TextCtrl::get_value() + boost::any& TextCtrl::get_value() { wxString ret_str = static_cast(window)->GetValue(); - boost::any ret_val = get_value_by_opt_type(ret_str); + /*boost::any ret_val*/get_value_by_opt_type(ret_str); - return ret_val; + return m_value;//ret_val; } void TextCtrl::enable() { dynamic_cast(window)->Enable(); dynamic_cast(window)->SetEditable(true); } @@ -215,15 +216,15 @@ void CheckBox::BUILD() { window = dynamic_cast(temp); } -boost::any CheckBox::get_value() +boost::any& CheckBox::get_value() { - boost::any ret_val; +// boost::any m_value; bool value = dynamic_cast(window)->GetValue(); if (m_opt.type == coBool) - ret_val = static_cast(value); + m_value = static_cast(value); else - ret_val = static_cast(value); - return ret_val; + m_value = static_cast(value); + return m_value; } int undef_spin_val = -9999; //! Probably, It's not necessary @@ -423,7 +424,33 @@ void Choice::set_value(const boost::any& value, bool change_event) break; } case coEnum:{ - dynamic_cast(window)->SetSelection(boost::any_cast(value)); + int val = boost::any_cast(value); + if (m_opt_id.compare("external_fill_pattern") == 0) + { + if (!m_opt.enum_values.empty()){ + std::string key; + t_config_enum_values map_names = ConfigOptionEnum::get_enum_values(); + for (auto it : map_names) { + if (val == it.second) { + key = it.first; + break; + } + } + + size_t idx = 0; + for (auto el : m_opt.enum_values) + { + if (el.compare(key) == 0) + break; + ++idx; + } + + val = idx == m_opt.enum_values.size() ? 0 : idx; + } + else + val = 0; + } + dynamic_cast(window)->SetSelection(val); break; } default: @@ -453,16 +480,16 @@ void Choice::set_values(const std::vector& values) m_disable_change_event = false; } -boost::any Choice::get_value() +boost::any& Choice::get_value() { - boost::any ret_val; +// boost::any m_value; wxString ret_str = static_cast(window)->GetValue(); if (m_opt_id == "support") - return ret_str; + return m_value = boost::any(ret_str);//ret_str; if (m_opt.type != coEnum) - ret_val = get_value_by_opt_type(ret_str); + /*m_value = */get_value_by_opt_type(ret_str); else { int ret_enum = static_cast(window)->GetSelection(); @@ -473,22 +500,22 @@ boost::any Choice::get_value() t_config_enum_values map_names = ConfigOptionEnum::get_enum_values(); int value = map_names.at(key); - ret_val = static_cast(value); + m_value = static_cast(value); } else - ret_val = static_cast(0); + m_value = static_cast(0); } if (m_opt_id.compare("fill_pattern") == 0) - ret_val = static_cast(ret_enum); + m_value = static_cast(ret_enum); else if (m_opt_id.compare("gcode_flavor") == 0) - ret_val = static_cast(ret_enum); + m_value = static_cast(ret_enum); else if (m_opt_id.compare("support_material_pattern") == 0) - ret_val = static_cast(ret_enum); + m_value = static_cast(ret_enum); else if (m_opt_id.compare("seam_position") == 0) - ret_val = static_cast(ret_enum); + m_value = static_cast(ret_enum); } - return ret_val; + return m_value; } void ColourPicker::BUILD() @@ -508,14 +535,14 @@ void ColourPicker::BUILD() temp->SetToolTip(get_tooltip_text(clr)); } -boost::any ColourPicker::get_value(){ - boost::any ret_val; +boost::any& ColourPicker::get_value(){ +// boost::any m_value; auto colour = static_cast(window)->GetColour(); auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - ret_val = clr_str.ToStdString(); + m_value = clr_str.ToStdString(); - return ret_val; + return m_value; } void PointCtrl::BUILD() @@ -579,7 +606,7 @@ void PointCtrl::set_value(const boost::any& value, bool change_event) set_value(pt, change_event); } -boost::any PointCtrl::get_value() +boost::any& PointCtrl::get_value() { Pointf ret_point; double val; @@ -587,7 +614,7 @@ boost::any PointCtrl::get_value() ret_point.x = val; y_textctrl->GetValue().ToDouble(&val); ret_point.y = val; - return ret_point; + return m_value = ret_point; } } // GUI diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 292bfd81f8..9e5730cf6e 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -85,7 +85,7 @@ public: /// Gets a boost::any representing this control. /// subclasses should overload with a specific version - virtual boost::any get_value() = 0; + virtual boost::any& get_value() = 0; virtual void enable() = 0; virtual void disable() = 0; @@ -106,7 +106,8 @@ public: virtual wxWindow* getWindow() { return nullptr; } bool is_matched(const std::string& string, const std::string& pattern); - boost::any get_value_by_opt_type(wxString& str); +// boost::any get_value_by_opt_type(wxString& str); + void get_value_by_opt_type(wxString& str); /// Factory method for generating new derived classes. template @@ -157,6 +158,9 @@ protected: // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. const wxColour* m_label_color; + // current value + boost::any m_value; + friend class OptionsGroup; }; @@ -191,7 +195,7 @@ public: m_disable_change_event = false; } - boost::any get_value() override; + boost::any& get_value() override; virtual void enable(); virtual void disable(); @@ -218,7 +222,7 @@ public: dynamic_cast(window)->SetValue(boost::any_cast(value)); m_disable_change_event = false; } - boost::any get_value() override; + boost::any& get_value() override; void enable() override { dynamic_cast(window)->Enable(); } void disable() override { dynamic_cast(window)->Disable(); } @@ -248,8 +252,9 @@ public: dynamic_cast(window)->SetValue(tmp_value); m_disable_change_event = false; } - boost::any get_value() override { - return boost::any(tmp_value); + boost::any& get_value() override { +// return boost::any(tmp_value); + return m_value = tmp_value; } void enable() override { dynamic_cast(window)->Enable(); } @@ -271,7 +276,7 @@ public: void set_value(const std::string& value, bool change_event = false); void set_value(const boost::any& value, bool change_event = false); void set_values(const std::vector &values); - boost::any get_value() override; + boost::any& get_value() override; void enable() override { dynamic_cast(window)->Enable(); }; void disable() override{ dynamic_cast(window)->Disable(); }; @@ -299,7 +304,7 @@ public: m_disable_change_event = false; } - boost::any get_value() override; + boost::any& get_value() override; void enable() override { dynamic_cast(window)->Enable(); }; void disable() override{ dynamic_cast(window)->Disable(); }; @@ -321,7 +326,7 @@ public: void set_value(const Pointf& value, bool change_event = false); void set_value(const boost::any& value, bool change_event = false); - boost::any get_value() override; + boost::any& get_value() override; void enable() override { x_textctrl->Enable(); From 92b67fb62ea0b0c3019c8ae601ae19970718ddc9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 24 Apr 2018 12:12:15 +0200 Subject: [PATCH 073/145] Change reset buttons tooltips according to its state --- xs/src/slic3r/GUI/OptionsGroup.cpp | 3 +- xs/src/slic3r/GUI/Tab.cpp | 49 ++++++++++++++++++++---------- xs/src/slic3r/GUI/Tab.hpp | 13 ++++++-- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index c71dd5ba6b..265497e9c0 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -149,7 +149,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // If there's a widget, build it and add the result to the sizer. if (line.widget != nullptr) { auto wgt = line.widget(parent()); - grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 5); + // If widget doesn't have label, don't use border + grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5); if (colored_Label != nullptr) *colored_Label = label; return; } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index c9c7ad6325..8200ec4b23 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -104,27 +104,41 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_undo_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); m_undo_to_sys_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); m_question_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); -// m_undo_btns_legent = new wxStaticText(panel, wxID_ANY, _(L("For more information about buttons hover the mouse cursor over them.")), wxDefaultPosition, wxDefaultSize); if (wxMSW) { m_undo_btn->SetBackgroundColour(color); m_undo_to_sys_btn->SetBackgroundColour(color); m_question_btn->SetBackgroundColour(color); } - m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " - "for the current option group.\n" - "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " - "to the system values for the current option group.\n" - "WHITE BULLET icon indicates a nonsystem preset.\n\n" - "Click the UNLOCKED LOCK icon to reset all settings for current option group to " - "the system values."))); +// m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " +// "for the current option group.\n" +// "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " +// "to the system values for the current option group.\n" +// "WHITE BULLET icon indicates a non system preset.\n\n" +// "Click the UNLOCKED LOCK icon to reset all settings for current option group to " +// "the system values."))); +// +// m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" +// "preset for the current option group.\n" +// "BACK ARROW icon indicates that the settings were changed and are not equal to " +// "the last saved preset for the current option group.\n\n" +// "Click the BACK ARROW icon to reset all settings for the current option group to " +// "the last saved preset."))); - m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" - "preset for the current option group.\n" - "BACK ARROW icon indicates that the settings were changed and are not equal to " - "the last saved preset for the current option group.\n\n" - "Click the BACK ARROW icon to reset all settings for the current option group to " - "the last saved preset."))); + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system values " + "for the current option group")); + m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " + "to the system values for the current option group.\n" + "Click it to reset all settings for current option group to the system values.")); + m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); + m_tt_non_system = &m_tt_white_bullet_ns; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " + "preset for the current option group.")); + m_tt_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " + "the last saved preset for the current option group.\n" + "Click it to reset all settings for the current option group to the last saved preset.")); m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information."))); @@ -171,8 +185,6 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(32); m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); -// m_hsizer->Add(m_undo_btns_legent, 0, wxALIGN_CENTER_VERTICAL); -// m_hsizer->AddSpacer(64); // m_hsizer->Add(m_cc_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); //Horizontal sizer to hold the tree and the selected page. @@ -265,6 +277,7 @@ void Tab::load_initial_data() { m_config = &m_presets->get_edited_preset().config; m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_tt_white_bullet_ns; } PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages/* = false*/) @@ -468,6 +481,9 @@ void Tab::update_undo_buttons() { m_undo_btn->SetBitmap(m_is_modified_values ? m_bmp_value_revert : m_bmp_white_bullet); m_undo_to_sys_btn->SetBitmap(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); + + m_undo_btn->SetToolTip(m_is_modified_values ? m_tt_value_revert : m_tt_white_bullet); + m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_tt_non_system : m_tt_value_lock); } void Tab::on_roll_back_value(const bool to_sys /*= true*/) @@ -1810,6 +1826,7 @@ void Tab::load_current_preset() // Reload preset pages with the new configuration values. reload_config(); m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_tt_white_bullet_ns; // use CallAfter because some field triggers schedule on_change calls using CallAfter, // and we don't want them to be called after this update_dirty() as they would mark the diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index eda6727a85..8c6e7f3295 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -118,7 +118,6 @@ protected: wxButton* m_undo_btn; wxButton* m_undo_to_sys_btn; wxButton* m_question_btn; -// wxStaticText* m_undo_btns_legent; wxComboCtrl* m_cc_presets_choice; wxDataViewTreeCtrl* m_presetctrl; @@ -136,7 +135,7 @@ protected: wxBitmap *m_bmp_non_system; // Bitmaps to be shown on the "Undo user changes" button next to each input field. wxBitmap m_bmp_value_revert; - wxBitmap m_bmp_value_unmodified; +// wxBitmap m_bmp_value_unmodified; wxBitmap m_bmp_question; // Colors for ui "decoration" @@ -144,6 +143,16 @@ protected: wxColour m_modified_label_clr; wxColour m_default_text_clr; + // Text for reset buttons tooltips + wxString m_tt_value_lock; + wxString m_tt_value_unlock; + wxString m_tt_white_bullet_ns; + // The following text points to either m_bmp_value_unlock or m_bmp_white_bullet, depending on whether the current preset has a parent preset. + wxString *m_tt_non_system; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + wxString m_tt_white_bullet; + wxString m_tt_value_revert; + int m_icon_count; std::map m_icon_index; // Map from an icon file name to its index std::vector m_pages; From 7c7c37a4f4b8c4f14fc5ece0cee84e2742e3e64d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 24 Apr 2018 14:11:23 +0200 Subject: [PATCH 074/145] Added tooltips for reset buttons near each option --- xs/src/slic3r/GUI/Field.hpp | 24 ++++++++- xs/src/slic3r/GUI/Tab.cpp | 105 +++++++++++++++++++++++------------- xs/src/slic3r/GUI/Tab.hpp | 18 +++++-- 3 files changed, 104 insertions(+), 43 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 9e5730cf6e..c7eb25c754 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -146,13 +146,33 @@ public: return false; } + bool set_undo_tooltip(const wxString *tip) { + if (m_undo_tooltip != tip) { + m_undo_tooltip = tip; + m_Undo_btn->SetToolTip(*tip); + return true; + } + return false; + } + + bool set_undo_to_sys_tooltip(const wxString *tip) { + if (m_undo_to_sys_tooltip != tip) { + m_undo_to_sys_tooltip = tip; + m_Undo_to_sys_btn->SetToolTip(*tip); + return true; + } + return false; + } + protected: wxButton* m_Undo_btn = nullptr; - // Bitmap for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. + // Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. const wxBitmap* m_undo_bitmap = nullptr; + const wxString* m_undo_tooltip = nullptr; wxButton* m_Undo_to_sys_btn = nullptr; - // Bitmap for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. + // Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. const wxBitmap* m_undo_to_sys_bitmap = nullptr; + const wxString* m_undo_to_sys_tooltip = nullptr; wxStaticText* m_Label = nullptr; // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 8200ec4b23..5fbd9c96da 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -110,36 +110,6 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_question_btn->SetBackgroundColour(color); } -// m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " -// "for the current option group.\n" -// "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " -// "to the system values for the current option group.\n" -// "WHITE BULLET icon indicates a non system preset.\n\n" -// "Click the UNLOCKED LOCK icon to reset all settings for current option group to " -// "the system values."))); -// -// m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" -// "preset for the current option group.\n" -// "BACK ARROW icon indicates that the settings were changed and are not equal to " -// "the last saved preset for the current option group.\n\n" -// "Click the BACK ARROW icon to reset all settings for the current option group to " -// "the last saved preset."))); - - // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system values " - "for the current option group")); - m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " - "to the system values for the current option group.\n" - "Click it to reset all settings for current option group to the system values.")); - m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); - m_tt_non_system = &m_tt_white_bullet_ns; - // Bitmaps to be shown on the "Undo user changes" button next to each input field. - m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " - "preset for the current option group.")); - m_tt_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " - "the last saved preset for the current option group.\n" - "Click it to reset all settings for the current option group to the last saved preset.")); - m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information."))); // Determine the theme color of OS (dark or light) @@ -154,6 +124,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_bmp_question .LoadFile(from_u8(var("question_mark_01.png")), wxBITMAP_TYPE_PNG); fill_icon_descriptions(); + set_tooltips_text(); m_undo_btn->SetBitmap(m_bmp_white_bullet); m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(); })); @@ -277,7 +248,8 @@ void Tab::load_initial_data() { m_config = &m_presets->get_edited_preset().config; m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_tt_white_bullet_ns; + m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; } PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages/* = false*/) @@ -335,15 +307,23 @@ void Tab::update_changed_ui() { bool is_nonsys_value = false; bool is_modified_value = true; - const wxBitmap *sys_icon = &m_bmp_value_lock; - const wxBitmap *icon = &m_bmp_value_revert; - const wxColour *color = &m_sys_label_clr; + const wxBitmap *sys_icon = &m_bmp_value_lock; + const wxBitmap *icon = &m_bmp_value_revert; + const wxColour *color = &m_sys_label_clr; + + const wxString *sys_tt = &m_tt_value_lock; + const wxString *tt = &m_tt_value_revert; + + // value isn't equal to system value if ((opt.second & osSystemValue) == 0){ is_nonsys_value = true; sys_icon = m_bmp_non_system; + sys_tt = m_tt_non_system; + // value is equal to last saved if ((opt.second & osInitValue) != 0) color = &m_default_text_clr; + // value is modified else color = &m_modified_label_clr; } @@ -351,6 +331,7 @@ void Tab::update_changed_ui() { is_modified_value = false; icon = &m_bmp_white_bullet; + tt = &m_tt_white_bullet; } if (opt.first == "bed_shape" || opt.first == "compatible_printers") { if (m_colored_Label != nullptr) { @@ -366,6 +347,8 @@ void Tab::update_changed_ui() field->m_is_modified_value = is_modified_value; field->set_undo_bitmap(icon); field->set_undo_to_sys_bitmap(sys_icon); + field->set_undo_tooltip(tt); + field->set_undo_to_sys_tooltip(sys_tt); field->set_label_colour(color); } Thaw(); @@ -482,8 +465,8 @@ void Tab::update_undo_buttons() m_undo_btn->SetBitmap(m_is_modified_values ? m_bmp_value_revert : m_bmp_white_bullet); m_undo_to_sys_btn->SetBitmap(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); - m_undo_btn->SetToolTip(m_is_modified_values ? m_tt_value_revert : m_tt_white_bullet); - m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_tt_non_system : m_tt_value_lock); + m_undo_btn->SetToolTip(m_is_modified_values ? m_ttg_value_revert : m_ttg_white_bullet); + m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); } void Tab::on_roll_back_value(const bool to_sys /*= true*/) @@ -1826,7 +1809,8 @@ void Tab::load_current_preset() // Reload preset pages with the new configuration values. reload_config(); m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_tt_white_bullet_ns; + m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; // use CallAfter because some field triggers schedule on_change calls using CallAfter, // and we don't want them to be called after this update_dirty() as they would mark the @@ -2400,6 +2384,53 @@ void Tab::fill_icon_descriptions() "the last saved preset."))); } +void Tab::set_tooltips_text() +{ +// m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " +// "for the current option group.\n" +// "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " +// "to the system values for the current option group.\n" +// "WHITE BULLET icon indicates a non system preset.\n\n" +// "Click the UNLOCKED LOCK icon to reset all settings for current option group to " +// "the system values."))); +// +// m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" +// "preset for the current option group.\n" +// "BACK ARROW icon indicates that the settings were changed and are not equal to " +// "the last saved preset for the current option group.\n\n" +// "Click the BACK ARROW icon to reset all settings for the current option group to " +// "the last saved preset."))); + + // --- Tooltip text for reset buttons (for whole options group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system values " + "for the current option group")); + m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " + "to the system values for the current option group.\n" + "Click to reset all settings for current option group to the system values.")); + m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system preset.")); + m_ttg_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " + "preset for the current option group.")); + m_ttg_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " + "the last saved preset for the current option group.\n" + "Click to reset all settings for the current option group to the last saved preset.")); + + // --- Tooltip text for reset buttons (for each option in group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system value.")); + m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " + "to the system value.\n" + "Click to reset current value to the system value.")); + // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); + m_tt_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the value is the same as in the last saved preset.")); + m_tt_value_revert = _(L("BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" + "Click to reset current value to the last saved preset.")); +} + void Page::reload_config() { for (auto group : m_optgroups) diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 8c6e7f3295..de04fc4cd9 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -143,13 +143,22 @@ protected: wxColour m_modified_label_clr; wxColour m_default_text_clr; - // Text for reset buttons tooltips + // Tooltip text for reset buttons (for whole options group) + wxString m_ttg_value_lock; + wxString m_ttg_value_unlock; + wxString m_ttg_white_bullet_ns; + // The following text points to either m_ttg_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset. + wxString *m_ttg_non_system; + // Tooltip text to be shown on the "Undo user changes" button next to each input field. + wxString m_ttg_white_bullet; + wxString m_ttg_value_revert; + + // Tooltip text for reset buttons (for each option in group) wxString m_tt_value_lock; wxString m_tt_value_unlock; - wxString m_tt_white_bullet_ns; - // The following text points to either m_bmp_value_unlock or m_bmp_white_bullet, depending on whether the current preset has a parent preset. + // The following text points to either m_tt_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset. wxString *m_tt_non_system; - // Bitmaps to be shown on the "Undo user changes" button next to each input field. + // Tooltip text to be shown on the "Undo user changes" button next to each input field. wxString m_tt_white_bullet; wxString m_tt_value_revert; @@ -260,6 +269,7 @@ protected: void update_wiping_button_visibility(); void update_tab_presets(wxComboCtrl* ui, bool show_incompatible); void fill_icon_descriptions(); + void set_tooltips_text(); }; //Slic3r::GUI::Tab::Print; From ccd1c01d0b46354af7fe2a11a205ceee9f6c5a74 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 24 Apr 2018 14:21:31 +0200 Subject: [PATCH 075/145] Fixed automatic view type selection when changing printer --- lib/Slic3r/GUI/Plater/3DPreview.pm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm index 4834f7aed1..9d8b822429 100644 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ b/lib/Slic3r/GUI/Plater/3DPreview.pm @@ -72,6 +72,9 @@ sub new { $choice_view_type->Append(L("Tool")); $choice_view_type->SetSelection(0); + # the following value needs to be changed if new items are added into $choice_view_type before "Tool" + $self->{tool_idx} = 5; + my $label_show_features = $self->{label_show_features} = Wx::StaticText->new($self, -1, L("Show")); my $combochecklist_features = $self->{combochecklist_features} = Wx::ComboCtrl->new(); @@ -204,7 +207,7 @@ sub new { }); EVT_CHOICE($self, $choice_view_type, sub { my $selection = $choice_view_type->GetCurrentSelection(); - $self->{preferred_color_mode} = ($selection == 4) ? 'tool' : 'feature'; + $self->{preferred_color_mode} = ($selection == $self->{tool_idx}) ? 'tool' : 'feature'; $self->gcode_preview_data->set_type($selection); $self->reload_print; }); @@ -334,7 +337,7 @@ sub load_print { # It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature. # Color by feature if it is a single extruder print. my $extruders = $self->{print}->extruders; - my $type = (scalar(@{$extruders}) > 1) ? 4 : 0; + my $type = (scalar(@{$extruders}) > 1) ? $self->{tool_idx} : 0; $self->gcode_preview_data->set_type($type); $self->{choice_view_type}->SetSelection($type); # If the ->SetSelection changed the following line, revert it to "decide yourself". @@ -343,7 +346,7 @@ sub load_print { # Collect colors per extruder. my @colors = (); - if (! $self->gcode_preview_data->empty() || $self->gcode_preview_data->type == 4) { + if (! $self->gcode_preview_data->empty() || $self->gcode_preview_data->type == $self->{tool_idx}) { my @extruder_colors = @{$self->{config}->extruder_colour}; my @filament_colors = @{$self->{config}->filament_colour}; for (my $i = 0; $i <= $#extruder_colors; $i += 1) { @@ -464,11 +467,11 @@ sub set_number_extruders { if ($self->{number_extruders} != $number_extruders) { $self->{number_extruders} = $number_extruders; my $type = ($number_extruders > 1) ? - 4 # color by a tool number + $self->{tool_idx} # color by a tool number : 0; # color by a feature type $self->{choice_view_type}->SetSelection($type); $self->gcode_preview_data->set_type($type); - $self->{preferred_color_mode} = ($type == 4) ? 'tool_or_feature' : 'feature'; + $self->{preferred_color_mode} = ($type == $self->{tool_idx}) ? 'tool_or_feature' : 'feature'; } } From fea560340967e782849880079013014a32d3971c Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Apr 2018 18:06:42 +0200 Subject: [PATCH 076/145] PresetUpdater: Bundle incompatibility / Slic3r downgrade scnario --- lib/Slic3r/GUI.pm | 17 +- resources/icons/Slic3r_192px_grayscale.png | Bin 0 -> 19251 bytes xs/CMakeLists.txt | 2 + xs/src/slic3r/GUI/ConfigWizard.cpp | 27 ++- xs/src/slic3r/GUI/ConfigWizard.hpp | 13 +- xs/src/slic3r/GUI/ConfigWizard_private.hpp | 5 +- xs/src/slic3r/GUI/GUI.cpp | 23 +- xs/src/slic3r/GUI/GUI.hpp | 9 +- xs/src/slic3r/GUI/UpdateDialogs.cpp | 203 +++++++++++++++++ xs/src/slic3r/GUI/UpdateDialogs.hpp | 92 ++++++++ xs/src/slic3r/Utils/PresetUpdater.cpp | 240 ++++++++++----------- xs/src/slic3r/Utils/PresetUpdater.hpp | 5 +- xs/xsp/GUI.xsp | 4 +- xs/xsp/Utils_PresetUpdater.xsp | 2 +- 14 files changed, 480 insertions(+), 162 deletions(-) create mode 100644 resources/icons/Slic3r_192px_grayscale.png create mode 100644 xs/src/slic3r/GUI/UpdateDialogs.cpp create mode 100644 xs/src/slic3r/GUI/UpdateDialogs.hpp diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 10375a9dce..89a8e79748 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -86,8 +86,9 @@ sub OnInit { Slic3r::GUI::set_wxapp($self); $self->{app_config} = Slic3r::GUI::AppConfig->new; - $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; Slic3r::GUI::set_app_config($self->{app_config}); + $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; + Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); # 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 @@ -104,7 +105,11 @@ sub OnInit { $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT); Slic3r::GUI::set_preset_updater($self->{preset_updater}); - eval { $self->{preset_updater}->config_update(); }; + eval { + if (! $self->{preset_updater}->config_update()) { + exit 0; + } + }; if ($@) { warn $@ . "\n"; fatal_error(undef, $@); @@ -120,8 +125,6 @@ sub OnInit { show_error(undef, $@); } - Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); - # application frame print STDERR "Creating main frame...\n"; Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->new); @@ -144,8 +147,10 @@ sub OnInit { # On OSX the UI was not initialized correctly if the wizard was called # before the UI was up and running. $self->CallAfter(sub { - Slic3r::GUI::config_wizard_startup($app_conf_exists); - $self->{preset_updater}->slic3r_update_notify(); + if (! Slic3r::GUI::config_wizard_startup($app_conf_exists)) { + # Only notify if there was not wizard so as not to bother too much ... + $self->{preset_updater}->slic3r_update_notify(); + } $self->{preset_updater}->sync($self->{preset_bundle}); }); diff --git a/resources/icons/Slic3r_192px_grayscale.png b/resources/icons/Slic3r_192px_grayscale.png new file mode 100644 index 0000000000000000000000000000000000000000..910f941870510c4fcc8c7c5cf728f9758af3e1d6 GIT binary patch literal 19251 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 && wizard_p()->flag_empty_datadir) { + if (wizard_p()->run_reason == ConfigWizard::RR_DATA_EMPTY) { wxString::Format(_(L("Run %s")), ConfigWizard::name()); append_text(wxString::Format( _(L("Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")), @@ -709,7 +709,7 @@ void ConfigWizard::priv::on_custom_setup() set_page(page_firmware); } -void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, PresetUpdater *updater) +void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater) { const bool is_custom_setup = page_welcome->page_next() == page_firmware; @@ -730,8 +730,14 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese install_bundles.emplace_back(vendor_rsrc.second); } - // If the datadir was empty don't take a snapshot (it would just be an empty snapshot) - const bool snapshot = !flag_empty_datadir || page_welcome->reset_user_profile(); + // Decide whether to create snapshot based on run_reason and the reset profile checkbox + bool snapshot = true; + switch (run_reason) { + case ConfigWizard::RR_DATA_EMPTY: snapshot = false; break; + case ConfigWizard::RR_DATA_LEGACY: snapshot = true; break; + case ConfigWizard::RR_DATA_INCOMPAT: snapshot = false; break; // In this case snapshot is done by PresetUpdater with the appropriate reason + case ConfigWizard::RR_USER: snapshot = page_welcome->reset_user_profile(); break; + } if (install_bundles.size() > 0) { // Install bundles from resources. updater->install_bundles_rsrc(std::move(install_bundles), snapshot); @@ -744,8 +750,7 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese app_config->set_vendors(appconfig_vendors); app_config->set("version_check", page_update->version_check ? "1" : "0"); app_config->set("preset_update", page_update->preset_update ? "1" : "0"); - if (flag_startup) - app_config->reset_selections(); + app_config->reset_selections(); // ^ TODO: replace with appropriate printer selection preset_bundle->load_presets(*app_config); } else { @@ -760,12 +765,11 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // Public -ConfigWizard::ConfigWizard(wxWindow *parent, bool startup, bool empty_datadir) : +ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) : wxDialog(parent, wxID_ANY, name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), p(new priv(this)) { - p->flag_startup = startup; - p->flag_empty_datadir = empty_datadir; + p->run_reason = reason; p->load_vendors(); p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ @@ -822,10 +826,13 @@ ConfigWizard::ConfigWizard(wxWindow *parent, bool startup, bool empty_datadir) : ConfigWizard::~ConfigWizard() {} -void ConfigWizard::run(PresetBundle *preset_bundle, PresetUpdater *updater) +bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater) { if (ShowModal() == wxID_OK) { p->apply_config(GUI::get_app_config(), preset_bundle, updater); + return true; + } else { + return false; } } diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp index eeb64c57f6..73fce7cd21 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -16,14 +16,23 @@ namespace GUI { class ConfigWizard: public wxDialog { public: - ConfigWizard(wxWindow *parent, bool startup, bool empty_datadir); + // Why is the Wizard run + enum RunReason { + RR_DATA_EMPTY, // No or empty datadir + RR_DATA_LEGACY, // Pre-updating datadir + RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation + RR_USER, // User requested the Wizard from the menus + }; + + ConfigWizard(wxWindow *parent, RunReason run_reason); ConfigWizard(ConfigWizard &&) = delete; ConfigWizard(const ConfigWizard &) = delete; ConfigWizard &operator=(ConfigWizard &&) = delete; ConfigWizard &operator=(const ConfigWizard &) = delete; ~ConfigWizard(); - void run(PresetBundle *preset_bundle, PresetUpdater *updater); + // Run the Wizard. Return whether it was completed. + bool run(PresetBundle *preset_bundle, const PresetUpdater *updater); static const wxString& name(); private: diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index cdab2eb3ca..474394bc34 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -190,8 +190,7 @@ private: struct ConfigWizard::priv { ConfigWizard *q; - bool flag_startup; - bool flag_empty_datadir; + ConfigWizard::RunReason run_reason; AppConfig appconfig_vendors; std::unordered_map vendors; std::unordered_map vendors_rsrc; @@ -228,7 +227,7 @@ struct ConfigWizard::priv void on_other_vendors(); void on_custom_setup(); - void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, PresetUpdater *updater); + void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater); }; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 88c3f421b2..bd74e118ee 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -390,7 +390,7 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ switch (event.GetId() - config_id_base) { case ConfigMenuWizard: - config_wizard(false, false); + config_wizard(ConfigWizard::RR_USER); break; case ConfigMenuTakeSnapshot: // Take a configuration snapshot. @@ -469,10 +469,11 @@ bool check_unsaved_changes() return dialog->ShowModal() == wxID_YES; } -void config_wizard_startup(bool app_config_exists) +bool config_wizard_startup(bool app_config_exists) { if (! app_config_exists || g_PresetBundle->has_defauls_only()) { - config_wizard(true, true); + config_wizard(ConfigWizard::RR_DATA_EMPTY); + return true; } else if (g_AppConfig->legacy_datadir()) { // Looks like user has legacy pre-vendorbundle data directory, // explain what this is and run the wizard @@ -496,20 +497,19 @@ void config_wizard_startup(bool app_config_exists) dlg.SetExtendedMessage(ext_msg); const auto res = dlg.ShowModal(); - config_wizard(true, false); + config_wizard(ConfigWizard::RR_DATA_LEGACY); + return true; } + return false; } -void config_wizard(bool startup, bool empty_datadir) +void config_wizard(int reason) { - if (g_wxMainFrame == nullptr) - throw std::runtime_error("Main frame not set"); - // Exit wizard if there are unsaved changes and the user cancels the action. if (! check_unsaved_changes()) return; - ConfigWizard wizard(g_wxMainFrame, startup, empty_datadir); + ConfigWizard wizard(nullptr, static_cast(reason)); wizard.run(g_PresetBundle, g_PresetUpdater); // Load the currently selected preset into the GUI, update the preset selection box. @@ -686,6 +686,11 @@ wxApp* get_app(){ return g_wxApp; } +PresetBundle* get_preset_bundle() +{ + return g_PresetBundle; +} + const wxColour& get_modified_label_clr() { return g_color_label_modified; } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 92a6e6ebb8..a8bbdccc73 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -82,6 +82,7 @@ void set_preset_updater(PresetUpdater *updater); AppConfig* get_app_config(); wxApp* get_app(); +PresetBundle* get_preset_bundle(); const wxColour& get_modified_label_clr(); const wxColour& get_sys_label_clr(); @@ -93,11 +94,13 @@ extern void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int // to notify the user whether he is aware that some preset changes will be lost. extern bool check_unsaved_changes(); -// Checks if configuration wizard needs to run, calls config_wizard if so -extern void config_wizard_startup(bool app_config_exists); +// Checks if configuration wizard needs to run, calls config_wizard if so. +// Returns whether the Wizard ran. +extern bool config_wizard_startup(bool app_config_exists); // Opens the configuration wizard, returns true if wizard is finished & accepted. -extern void config_wizard(bool startup, bool empty_datadir); +// The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl. +extern void config_wizard(int run_reason); // Create "Preferences" dialog after selecting menu "Preferences" in Perl part extern void open_preferences_dialog(int event_preferences); diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp new file mode 100644 index 0000000000..076554e237 --- /dev/null +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -0,0 +1,203 @@ +#include "UpdateDialogs.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "GUI.hpp" + +namespace Slic3r { +namespace GUI { + + +enum { + CONTENT_WIDTH = 400, + BORDER = 30, + VERT_SPACING = 15, + HORIZ_SPACING = 5, +}; + + +MsgDialog::MsgDialog(const wxString &title, const wxString &headline, wxWindowID button_id) : + MsgDialog(title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id) +{} + +MsgDialog::MsgDialog(const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) : + wxDialog(nullptr, wxID_ANY, title), + boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)), + content_sizer(new wxBoxSizer(wxVERTICAL)), + btn_sizer(new wxBoxSizer(wxHORIZONTAL)) +{ + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + auto *topsizer = new wxBoxSizer(wxHORIZONTAL); + auto *rightsizer = new wxBoxSizer(wxVERTICAL); + + auto *headtext = new wxStaticText(this, wxID_ANY, headline); + headtext->SetFont(boldfont); + headtext->Wrap(CONTENT_WIDTH); + rightsizer->Add(headtext); + rightsizer->AddSpacer(VERT_SPACING); + + rightsizer->Add(content_sizer); + + if (button_id != wxID_NONE) { + auto *button = new wxButton(this, button_id); + button->SetFocus(); + btn_sizer->Add(button); + } + + rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL); + + auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap)); + + topsizer->Add(logo, 0, wxALL, BORDER); + topsizer->Add(rightsizer, 0, wxALL, BORDER); + + SetSizerAndFit(topsizer); +} + +MsgDialog::~MsgDialog() {} + + +// MsgUpdateSlic3r + +MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online) : + MsgDialog(_(L("Update available")), _(L("New version of Slic3r PE is available"))), + ver_current(ver_current), + ver_online(ver_online) +{ + const auto url = wxString::Format("https://github.com/prusa3d/Slic3r/releases/tag/version_%s", ver_online.to_string()); + auto *link = new wxHyperlinkCtrl(this, wxID_ANY, url, url); + + auto *text = new wxStaticText(this, wxID_ANY, _(L("To download, follow the link below."))); + const auto link_width = link->GetSize().GetWidth(); + text->Wrap(CONTENT_WIDTH > link_width ? CONTENT_WIDTH : link_width); + content_sizer->Add(text); + content_sizer->AddSpacer(VERT_SPACING); + + auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING); + versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:")))); + versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string())); + versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:")))); + versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string())); + content_sizer->Add(versions); + content_sizer->AddSpacer(VERT_SPACING); + + content_sizer->Add(link); + content_sizer->AddSpacer(2*VERT_SPACING); + + cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more"))); + content_sizer->Add(cbox); + content_sizer->AddSpacer(VERT_SPACING); + + Fit(); +} + +MsgUpdateSlic3r::~MsgUpdateSlic3r() {} + +bool MsgUpdateSlic3r::disable_version_check() const +{ + return cbox->GetValue(); +} + + +// MsgUpdateConfig + +MsgUpdateConfig::MsgUpdateConfig(const std::unordered_map &updates) : + MsgDialog(_(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE) +{ + auto *text = new wxStaticText(this, wxID_ANY, _(L( + "Would you like to install it?\n\n" + "Note that a full configuration snapshot will be created first. It can then be restored at any time " + "should there be a problem with the new version.\n\n" + "Updated configuration bundles:" + ))); + text->Wrap(CONTENT_WIDTH); + content_sizer->Add(text); + content_sizer->AddSpacer(VERT_SPACING); + + auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING); + for (const auto &update : updates) { + auto *text_vendor = new wxStaticText(this, wxID_ANY, update.first); + text_vendor->SetFont(boldfont); + versions->Add(text_vendor); + versions->Add(new wxStaticText(this, wxID_ANY, update.second)); + } + + content_sizer->Add(versions); + content_sizer->AddSpacer(2*VERT_SPACING); + + auto *btn_cancel = new wxButton(this, wxID_CANCEL); + btn_sizer->Add(btn_cancel); + btn_sizer->AddSpacer(HORIZ_SPACING); + auto *btn_ok = new wxButton(this, wxID_YES); + btn_sizer->Add(btn_ok); + btn_ok->SetFocus(); + + Fit(); +} + +MsgUpdateConfig::~MsgUpdateConfig() {} + + +// MsgDataIncompatible + +MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map &incompats) : + MsgDialog(_(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png"))), wxID_NONE) +{ + auto *text = new wxStaticText(this, wxID_ANY, _(L( + "This version of Slic3r PE is not compatible with currently installed configuration bundles.\n" + "This probably happened as a result of running an older Slic3r PE after using a newer one.\n\n" + "TODO: more instrs\n" + ))); + text->Wrap(CONTENT_WIDTH); + content_sizer->Add(text); + + auto *text2 = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("This Slic3r PE version: %s")), SLIC3R_VERSION)); + text2->Wrap(CONTENT_WIDTH); + content_sizer->Add(text2); + content_sizer->AddSpacer(VERT_SPACING); + + auto *text3 = new wxStaticText(this, wxID_ANY, _(L("Incompatible bundles:"))); + text3->Wrap(CONTENT_WIDTH); + content_sizer->Add(text3); + content_sizer->AddSpacer(VERT_SPACING); + + auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING); + for (const auto &incompat : incompats) { + auto *text_vendor = new wxStaticText(this, wxID_ANY, incompat.first); + text_vendor->SetFont(boldfont); + versions->Add(text_vendor); + versions->Add(new wxStaticText(this, wxID_ANY, incompat.second)); + } + + content_sizer->Add(versions); + content_sizer->AddSpacer(2*VERT_SPACING); + + auto *btn_exit = new wxButton(this, wxID_EXIT, _(L("Exit Slic3r"))); + btn_sizer->Add(btn_exit); + btn_sizer->AddSpacer(HORIZ_SPACING); + auto *btn_reconf = new wxButton(this, wxID_REPLACE, _(L("Re-configure"))); + btn_sizer->Add(btn_reconf); + btn_exit->SetFocus(); + + auto exiter = [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); }; + btn_exit->Bind(wxEVT_BUTTON, exiter); + btn_reconf->Bind(wxEVT_BUTTON, exiter); + + Fit(); +} + +MsgDataIncompatible::~MsgDataIncompatible() {} + + +} +} diff --git a/xs/src/slic3r/GUI/UpdateDialogs.hpp b/xs/src/slic3r/GUI/UpdateDialogs.hpp new file mode 100644 index 0000000000..ae949b8dd1 --- /dev/null +++ b/xs/src/slic3r/GUI/UpdateDialogs.hpp @@ -0,0 +1,92 @@ +#ifndef slic3r_UpdateDialogs_hpp_ +#define slic3r_UpdateDialogs_hpp_ + +#include +#include + +#include +#include +#include + +#include "slic3r/Utils/Semver.hpp" + +class wxBoxSizer; +class wxCheckBox; + +namespace Slic3r { + +namespace GUI { + + +// A message / query dialog with a bitmap on the left and any content on the right +// with buttons underneath. +struct MsgDialog : wxDialog +{ + MsgDialog(MsgDialog &&) = delete; + MsgDialog(const MsgDialog &) = delete; + MsgDialog &operator=(MsgDialog &&) = delete; + MsgDialog &operator=(const MsgDialog &) = delete; + virtual ~MsgDialog(); + +protected: + // button_id is an id of a button that can be added by default, use wxID_NONE to disable + MsgDialog(const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK); + MsgDialog(const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id = wxID_OK); + + wxFont boldfont; + wxBoxSizer *content_sizer; + wxBoxSizer *btn_sizer; +}; + +// A confirmation dialog listing configuration updates +class MsgUpdateSlic3r : public MsgDialog +{ +public: + MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online); + MsgUpdateSlic3r(MsgUpdateSlic3r &&) = delete; + MsgUpdateSlic3r(const MsgUpdateSlic3r &) = delete; + MsgUpdateSlic3r &operator=(MsgUpdateSlic3r &&) = delete; + MsgUpdateSlic3r &operator=(const MsgUpdateSlic3r &) = delete; + virtual ~MsgUpdateSlic3r(); + + // Tells whether the user checked the "don't bother me again" checkbox + bool disable_version_check() const; + +private: + const Semver &ver_current; + const Semver &ver_online; + wxCheckBox *cbox; +}; + + +// Confirmation dialog informing about configuration update. Lists updated bundles & their versions. +class MsgUpdateConfig : public MsgDialog +{ +public: + // updates is a map of "vendor name" -> "version (comment)" + MsgUpdateConfig(const std::unordered_map &updates); + MsgUpdateConfig(MsgUpdateConfig &&) = delete; + MsgUpdateConfig(const MsgUpdateConfig &) = delete; + MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete; + MsgUpdateConfig &operator=(const MsgUpdateConfig &) = delete; + ~MsgUpdateConfig(); +}; + +// Informs about currently installed bundles not being compatible with the running Slic3r. Asks about action. +class MsgDataIncompatible : public MsgDialog +{ +public: + // incompats is a map of "vendor name" -> "version restrictions" + MsgDataIncompatible(const std::unordered_map &incompats); + MsgDataIncompatible(MsgDataIncompatible &&) = delete; + MsgDataIncompatible(const MsgDataIncompatible &) = delete; + MsgDataIncompatible &operator=(MsgDataIncompatible &&) = delete; + MsgDataIncompatible &operator=(const MsgDataIncompatible &) = delete; + ~MsgDataIncompatible(); +}; + + +} +} + +#endif diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 8dd974537e..ec152df63e 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -14,18 +14,13 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/GUI/UpdateDialogs.hpp" +#include "slic3r/GUI/ConfigWizard.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Config/Version.hpp" #include "slic3r/Config/Snapshot.hpp" @@ -48,90 +43,49 @@ static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; -// A confirmation dialog listing configuration updates -struct UpdateNotification : wxDialog -{ - // If this dialog gets any more complex, it should probably be factored out... - - enum { - CONTENT_WIDTH = 400, - BORDER = 30, - SPACING = 15, - }; - - wxCheckBox *cbox; - - UpdateNotification(const Semver &ver_current, const Semver &ver_online) : wxDialog(nullptr, wxID_ANY, _(L("Update available"))) - { - auto *topsizer = new wxBoxSizer(wxHORIZONTAL); - auto *sizer = new wxBoxSizer(wxVERTICAL); - - const auto url = wxString::Format("https://github.com/prusa3d/Slic3r/releases/tag/version_%s", ver_online.to_string()); - auto *link = new wxHyperlinkCtrl(this, wxID_ANY, url, url); - - auto *text = new wxStaticText(this, wxID_ANY, - _(L("New version of Slic3r PE is available. To download, follow the link below."))); - const auto link_width = link->GetSize().GetWidth(); - text->Wrap(CONTENT_WIDTH > link_width ? CONTENT_WIDTH : link_width); - sizer->Add(text); - sizer->AddSpacer(SPACING); - - auto *versions = new wxFlexGridSizer(2, 0, SPACING); - versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:")))); - versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string())); - versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:")))); - versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string())); - sizer->Add(versions); - sizer->AddSpacer(SPACING); - - sizer->Add(link); - sizer->AddSpacer(2*SPACING); - - cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more"))); - sizer->Add(cbox); - sizer->AddSpacer(SPACING); - - auto *ok = new wxButton(this, wxID_OK); - ok->SetFocus(); - sizer->Add(ok, 0, wxALIGN_CENTRE_HORIZONTAL); - - auto *logo = new wxStaticBitmap(this, wxID_ANY, wxBitmap(GUI::from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG)); - - topsizer->Add(logo, 0, wxALL, BORDER); - topsizer->Add(sizer, 0, wxALL, BORDER); - - SetSizerAndFit(topsizer); - } - - bool disable_version_check() const { return cbox->GetValue(); } -}; - struct Update { fs::path source; fs::path target; Version version; - Update(fs::path &&source, const fs::path &target, const Version &version) : - source(source), + Update(fs::path &&source, fs::path &&target, const Version &version) : + source(std::move(source)), target(std::move(target)), version(version) {} - Update(fs::path &&source, fs::path &&target) : - source(source), - target(std::move(target)) - {} + std::string name() const { return source.stem().string(); } - std::string name() { return source.stem().string(); } - - friend std::ostream& operator<<(std::ostream& os , const Update &update) { - os << "Update(" << update.source.string() << " -> " << update.target.string() << ')'; + friend std::ostream& operator<<(std::ostream& os , const Update &self) { + os << "Update(" << self.source.string() << " -> " << self.target.string() << ')'; return os; } }; -typedef std::vector Updates; +struct Incompat +{ + fs::path bundle; + Version version; + + Incompat(fs::path &&bundle, const Version &version) : + bundle(std::move(bundle)), + version(version) + {} + + std::string name() const { return bundle.stem().string(); } + + friend std::ostream& operator<<(std::ostream& os , const Incompat &self) { + os << "Incompat(" << self.bundle.string() << ')'; + return os; + } +}; + +struct Updates +{ + std::vector incompats; + std::vector updates; +}; struct PresetUpdater::priv @@ -206,7 +160,7 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%") % url % http_status - % body; + % error; }) .on_complete([&](std::string body, unsigned http_status) { fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); @@ -248,7 +202,7 @@ void PresetUpdater::priv::sync_version() const BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%") % version_check_url % http_status - % body; + % error; }) .on_complete([&](std::string body, unsigned http_status) { boost::trim(body); @@ -354,7 +308,7 @@ Updates PresetUpdater::priv::get_config_updates() const BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates..."; for (const auto idx : index_db) { - const auto bundle_path = vendor_path / (idx.vendor() + ".ini"); + auto bundle_path = vendor_path / (idx.vendor() + ".ini"); if (! fs::exists(bundle_path)) { BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor(); @@ -377,14 +331,12 @@ Updates PresetUpdater::priv::get_config_updates() const BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%, version cached: %3%") % vp.name - % recommended->config_version.to_string() - % ver_current->config_version.to_string(); + % ver_current->config_version.to_string() + % recommended->config_version.to_string(); if (! ver_current->is_current_slic3r_supported()) { BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); - - // TODO: Downgrade situation - + updates.incompats.emplace_back(std::move(bundle_path), *ver_current); } else if (recommended->config_version > ver_current->config_version) { // Config bundle update situation @@ -406,7 +358,7 @@ Updates PresetUpdater::priv::get_config_updates() const const auto cached_vp = VendorProfile::from_ini(path_in_cache, false); if (cached_vp.config_version == recommended->config_version) { - updates.emplace_back(std::move(path_in_cache), bundle_path, *recommended); + updates.updates.emplace_back(std::move(path_in_cache), std::move(bundle_path), *recommended); } } } @@ -416,28 +368,43 @@ Updates PresetUpdater::priv::get_config_updates() const void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const { - BOOST_LOG_TRIVIAL(info) << boost::format("Performing %1% updates") % updates.size(); + if (updates.incompats.size() > 0) { + if (snapshot) { + BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; + SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_DOWNGRADE); + } - if (snapshot) { - BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; - SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE); + BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% incompatible bundles") % updates.incompats.size(); + + for (const auto &incompat : updates.incompats) { + BOOST_LOG_TRIVIAL(info) << '\t' << incompat; + fs::remove(incompat.bundle); + } } + else if (updates.updates.size() > 0) { + if (snapshot) { + BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; + SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE); + } - for (const auto &update : updates) { - BOOST_LOG_TRIVIAL(info) << '\t' << update; + BOOST_LOG_TRIVIAL(info) << boost::format("Performing %1% updates") % updates.updates.size(); - fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); + for (const auto &update : updates.updates) { + BOOST_LOG_TRIVIAL(info) << '\t' << update; - PresetBundle bundle; - bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); - auto preset_remover = [](const Preset &preset) { - fs::remove(preset.file); - }; + PresetBundle bundle; + bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); - for (const auto &preset : bundle.prints) { preset_remover(preset); } - for (const auto &preset : bundle.filaments) { preset_remover(preset); } - for (const auto &preset : bundle.printers) { preset_remover(preset); } + auto preset_remover = [](const Preset &preset) { + fs::remove(preset.file); + }; + + for (const auto &preset : bundle.prints) { preset_remover(preset); } + for (const auto &preset : bundle.filaments) { preset_remover(preset); } + for (const auto &preset : bundle.printers) { preset_remover(preset); } + } } } @@ -497,7 +464,7 @@ void PresetUpdater::slic3r_update_notify() if (ver_online) { // Only display the notification if the version available online is newer AND if we haven't seen it before if (*ver_online > *ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) { - UpdateNotification notification(*ver_slic3r, *ver_online); + GUI::MsgUpdateSlic3r notification(*ver_slic3r, *ver_online); notification.ShowModal(); if (notification.disable_version_check()) { app_config->set("version_check", "0"); @@ -508,32 +475,55 @@ void PresetUpdater::slic3r_update_notify() } } -void PresetUpdater::config_update() const +bool PresetUpdater::config_update() const { - if (! p->enabled_config_update) { return; } + if (! p->enabled_config_update) { return true; } auto updates = p->get_config_updates(); - if (updates.size() > 0) { - BOOST_LOG_TRIVIAL(info) << boost::format("Update of %1% bundles available. Asking for confirmation ...") % updates.size(); + if (updates.incompats.size() > 0) { + BOOST_LOG_TRIVIAL(info) << boost::format("%1% bundles incompatible. Asking for action...") % updates.incompats.size(); - const auto msg = _(L("Configuration update is available. Would you like to install it?")); - - auto ext_msg = _(L( - "Note that a full configuration snapshot will be created first. It can then be restored at any time " - "should there be a problem with the new version.\n\n" - "Updated configuration bundles:\n" - )); - - for (const auto &update : updates) { - ext_msg += update.target.stem().string() + " " + update.version.config_version.to_string(); - if (! update.version.comment.empty()) { - ext_msg += std::string(" (") + update.version.comment + ")"; - } - ext_msg += "\n"; + std::unordered_map incompats_map; + for (const auto &incompat : updates.incompats) { + auto vendor = incompat.name(); + auto restrictions = wxString::Format(_(L("requires min. %s and max. %s")), + incompat.version.min_slic3r_version.to_string(), + incompat.version.max_slic3r_version.to_string() + ); + incompats_map.emplace(std::move(vendor), std::move(restrictions)); } - wxMessageDialog dlg(NULL, msg, _(L("Configuration update")), wxYES_NO|wxCENTRE); - dlg.SetExtendedMessage(ext_msg); + GUI::MsgDataIncompatible dlg(std::move(incompats_map)); + const auto res = dlg.ShowModal(); + if (res == wxID_REPLACE) { + BOOST_LOG_TRIVIAL(info) << "User wants to re-configure..."; + p->perform_updates(std::move(updates)); + GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT); + if (wizard.run(GUI::get_preset_bundle(), this)) { + p->had_config_update = true; + } else { + return false; + } + } else { + BOOST_LOG_TRIVIAL(info) << "User wants to exit Slic3r, bye..."; + return false; + } + } + else if (updates.updates.size() > 0) { + BOOST_LOG_TRIVIAL(info) << boost::format("Update of %1% bundles available. Asking for confirmation ...") % updates.updates.size(); + + std::unordered_map updates_map; + for (const auto &update : updates.updates) { + auto vendor = update.name(); + auto ver_str = update.version.config_version.to_string(); + if (! update.version.comment.empty()) { + ver_str += std::string(" (") + update.version.comment + ")"; + } + updates_map.emplace(std::move(vendor), std::move(ver_str)); + } + + GUI::MsgUpdateConfig dlg(std::move(updates_map)); + const auto res = dlg.ShowModal(); if (res == wxID_YES) { BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; @@ -546,9 +536,11 @@ void PresetUpdater::config_update() const } else { BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; } + + return true; } -void PresetUpdater::install_bundles_rsrc(std::vector bundles, bool snapshot) +void PresetUpdater::install_bundles_rsrc(std::vector bundles, bool snapshot) const { Updates updates; @@ -557,7 +549,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector bundles, bool for (const auto &bundle : bundles) { auto path_in_rsrc = p->rsrc_path / bundle; auto path_in_vendors = p->vendor_path / bundle; - updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors)); + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version()); } p->perform_updates(std::move(updates), snapshot); diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp index 287f20652f..6a53cca816 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.hpp +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -27,10 +27,11 @@ public: void slic3r_update_notify(); // If updating is enabled, check if updates are available in cache, if so, ask about installation. - void config_update() const; + // A false return value implies Slic3r should exit due to incompatibility of configuration. + bool config_update() const; // "Update" a list of bundles from resources (behaves like an online update). - void install_bundles_rsrc(std::vector bundles, bool snapshot = true); + void install_bundles_rsrc(std::vector bundles, bool snapshot = true) const; private: struct priv; std::unique_ptr p; diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 0d9f0b62e7..ca90c54f2d 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -60,10 +60,10 @@ void set_app_config(AppConfig *app_config) bool check_unsaved_changes() %code%{ RETVAL=Slic3r::GUI::check_unsaved_changes(); %}; -void config_wizard_startup(int app_config_exists) +bool config_wizard_startup(int app_config_exists) %code%{ try { - Slic3r::GUI::config_wizard_startup(app_config_exists != 0); + RETVAL=Slic3r::GUI::config_wizard_startup(app_config_exists != 0); } catch (std::exception& e) { croak("%s\n", e.what()); } diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp index 53c3aa9855..dc874acabe 100644 --- a/xs/xsp/Utils_PresetUpdater.xsp +++ b/xs/xsp/Utils_PresetUpdater.xsp @@ -9,5 +9,5 @@ PresetUpdater(int version_online_event); void sync(PresetBundle* preset_bundle); void slic3r_update_notify(); - void config_update(); + bool config_update(); }; From 60f62a6463e2eaa550a2e42139906e1e87007c7b Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Apr 2018 18:29:36 +0200 Subject: [PATCH 077/145] Fix text in UpdateDialogs --- xs/src/slic3r/GUI/UpdateDialogs.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp index 076554e237..5d198661f8 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.cpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -156,7 +156,9 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_mapWrap(CONTENT_WIDTH); content_sizer->Add(text); From 0feb4d823f629144e8e74645a7e032f03ab2c7fc Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Apr 2018 18:40:06 +0200 Subject: [PATCH 078/145] PresetUpdater: Fix string type --- xs/src/slic3r/GUI/UpdateDialogs.cpp | 2 +- xs/src/slic3r/GUI/UpdateDialogs.hpp | 2 +- xs/src/slic3r/Utils/PresetUpdater.cpp | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp index 5d198661f8..9a689e03df 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.cpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -150,7 +150,7 @@ MsgUpdateConfig::~MsgUpdateConfig() {} // MsgDataIncompatible -MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map &incompats) : +MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map &incompats) : MsgDialog(_(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png"))), wxID_NONE) { auto *text = new wxStaticText(this, wxID_ANY, _(L( diff --git a/xs/src/slic3r/GUI/UpdateDialogs.hpp b/xs/src/slic3r/GUI/UpdateDialogs.hpp index ae949b8dd1..f12fd3333a 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.hpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.hpp @@ -77,7 +77,7 @@ class MsgDataIncompatible : public MsgDialog { public: // incompats is a map of "vendor name" -> "version restrictions" - MsgDataIncompatible(const std::unordered_map &incompats); + MsgDataIncompatible(const std::unordered_map &incompats); MsgDataIncompatible(MsgDataIncompatible &&) = delete; MsgDataIncompatible(const MsgDataIncompatible &) = delete; MsgDataIncompatible &operator=(MsgDataIncompatible &&) = delete; diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index ec152df63e..54841a3708 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -483,14 +483,14 @@ bool PresetUpdater::config_update() const if (updates.incompats.size() > 0) { BOOST_LOG_TRIVIAL(info) << boost::format("%1% bundles incompatible. Asking for action...") % updates.incompats.size(); - std::unordered_map incompats_map; + std::unordered_map incompats_map; for (const auto &incompat : updates.incompats) { auto vendor = incompat.name(); auto restrictions = wxString::Format(_(L("requires min. %s and max. %s")), incompat.version.min_slic3r_version.to_string(), incompat.version.max_slic3r_version.to_string() ); - incompats_map.emplace(std::move(vendor), std::move(restrictions)); + incompats_map.emplace(std::make_pair(std::move(vendor), std::move(restrictions))); } GUI::MsgDataIncompatible dlg(std::move(incompats_map)); @@ -519,7 +519,7 @@ bool PresetUpdater::config_update() const if (! update.version.comment.empty()) { ver_str += std::string(" (") + update.version.comment + ")"; } - updates_map.emplace(std::move(vendor), std::move(ver_str)); + updates_map.emplace(std::make_pair(std::move(vendor), std::move(ver_str))); } GUI::MsgUpdateConfig dlg(std::move(updates_map)); From 6c627be4c18cc747df9040ed3bceb2bc14823f0b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 25 Apr 2018 10:37:31 +0200 Subject: [PATCH 079/145] New cooling logic to equalize extrusion velocity. The old behavior caused bad outer surface print quality on Prusa i3 MK3 --- xs/src/libslic3r/GCode/CoolingBuffer.cpp | 479 +++++++++++++++-------- 1 file changed, 306 insertions(+), 173 deletions(-) diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp index cd2baeffbd..121cbb0175 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -30,6 +30,174 @@ void CoolingBuffer::reset() m_current_pos[4] = float(m_gcodegen.config().travel_speed.value); } +struct CoolingLine +{ + enum Type { + TYPE_SET_TOOL = 1 << 0, + TYPE_EXTRUDE_END = 1 << 1, + TYPE_BRIDGE_FAN_START = 1 << 2, + TYPE_BRIDGE_FAN_END = 1 << 3, + TYPE_G0 = 1 << 4, + TYPE_G1 = 1 << 5, + TYPE_ADJUSTABLE = 1 << 6, + TYPE_EXTERNAL_PERIMETER = 1 << 7, + // The line sets a feedrate. + TYPE_HAS_F = 1 << 8, + TYPE_WIPE = 1 << 9, + TYPE_G4 = 1 << 10, + TYPE_G92 = 1 << 11, + }; + + CoolingLine(unsigned int type, size_t line_start, size_t line_end) : + type(type), line_start(line_start), line_end(line_end), + length(0.f), time(0.f), time_max(0.f), slowdown(false) {} + + bool adjustable(bool slowdown_external_perimeters) const { + return (this->type & TYPE_ADJUSTABLE) && + (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && + this->time < this->time_max; + } + + bool adjustable() const { + return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max; + } + + size_t type; + // Start of this line at the G-code snippet. + size_t line_start; + // End of this line at the G-code snippet. + size_t line_end; + // XY Euclidian length of this segment. + float length; + // Current feedrate, possibly adjusted. + float feedrate; + // Current duration of this segment. + float time; + // Maximum duration of this segment. + float time_max; + // If marked with the "slowdown" flag, the line has been slowed down. + bool slowdown; +}; + +// Calculate the required per extruder time stretches. +struct PerExtruderAdjustments +{ + // Calculate the total elapsed time per this extruder, adjusted for the slowdown. + float elapsed_time_total() { + float time_total = 0.f; + for (const CoolingLine &line : lines) + time_total += line.time; + return time_total; + } + // Calculate the maximum time when slowing down. + float maximum_time(bool slowdown_external_perimeters) { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (line.adjustable(slowdown_external_perimeters)) { + if (line.time_max == FLT_MAX) + return FLT_MAX; + else + time_total += line.time_max; + } else + time_total += line.time; + return time_total; + } + // Calculate the non-adjustable part of the total time. + float non_adjustable_time(bool slowdown_external_perimeters) { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (! line.adjustable(slowdown_external_perimeters)) + time_total += line.time; + return time_total; + } + float slow_down_maximum(bool slowdown_external_perimeters) { + float time_total = 0.f; + for (CoolingLine &line : lines) { + if (line.adjustable(slowdown_external_perimeters)) { + assert(line.time_max >= 0.f && line.time_max < FLT_MAX); + line.slowdown = true; + line.time = line.time_max; + line.feedrate = line.length / line.time; + } + time_total += line.time; + } + return time_total; + } + float slow_down_proportional(float factor, bool slowdown_external_perimeters) { + assert(factor >= 1.f); + float time_total = 0.f; + for (CoolingLine &line : lines) { + if (line.adjustable(slowdown_external_perimeters)) { + line.slowdown = true; + line.time = std::min(line.time_max, line.time * factor); + line.feedrate = line.length / line.time; + } + time_total += line.time; + } + return time_total; + } + + bool operator<(const PerExtruderAdjustments &rhs) const { return this->extruder_id < rhs.extruder_id; } + + // Sort the lines, adjustable first, higher feedrate first. + void sort_lines_by_decreasing_feedrate() { + std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) { + bool adj1 = l1.adjustable(); + bool adj2 = l2.adjustable(); + return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1; + }); + for (n_lines_adjustable = 0; n_lines_adjustable < lines.size(); ++ n_lines_adjustable) + if ((this->lines[n_lines_adjustable].type & CoolingLine::TYPE_ADJUSTABLE) == 0) + break; + time_non_adjustable = 0.f; + for (size_t i = n_lines_adjustable; i < lines.size(); ++ i) + time_non_adjustable += lines[i].time; + } + + // Calculate the maximum time when slowing down. + float time_stretch_when_slowing_down_to(float min_feedrate) { + float time_stretch = 0.f; + if (this->min_print_speed < min_feedrate + EPSILON) { + for (size_t i = 0; i < n_lines_adjustable; ++ i) { + const CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) + time_stretch += line.time * (line.feedrate / min_feedrate - 1.f); + } + } + return time_stretch; + } + + void slow_down_to(float min_feedrate) { + if (this->min_print_speed < min_feedrate + EPSILON) { + for (size_t i = 0; i < n_lines_adjustable; ++ i) { + CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) { + line.time *= std::max(1.f, line.feedrate / min_feedrate); + line.feedrate = min_feedrate; + line.slowdown = true; + } + } + } + } + + // Extruder, for which the G-code will be adjusted. + unsigned int extruder_id = 0; + // Minimum print speed allowed for this extruder. + float min_print_speed = 0.f; + + // Parsed lines. + std::vector lines; + // The following two values are set by sort_lines_by_decreasing_feedrate(): + // Number of adjustable lines, at the start of lines. + size_t n_lines_adjustable = 0; + // Non-adjustable time of lines starting with n_lines_adjustable. + float time_non_adjustable = 0; + + // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable. + size_t idx_line_begin = 0; + size_t idx_line_end = 0; +}; + #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id) @@ -38,125 +206,23 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ const std::vector &extruders = m_gcodegen.writer().extruders(); const size_t num_extruders = extruders.size(); - // Calculate the required per extruder time stretches. - struct Adjustment { - Adjustment(unsigned int extruder_id = 0) : extruder_id(extruder_id) {} - // Calculate the total elapsed time per this extruder, adjusted for the slowdown. - float elapsed_time_total() { - float time_total = 0.f; - for (const Line &line : lines) - time_total += line.time; - return time_total; - } - // Calculate the maximum time when slowing down. - float maximum_time(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (const Line &line : lines) - if (line.adjustable(slowdown_external_perimeters)) { - if (line.time_max == FLT_MAX) - return FLT_MAX; - else - time_total += line.time_max; - } else - time_total += line.time; - return time_total; - } - // Calculate the non-adjustable part of the total time. - float non_adjustable_time(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (const Line &line : lines) - if (! line.adjustable(slowdown_external_perimeters)) - time_total += line.time; - return time_total; - } - float slow_down_maximum(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (Line &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - assert(line.time_max >= 0.f && line.time_max < FLT_MAX); - line.slowdown = true; - line.time = line.time_max; - } - time_total += line.time; - } - return time_total; - } - float slow_down_proportional(float factor, bool slowdown_external_perimeters) { - assert(factor >= 1.f); - float time_total = 0.f; - for (Line &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - line.slowdown = true; - line.time = std::min(line.time_max, line.time * factor); - } - time_total += line.time; - } - return time_total; - } - - bool operator<(const Adjustment &rhs) const { return this->extruder_id < rhs.extruder_id; } - - struct Line - { - enum Type { - TYPE_SET_TOOL = 1 << 0, - TYPE_EXTRUDE_END = 1 << 1, - TYPE_BRIDGE_FAN_START = 1 << 2, - TYPE_BRIDGE_FAN_END = 1 << 3, - TYPE_G0 = 1 << 4, - TYPE_G1 = 1 << 5, - TYPE_ADJUSTABLE = 1 << 6, - TYPE_EXTERNAL_PERIMETER = 1 << 7, - // The line sets a feedrate. - TYPE_HAS_F = 1 << 8, - TYPE_WIPE = 1 << 9, - TYPE_G4 = 1 << 10, - TYPE_G92 = 1 << 11, - }; - - Line(unsigned int type, size_t line_start, size_t line_end) : - type(type), line_start(line_start), line_end(line_end), - length(0.f), time(0.f), time_max(0.f), slowdown(false) {} - - bool adjustable(bool slowdown_external_perimeters) const { - return (this->type & TYPE_ADJUSTABLE) && - (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && - this->time < this->time_max; - } - - size_t type; - // Start of this line at the G-code snippet. - size_t line_start; - // End of this line at the G-code snippet. - size_t line_end; - // XY Euclidian length of this segment. - float length; - // Current duration of this segment. - float time; - // Maximum duration of this segment. - float time_max; - // If marked with the "slowdown" flag, the line has been slowed down. - bool slowdown; - }; - - // Extruder, for which the G-code will be adjusted. - unsigned int extruder_id; - // Parsed lines. - std::vector lines; - }; - std::vector adjustments(num_extruders, Adjustment()); - for (size_t i = 0; i < num_extruders; ++ i) - adjustments[i].extruder_id = extruders[i].id(); - const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); + std::vector per_extruder_adjustments(num_extruders); + std::vector map_extruder_to_per_extruder_adjustment(num_extruders, 0); + for (size_t i = 0; i < num_extruders; ++ i) { + unsigned int extruder_id = extruders[i].id(); + per_extruder_adjustments[i].extruder_id = extruder_id; + per_extruder_adjustments[i].min_print_speed = config.min_print_speed.get_at(i); + map_extruder_to_per_extruder_adjustment[extruder_id] = i; + } + const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); // Parse the layer G-code for the moves, which could be adjusted. { - float min_print_speed = float(EXTRUDER_CONFIG(min_print_speed)); - auto adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder)); + PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[m_current_extruder]]; unsigned int initial_extruder = m_current_extruder; const char *line_start = gcode.c_str(); const char *line_end = line_start; const char extrusion_axis = config.get_extrusion_axis()[0]; - // Index of an existing Adjustment::Line of the current adjustment, which holds the feedrate setting command + // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); for (; *line_start != 0; line_start = line_end) { @@ -164,16 +230,16 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ ++ line_end; // sline will not contain the trailing '\n'. std::string sline(line_start, line_end); - // Adjustment::Line will contain the trailing '\n'. + // CoolingLine will contain the trailing '\n'. if (*line_end == '\n') ++ line_end; - Adjustment::Line line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); + CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); if (boost::starts_with(sline, "G0 ")) - line.type = Adjustment::Line::TYPE_G0; + line.type = CoolingLine::TYPE_G0; else if (boost::starts_with(sline, "G1 ")) - line.type = Adjustment::Line::TYPE_G1; + line.type = CoolingLine::TYPE_G1; else if (boost::starts_with(sline, "G92 ")) - line.type = Adjustment::Line::TYPE_G92; + line.type = CoolingLine::TYPE_G92; if (line.type) { // G0, G1 or G92 // Parse the G-code line. @@ -192,9 +258,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ if (axis == 4) { // Convert mm/min to mm/sec. new_pos[4] /= 60.f; - if ((line.type & Adjustment::Line::TYPE_G92) == 0) + if ((line.type & CoolingLine::TYPE_G92) == 0) // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. - line.type |= Adjustment::Line::TYPE_HAS_F; + line.type |= CoolingLine::TYPE_HAS_F; } } // Skip this word. @@ -203,14 +269,14 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); bool wipe = boost::contains(sline, ";_WIPE"); if (external_perimeter) - line.type |= Adjustment::Line::TYPE_EXTERNAL_PERIMETER; + line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; if (wipe) - line.type |= Adjustment::Line::TYPE_WIPE; + line.type |= CoolingLine::TYPE_WIPE; if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { - line.type |= Adjustment::Line::TYPE_ADJUSTABLE; + line.type |= CoolingLine::TYPE_ADJUSTABLE; active_speed_modifier = adjustment->lines.size(); } - if ((line.type & Adjustment::Line::TYPE_G92) == 0) { + if ((line.type & CoolingLine::TYPE_G92) == 0) { // G0 or G1. Calculate the duration. if (config.use_relative_e_distances.value) // Reset extruder accumulator. @@ -227,15 +293,17 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ // Movement in the extruder axis. line.length = std::abs(dif[3]); } - if (line.length > 0) - line.time = line.length / new_pos[4]; // current F + if (line.length > 0) { + line.feedrate = new_pos[4]; // current F + line.time = line.length / line.feedrate; + } line.time_max = line.time; - if ((line.type & Adjustment::Line::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) - line.time_max = (min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / min_print_speed); - if (active_speed_modifier < adjustment->lines.size() && (line.type & Adjustment::Line::TYPE_G1)) { + if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) + line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed); + if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) { // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. - assert((line.type & Adjustment::Line::TYPE_HAS_F) == 0); - Adjustment::Line &sm = adjustment->lines[active_speed_modifier]; + assert((line.type & CoolingLine::TYPE_HAS_F) == 0); + CoolingLine &sm = adjustment->lines[active_speed_modifier]; sm.length += line.length; sm.time += line.time; if (sm.time_max != FLT_MAX) { @@ -250,24 +318,23 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ } m_current_pos = std::move(new_pos); } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { - line.type = Adjustment::Line::TYPE_EXTRUDE_END; + line.type = CoolingLine::TYPE_EXTRUDE_END; active_speed_modifier = size_t(-1); } else if (boost::starts_with(sline, toolchange_prefix)) { // Switch the tool. - line.type = Adjustment::Line::TYPE_SET_TOOL; + line.type = CoolingLine::TYPE_SET_TOOL; unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; - min_print_speed = float(EXTRUDER_CONFIG(min_print_speed)); - adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder)); + adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[m_current_extruder]]; } } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) { - line.type = Adjustment::Line::TYPE_BRIDGE_FAN_START; + line.type = CoolingLine::TYPE_BRIDGE_FAN_START; } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) { - line.type = Adjustment::Line::TYPE_BRIDGE_FAN_END; + line.type = CoolingLine::TYPE_BRIDGE_FAN_END; } else if (boost::starts_with(sline, "G4 ")) { // Parse the wait time. - line.type = Adjustment::Line::TYPE_G4; + line.type = CoolingLine::TYPE_G4; size_t pos_S = sline.find('S', 3); size_t pos_P = sline.find('P', 3); line.time = line.time_max = float( @@ -281,18 +348,19 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ } // Sort the extruders by the increasing slowdown_below_layer_time. - std::vector by_slowdown_layer_time; - by_slowdown_layer_time.reserve(num_extruders); + std::vector extruder_by_slowdown_time; + extruder_by_slowdown_time.reserve(num_extruders); // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time). // Collect total print time of non-adjustable extruders. float elapsed_time_total_non_adjustable = 0.f; for (size_t i = 0; i < num_extruders; ++ i) { - if (config.cooling.get_at(extruders[i].id())) - by_slowdown_layer_time.emplace_back(i); - else - elapsed_time_total_non_adjustable += adjustments[i].elapsed_time_total(); + if (config.cooling.get_at(extruders[i].id())) { + extruder_by_slowdown_time.emplace_back(i); + per_extruder_adjustments[i].sort_lines_by_decreasing_feedrate(); + } else + elapsed_time_total_non_adjustable += per_extruder_adjustments[i].elapsed_time_total(); } - std::sort(by_slowdown_layer_time.begin(), by_slowdown_layer_time.end(), + std::sort(extruder_by_slowdown_time.begin(), extruder_by_slowdown_time.end(), [&config, &extruders](const size_t idx1, const size_t idx2){ return config.slowdown_below_layer_time.get_at(extruders[idx1].id()) < config.slowdown_below_layer_time.get_at(extruders[idx2].id()); @@ -303,18 +371,18 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ { // Elapsed time for the already adjusted extruders. float elapsed_time_total0 = elapsed_time_total_non_adjustable; - for (size_t i_by_slowdown_layer_time = 0; i_by_slowdown_layer_time < by_slowdown_layer_time.size(); ++ i_by_slowdown_layer_time) { - // Idx in adjustments. - size_t idx = by_slowdown_layer_time[i_by_slowdown_layer_time]; - // Macro to sum or adjust all sections starting with i_by_slowdown_layer_time. + for (size_t i_extruder_by_slowdown_time = 0; i_extruder_by_slowdown_time < extruder_by_slowdown_time.size(); ++ i_extruder_by_slowdown_time) { + // Idx in per_extruder_adjustments. + size_t idx = extruder_by_slowdown_time[i_extruder_by_slowdown_time]; + // Macro to sum or adjust all sections starting with i_extruder_by_slowdown_time. #define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \ ACCUMULATOR = elapsed_time_total0;\ - for (size_t j = i_by_slowdown_layer_time; j < by_slowdown_layer_time.size(); ++ j) \ - ACCUMULATOR += adjustments[by_slowdown_layer_time[j]].ACTION + for (size_t j = i_extruder_by_slowdown_time; j < extruder_by_slowdown_time.size(); ++ j) \ + ACCUMULATOR += per_extruder_adjustments[extruder_by_slowdown_time[j]].ACTION // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. float total; FORALL_UNPROCESSED(total, elapsed_time_total()); - float slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(adjustments[idx].extruder_id)) * 1.001f; + float slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(per_extruder_adjustments[idx].extruder_id)) * 1.001f; if (total > slowdown_below_layer_time) { // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. } else { @@ -323,8 +391,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ float max_time; FORALL_UNPROCESSED(max_time, maximum_time(true)); if (max_time > slowdown_below_layer_time) { - // By slowing every possible movement, the layer time could be reached. Now decide - // whether the external perimeters shall be slowed down as well. + // By slowing every possible movement, the layer time could be reached. +#if 0 + // Now decide, whether the external perimeters shall be slowed down as well. float max_time_nep; FORALL_UNPROCESSED(max_time_nep, maximum_time(false)); if (max_time_nep > slowdown_below_layer_time) { @@ -355,34 +424,98 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ break; } } +#else + // Slow down. Try to equalize the feedrates. + std::vector by_min_print_speed; + by_min_print_speed.reserve(extruder_by_slowdown_time.size() - i_extruder_by_slowdown_time); + for (size_t j = i_extruder_by_slowdown_time; j < extruder_by_slowdown_time.size(); ++ j) + by_min_print_speed.emplace_back(&per_extruder_adjustments[extruder_by_slowdown_time[j]]); + // Find the next highest adjustable feedrate among the extruders. + float feedrate = 0; + for (PerExtruderAdjustments *adj : by_min_print_speed) + if (adj->idx_line_begin < adj->n_lines_adjustable && adj->lines[adj->idx_line_begin].feedrate > feedrate) + feedrate = adj->lines[adj->idx_line_begin].feedrate; + if (feedrate == 0) + // No adjustable line is left. + break; + // Sort by min_print_speed, maximum speed first. + std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), + [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; }); + // Slow down, fast moves first. + for (;;) { + // For each extruder, find the span of lines with a feedrate close to feedrate. + for (PerExtruderAdjustments *adj : by_min_print_speed) { + for (adj->idx_line_end = adj->idx_line_begin; + adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON; + ++ adj->idx_line_end) ; + } + // Find the next highest adjustable feedrate among the extruders. + float feedrate_next = 0.f; + for (PerExtruderAdjustments *adj : by_min_print_speed) + if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next) + feedrate_next = adj->lines[adj->idx_line_end].feedrate; + // Slow down, limited by max(feedrate_next, min_print_speed). + for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) { + float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed); + float time_stretch = slowdown_below_layer_time - total; + float time_stretch_max = 0.f; + std::pair time_stretched(0.f, 0.f); + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + time_stretch_max += (*it)->time_stretch_when_slowing_down_to(feedrate_limit); + bool done = false; + if (time_stretch_max > time_stretch) { + feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max; + done = true; + } + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + (*it)->slow_down_to(feedrate_limit); + if (done) { + // Break from two levels of loops. + feedrate_next = 0.f; + break; + } + // Skip the other extruders with nearly the same min_print_speed, as they have been processed already. + auto next = adj; + for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next); + adj = next; + } + if (feedrate_next == 0.f) + // There are no other extrusions available for slow down. + break; + for (PerExtruderAdjustments *adj : by_min_print_speed) { + adj->idx_line_begin = adj->idx_line_end; + feedrate = feedrate_next; + } + } +#endif } else { // Slow down to maximum possible. FORALL_UNPROCESSED(total, slow_down_maximum(true)); } } #undef FORALL_UNPROCESSED - // Sum the final elapsed time for all extruders up to i_by_slowdown_layer_time. - if (i_by_slowdown_layer_time + 1 == by_slowdown_layer_time.size()) + // Sum the final elapsed time for all extruders up to i_extruder_by_slowdown_time. + if (i_extruder_by_slowdown_time + 1 == extruder_by_slowdown_time.size()) // Optimization for single extruder prints. elapsed_time_total0 = total; else - elapsed_time_total0 += adjustments[idx].elapsed_time_total(); + elapsed_time_total0 += per_extruder_adjustments[idx].elapsed_time_total(); } elapsed_time_total = elapsed_time_total0; } // Transform the G-code. // First sort the adjustment lines by their position in the source G-code. - std::vector lines; + std::vector lines; { size_t n_lines = 0; - for (const Adjustment &adj : adjustments) + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) n_lines += adj.lines.size(); lines.reserve(n_lines); - for (const Adjustment &adj : adjustments) - for (const Adjustment::Line &line : adj.lines) + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) + for (const CoolingLine &line : adj.lines) lines.emplace_back(&line); - std::sort(lines.begin(), lines.end(), [](const Adjustment::Line *ln1, const Adjustment::Line *ln2) { return ln1->line_start < ln2->line_start; } ); + std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } ); } // Second generate the adjusted G-code. std::string new_gcode; @@ -425,27 +558,27 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ const char *pos = gcode.c_str(); int current_feedrate = 0; - for (const Adjustment::Line *line : lines) { + for (const CoolingLine *line : lines) { const char *line_start = gcode.c_str() + line->line_start; const char *line_end = gcode.c_str() + line->line_end; if (line_start > pos) new_gcode.append(pos, line_start - pos); - if (line->type & Adjustment::Line::TYPE_SET_TOOL) { + if (line->type & CoolingLine::TYPE_SET_TOOL) { unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size()); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(); } new_gcode.append(line_start, line_end - line_start); - } else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_START) { + } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) { if (bridge_fan_control) new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true); - } else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_END) { + } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) { if (bridge_fan_control) new_gcode += m_gcodegen.writer().set_fan(fan_speed, true); - } else if (line->type & Adjustment::Line::TYPE_EXTRUDE_END) { + } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { // Just remove this comment. - } else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE | Adjustment::Line::TYPE_HAS_F)) { + } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { // Find the start of a comment, or roll to the end of line. const char *end = line_start; for (; end < line_end && *end != ';'; ++ end); @@ -456,14 +589,14 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ assert(fpos != nullptr); if (line->slowdown) { modify = true; - new_feedrate = int(floor(60. * (line->length / line->time) + 0.5)); + new_feedrate = int(floor(60. * line->feedrate + 0.5)); } else { new_feedrate = atoi(fpos); if (new_feedrate != current_feedrate) { // Append the line without the comment. new_gcode.append(line_start, end - line_start); current_feedrate = new_feedrate; - } else if ((line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) || line->length == 0.) { + } else if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) { // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment. end = line_end; } else { @@ -497,13 +630,13 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ } // Process the rest of the line. if (end < line_end) { - if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) { + if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) { // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" std::string comment(end, line_end); boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); - if (line->type & Adjustment::Line::TYPE_EXTERNAL_PERIMETER) + if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER) boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); - if (line->type & Adjustment::Line::TYPE_WIPE) + if (line->type & CoolingLine::TYPE_WIPE) boost::replace_all(comment, ";_WIPE", ""); new_gcode += comment; } else { From 3cd7987af4b6cbb6910eb612fa96775b34c11a0b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Apr 2018 10:59:06 +0200 Subject: [PATCH 080/145] Fixed layer heights profile invalidated when loading model from amf file --- lib/Slic3r/GUI/Plater.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ce78eab8ec..f41394ccc7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -663,6 +663,9 @@ sub load_files { Slic3r::GUI::show_error($self, $@) if $@; $_->load_current_preset for (values %{$self->GetFrame->{options_tabs}}); wxTheApp->{app_config}->update_config_dir(dirname($input_file)); + # forces the update of the config here, or it will invalidate the imported layer heights profile if done using the timer + # and if the config contains a "layer_height" different from the current defined one + $self->async_apply_config; } else { From cfac6c0ebb98e0a6630ade4a81c90dd6c0b64c0d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 25 Apr 2018 11:10:34 +0200 Subject: [PATCH 081/145] Aligned printer settings on Plater tab --- lib/Slic3r/GUI/Plater.pm | 2 +- xs/src/slic3r/GUI/GUI.cpp | 11 +++++------ xs/src/slic3r/GUI/OptionsGroup.cpp | 8 ++++---- xs/src/slic3r/GUI/OptionsGroup.hpp | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 893cc366bb..53176ccfe1 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -486,7 +486,7 @@ sub new { my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; - $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 10) if defined $frequently_changed_parameters_sizer; + $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); $right_sizer->Add($self->{list}, 1, wxEXPAND, 5); $right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index c4f07935c5..e85112008f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -640,8 +640,8 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl { DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; m_optgroup = std::make_shared(parent, "", config); - const wxArrayInt& ar = preset_sizer->GetColWidths(); - m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front(); +// const wxArrayInt& ar = preset_sizer->GetColWidths(); +// m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front(); // doesn't work m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ TabPrint* tab_print = nullptr; for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { @@ -695,10 +695,9 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl tab_print->update_dirty(); }; - const int width = 250; Option option = m_optgroup->get_option("fill_density"); option.opt.sidetext = ""; - option.opt.width = width; + option.opt.full_width = true; m_optgroup->append_single_option_line(option); ConfigOptionDef def; @@ -717,7 +716,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl "Everywhere"; def.default_value = new ConfigOptionStrings { selection }; option = Option(def, "support"); - option.opt.width = width; + option.opt.full_width = true; m_optgroup->append_single_option_line(option); m_brim_width = config->opt_float("brim_width"); @@ -756,7 +755,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl - sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxBottom, 1); + sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); } ConfigOptionsGroup* get_optgroup() diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 265497e9c0..1e78a600aa 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -137,11 +137,11 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // Build a label if we have it wxStaticText* label=nullptr; if (label_width != 0) { - label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"), - wxDefaultPosition, wxSize(label_width, -1)); + label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ": "), + wxDefaultPosition, staticbox ? wxSize(label_width, -1) : wxDefaultSize); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug - grid_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL,0); + grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT) | wxALIGN_CENTER_VERTICAL, 0); if (line.label_tooltip.compare("") != 0) label->SetToolTip(line.label_tooltip); } @@ -166,7 +166,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); if (is_window_field(field)) - sizer->Add(field->getWindow(), 0, (option.opt.full_width ? wxEXPAND : 0) | + sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) | wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, wxOSX ? 0 : 2); if (is_sizer_field(field)) sizer->Add(field->getSizer(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0); diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 6a00cbe0d7..8d57420a36 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -131,7 +131,7 @@ public: static_cast(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL); static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0); - sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0: 5); + sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); } protected: From e93391e0f8c7d808e0c48951e478918e28693b22 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Apr 2018 13:55:45 +0200 Subject: [PATCH 082/145] Fixed get_zoom_to_bounding_box_factor on linux --- lib/Slic3r/GUI/3DScene.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index ff6d733997..6cdf2a3e5e 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -688,6 +688,9 @@ sub select_view { sub get_zoom_to_bounding_box_factor { my ($self, $bb) = @_; + + return undef if (! $self->init); + my $max_bb_size = max(@{ $bb->size }); return undef if ($max_bb_size == 0); From bbc3c890ea54f08037535c7fa5040030bec1ec89 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 25 Apr 2018 13:44:06 +0200 Subject: [PATCH 083/145] Snapshots: Disable activation of incompatible snapshots --- xs/src/slic3r/Config/Snapshot.cpp | 28 +++++++++++----------- xs/src/slic3r/Config/Snapshot.hpp | 9 ++----- xs/src/slic3r/Config/Version.cpp | 4 ++-- xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp | 16 +++++++------ xs/src/slic3r/Utils/Semver.hpp | 11 +++++++++ 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index 18329aa5c8..704fbcfa1b 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -117,11 +117,11 @@ void Snapshot::load_ini(const std::string &path) if (! semver) throw_on_parse_error("invalid " + kvp.first + " format for " + section.first); if (kvp.first == "version") - vc.version = *semver; + vc.version.config_version = *semver; else if (kvp.first == "min_slic3r_version") - vc.min_slic3r_version = *semver; + vc.version.min_slic3r_version = *semver; else - vc.max_slic3r_version = *semver; + vc.version.max_slic3r_version = *semver; } else if (boost::starts_with(kvp.first, key_prefix_model) && kvp.first.size() > key_prefix_model.size()) { // Parse the printer variants installed for the current model. auto &set_variants = vc.models_variants_installed[kvp.first.substr(key_prefix_model.size())]; @@ -181,9 +181,9 @@ void Snapshot::save_ini(const std::string &path) // Export the vendor configs. for (const VendorConfig &vc : this->vendor_configs) { c << std::endl << "[Vendor:" << vc.name << "]" << std::endl; - c << "version = " << vc.version.to_string() << std::endl; - c << "min_slic3r_version = " << vc.min_slic3r_version.to_string() << std::endl; - c << "max_slic3r_version = " << vc.max_slic3r_version.to_string() << std::endl; + c << "version = " << vc.version.config_version.to_string() << std::endl; + c << "min_slic3r_version = " << vc.version.min_slic3r_version.to_string() << std::endl; + c << "max_slic3r_version = " << vc.version.max_slic3r_version.to_string() << std::endl; // Export installed printer models and their variants. for (const auto &model : vc.models_variants_installed) { if (model.second.size() == 0) @@ -326,10 +326,10 @@ void SnapshotDB::update_slic3r_versions(std::vector &index_db) for (Snapshot::VendorConfig &vendor_config : snapshot.vendor_configs) { auto it = std::find_if(index_db.begin(), index_db.end(), [&vendor_config](const Index &idx) { return idx.vendor() == vendor_config.name; }); if (it != index_db.end()) { - Index::const_iterator it_version = it->find(vendor_config.version); + Index::const_iterator it_version = it->find(vendor_config.version.config_version); if (it_version != it->end()) { - vendor_config.min_slic3r_version = it_version->min_slic3r_version; - vendor_config.max_slic3r_version = it_version->max_slic3r_version; + vendor_config.version.min_slic3r_version = it_version->min_slic3r_version; + vendor_config.version.max_slic3r_version = it_version->max_slic3r_version; } } } @@ -395,16 +395,16 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); for (const VendorProfile &vp : bundle.vendors) if (vp.id == cfg.name) - cfg.version = vp.config_version; + cfg.version.config_version = vp.config_version; // Fill-in the min/max slic3r version from the config index, if possible. try { // Load the config index for the vendor. Index index; index.load(data_dir / "vendor" / (cfg.name + ".idx")); - auto it = index.find(cfg.version); + auto it = index.find(cfg.version.config_version); if (it != index.end()) { - cfg.min_slic3r_version = it->min_slic3r_version; - cfg.max_slic3r_version = it->max_slic3r_version; + cfg.version.min_slic3r_version = it->min_slic3r_version; + cfg.version.max_slic3r_version = it->max_slic3r_version; } } catch (const std::runtime_error &err) { } @@ -476,7 +476,7 @@ SnapshotDB::const_iterator SnapshotDB::snapshot_with_vendor_preset(const std::st auto it_vendor_config = std::lower_bound(snapshot.vendor_configs.begin(), snapshot.vendor_configs.end(), key, [](const Snapshot::VendorConfig &cfg1, const Snapshot::VendorConfig &cfg2) { return cfg1.name < cfg2.name; }); if (it_vendor_config != snapshot.vendor_configs.end() && it_vendor_config->name == vendor_name && - config_version == it_vendor_config->version) { + config_version == it_vendor_config->version.config_version) { // Vendor config found with the correct version. // Save it, but continue searching, as we want the newest snapshot. it_found = it; diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp index 77aee3e21a..a916dfe92a 100644 --- a/xs/src/slic3r/Config/Snapshot.hpp +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -18,7 +18,6 @@ class AppConfig; namespace GUI { namespace Config { -class Version; class Index; // A snapshot contains: @@ -76,12 +75,8 @@ public: struct VendorConfig { // Name of the vendor contained in this snapshot. std::string name; - // Version of the vendor config contained in this snapshot. - Semver version = Semver::invalid(); - // Minimum Slic3r version compatible with this vendor configuration. - Semver min_slic3r_version = Semver::zero(); - // Maximum Slic3r version compatible with this vendor configuration, or empty. - Semver max_slic3r_version = Semver::inf(); + // Version of the vendor config contained in this snapshot, along with compatibility data. + Version version; // Which printer models of this vendor were installed, and which variants of the models? std::map> models_variants_installed; }; diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp index dc09ac8701..a85322ecaf 100644 --- a/xs/src/slic3r/Config/Version.cpp +++ b/xs/src/slic3r/Config/Version.cpp @@ -13,7 +13,7 @@ namespace Slic3r { namespace GUI { namespace Config { -static boost::optional s_current_slic3r_semver = Semver::parse(SLIC3R_VERSION); +static const Semver s_current_slic3r_semver(SLIC3R_VERSION); // Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix. static int compare_prerelease(const char *p1, const char *p2) @@ -62,7 +62,7 @@ bool Version::is_slic3r_supported(const Semver &slic3r_version) const bool Version::is_current_slic3r_supported() const { - return this->is_slic3r_supported(*s_current_slic3r_semver); + return this->is_slic3r_supported(s_current_slic3r_semver); } #if 0 diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 99af707e1c..c0affcab7c 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -39,18 +39,16 @@ static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_ text += " (" + snapshot.comment + ")"; text += "

"; // End of row header. -// text += _(L("ID:")) + " " + snapshot.id + "
"; - // text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "
"; text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "
"; -// text += "reason: " + snapshot.reason + "
"; text += _(L("print")) + ": " + snapshot.print + "
"; text += _(L("filaments")) + ": " + snapshot.filaments.front() + "
"; text += _(L("printer")) + ": " + snapshot.printer + "
"; + bool compatible = true; for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) { - text += _(L("vendor")) + ": " + vc.name + ", ver: " + vc.version.to_string() + ", min slic3r ver: " + vc.min_slic3r_version.to_string(); - if (vc.max_slic3r_version != Semver::inf()) - text += ", max slic3r ver: " + vc.max_slic3r_version.to_string(); + text += _(L("vendor")) + ": " + vc.name + ", ver: " + vc.version.config_version.to_string() + ", min slic3r ver: " + vc.version.min_slic3r_version.to_string(); + if (vc.version.max_slic3r_version != Semver::inf()) + text += ", max slic3r ver: " + vc.version.max_slic3r_version.to_string(); text += "
"; for (const std::pair> &model : vc.models_variants_installed) { text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": "; @@ -61,9 +59,13 @@ static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_ } text += "
"; } + if (! vc.version.is_current_slic3r_supported()) { compatible = false; } } - if (! snapshot_active) + if (! compatible) { + text += "

" + _(L("Incompatible with this Slic3r")) + "

"; + } + else if (! snapshot_active) text += "

" + _(L("Activate")) + "

"; text += ""; text += ""; diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index 87396d8124..ae9d21c6b8 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,16 @@ public: set_prerelease(prerelease); } + Semver(const std::string &str) : ver(semver_zero()) + { + auto parsed = parse(str); + if (! parsed) { + throw std::runtime_error(std::string("Could not parse version string: ") + str); + } + ver = parsed->ver; + parsed->ver = semver_zero(); + } + static boost::optional parse(const std::string &str) { semver_t ver = semver_zero(); From 1a4827ba33dfba5e6567d4e942fa91f662dc040d Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Apr 2018 14:38:44 +0200 Subject: [PATCH 084/145] Fixed incorrect z values set into GCode Preview sliders --- xs/src/slic3r/GUI/3DScene.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 7f5e632bba..a016ab5aa0 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -751,7 +751,10 @@ std::vector GLVolumeCollection::get_current_print_zs() const // Collect layer top positions of all volumes. std::vector print_zs; for (GLVolume *vol : this->volumes) - append(print_zs, vol->print_zs); + { + if (vol->is_active) + append(print_zs, vol->print_zs); + } std::sort(print_zs.begin(), print_zs.end()); // Replace intervals of layers with similar top positions with their average value. @@ -1757,6 +1760,11 @@ void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* pr { _generate_legend_texture(*preview_data, tool_colors); _load_shells(*print, *volumes, use_VBOs); + + // removes empty volumes + volumes->volumes.erase(std::remove_if(volumes->volumes.begin(), volumes->volumes.end(), + [](const GLVolume *volume) { return volume->print_zs.empty(); }), + volumes->volumes.end()); } } From 933c0eb6508f3e972ff338bbebfb5fee19e9d104 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 25 Apr 2018 15:14:01 +0200 Subject: [PATCH 085/145] Fixes in SemVer and MsgUpdateConfig --- xs/src/slic3r/GUI/UpdateDialogs.cpp | 2 +- xs/src/slic3r/Utils/Semver.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp index 9a689e03df..e11ecdf5e4 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.cpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -138,7 +138,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::unordered_mapAdd(btn_cancel); btn_sizer->AddSpacer(HORIZ_SPACING); - auto *btn_ok = new wxButton(this, wxID_YES); + auto *btn_ok = new wxButton(this, wxID_OK); btn_sizer->Add(btn_ok); btn_ok->SetFocus(); diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index ae9d21c6b8..736f9b891a 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -99,8 +99,8 @@ public: void set_maj(int maj) { ver.major = maj; } void set_min(int min) { ver.minor = min; } void set_patch(int patch) { ver.patch = patch; } - void set_metadata(boost::optional meta) { meta ? strdup(*meta) : nullptr; } - void set_prerelease(boost::optional pre) { pre ? strdup(*pre) : nullptr; } + void set_metadata(boost::optional meta) { ver.metadata = meta ? strdup(*meta) : nullptr; } + void set_prerelease(boost::optional pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; } // Comparison bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; } From 8096ef6844caf3662362e61b58bedba6311029c1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Apr 2018 15:16:39 +0200 Subject: [PATCH 086/145] Fixed wrong countours for multipart objects in cut dialog 3D view --- lib/Slic3r/GUI/Plater/ObjectCutDialog.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 7b5752cd2b..0cc8b2decc 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -227,12 +227,14 @@ sub _update { push @objects, $self->{model_object}; } + my $z_cut = $z + $self->{model_object}->bounding_box->z_min; + # get section contour my @expolygons = (); foreach my $volume (@{$self->{model_object}->volumes}) { next if !$volume->mesh; next if $volume->modifier; - my $expp = $volume->mesh->slice([ $z + $volume->mesh->bounding_box->z_min ])->[0]; + my $expp = $volume->mesh->slice([ $z_cut ])->[0]; push @expolygons, @$expp; } foreach my $expolygon (@expolygons) { From f23f86d91c41ee0dd5fed15e31e5c73cae394d9b Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 25 Apr 2018 15:20:46 +0200 Subject: [PATCH 087/145] PresetUpdate: Fix UpdateConfig dialog --- xs/src/slic3r/Utils/PresetUpdater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 54841a3708..2f9fc6871c 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -525,7 +525,7 @@ bool PresetUpdater::config_update() const GUI::MsgUpdateConfig dlg(std::move(updates_map)); const auto res = dlg.ShowModal(); - if (res == wxID_YES) { + if (res == wxID_OK) { BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; p->perform_updates(std::move(updates)); } else { From 166ee4c2c87a068263b26c7f43ed309e2078e4ec Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Apr 2018 15:31:37 +0200 Subject: [PATCH 088/145] Export of print config enabled as default in save file dialog when exporting to amf and 3mf files --- xs/src/slic3r/GUI/GUI.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 06929085c8..a9ce045436 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -925,6 +925,7 @@ wxWindow* export_option_creator(wxWindow* parent) wxPanel* panel = new wxPanel(parent, -1); wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); wxCheckBox* cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, L("Export print config")); + cbox->SetValue(true); sizer->AddSpacer(5); sizer->Add(cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); panel->SetSizer(sizer); From 03e9da804a2a9dc8ac987616a4119af5b02e88be Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 25 Apr 2018 16:42:33 +0200 Subject: [PATCH 089/145] Update PrusaResearch bundle & index --- resources/profiles/PrusaResearch.idx | 18 +----------------- resources/profiles/PrusaResearch.ini | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 5dd09868fa..f07d8b2806 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,17 +1 @@ -# This is an example configuration version index. -# The index contains version numbers -min_slic3r_version =1.39.0 -1.1.3 -1.1.2 -1.1.1 -1.1.0 -0.2.0-alpha "some test comment" -max_slic3r_version= 1.39.4 -0.1.0 another test comment - -# some empty lines - -# version without a comment -min_slic3r_version = 1.0.0 -max_slic3r_version = 1.1.0 -0.0.1 +0.1.0 Initial diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 0da7f22d14..b313c4dc57 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -7,13 +7,13 @@ name = Prusa Research # This means, the server may force the Slic3r configuration to be downgraded. config_version = 0.1.0 # Where to get the updates from? -# TODO: proper URL config_update_url = https://raw.githubusercontent.com/vojtechkral/slic3r-settings-tmp/master/PrusaResearch # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. #TODO: One day we may differentiate variants of the nozzles / hot ends, #for example by the melt zone size, or whether the nozzle is hardened. +# Printer model name will be shown by the installation wizard. [printer_model:MK3] name = Original Prusa i3 MK3 variants = 0.4; 0.25; 0.6 @@ -23,7 +23,6 @@ name = Original Prusa i3 MK2S variants = 0.4; 0.25; 0.6 [printer_model:MK2SMM] -# Printer model name will be shown by the installation wizard. name = Original Prusa i3 MK2SMM variants = 0.4; 0.6 @@ -69,6 +68,7 @@ infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% interface_shells = 0 +max_print_height = 200 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 @@ -120,7 +120,7 @@ thin_walls = 0 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 travel_speed = 180 -wipe_tower = 0 +wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 @@ -939,6 +939,8 @@ inherits = *multimaterial* end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusa PLA [printer:*mm-multi*] inherits = *multimaterial* @@ -948,10 +950,11 @@ nozzle_diameter = 0.4,0.4,0.4,0.4 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 variable_layer_height = 0 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusa PLA [printer:Original Prusa i3 MK2] inherits = *common* -default_print_profile = 0.15mm OPTIMAL [printer:Original Prusa i3 MK2 0.25 nozzle] inherits = *common* @@ -989,12 +992,14 @@ nozzle_diameter = 0.4,0.4,0.4,0.4 inherits = *mm-multi* nozzle_diameter = 0.6,0.6,0.6,0.6 printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle [printer:Original Prusa i3 MK3] inherits = *common* end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 @@ -1002,21 +1007,21 @@ default_print_profile = 0.15mm OPTIMAL MK3 [printer:Original Prusa i3 MK3 0.25 nozzle] inherits = *common* nozzle_diameter = 0.25 -printer_variant = 0.25 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 -default_print_profile = 0.10mm DETAIL MK3 +default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] inherits = *common* nozzle_diameter = 0.6 -printer_variant = 0.6 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 -default_print_profile = 0.15mm OPTIMAL MK3 +default_print_profile = 0.15mm OPTIMAL 0.6 nozzle MK3 From dce0aa677122172ced4502d43ab42dedb551c077 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 25 Apr 2018 17:43:01 +0200 Subject: [PATCH 090/145] Updating: Start using proper URLs --- resources/profiles/PrusaResearch.ini | 2 +- xs/src/slic3r/GUI/AppConfig.cpp | 10 +++++++--- xs/src/slic3r/GUI/AppConfig.hpp | 7 ++++++- xs/src/slic3r/Utils/PresetUpdater.cpp | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index b313c4dc57..d82921fd8d 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -7,7 +7,7 @@ name = Prusa Research # This means, the server may force the Slic3r configuration to be downgraded. config_version = 0.1.0 # Where to get the updates from? -config_update_url = https://raw.githubusercontent.com/vojtechkral/slic3r-settings-tmp/master/PrusaResearch +config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 965e8185d4..70f4ba110d 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -21,6 +21,7 @@ namespace Slic3r { static const std::string VENDOR_PREFIX = "vendor:"; static const std::string MODEL_PREFIX = "model:"; +static const std::string VERSION_CHECK_URL = "https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/Slic3rPE.version"; void AppConfig::reset() { @@ -49,9 +50,6 @@ void AppConfig::set_defaults() if (get("version_check").empty()) set("version_check", "1"); - // TODO: proper URL - if (get("version_check_url").empty()) - set("version_check_url", "https://gist.githubusercontent.com/vojtechkral/4d8fd4a3b8699a01ec892c264178461c/raw/2f05a64db19e45a7f8fe2cedeff555d544af679b/slic3rPE.version"); if (get("preset_update").empty()) set("preset_update", "1"); @@ -241,6 +239,12 @@ std::string AppConfig::config_path() return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); } +std::string AppConfig::version_check_url() const +{ + auto from_settings = get("version_check_url"); + return from_settings.empty() ? VERSION_CHECK_URL : from_settings; +} + bool AppConfig::exists() { return boost::filesystem::exists(AppConfig::config_path()); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index 6dcfec0461..16469f0e95 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -92,9 +92,14 @@ public: // Get the default config path from Slic3r::data_dir(). static std::string config_path(); - + + // Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating) bool legacy_datadir() const { return m_legacy_datadir; } + // 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; + // Does the config file exist? static bool exists(); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 2f9fc6871c..9c5fe07481 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -135,7 +135,7 @@ PresetUpdater::priv::priv(int version_online_event) : void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) { enabled_version_check = app_config->get("version_check") == "1"; - version_check_url = app_config->get("version_check_url"); + version_check_url = app_config->version_check_url(); enabled_config_update = app_config->get("preset_update") == "1"; } From 269770bbbc373f1462631f918b0bb894db7422a1 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 25 Apr 2018 22:06:44 +0200 Subject: [PATCH 091/145] Fix of a new cooling logic. --- t/cooling.t | 6 +-- xs/src/libslic3r/GCode/CoolingBuffer.cpp | 60 ++++++++++++++++-------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/t/cooling.t b/t/cooling.t index ee4f6abea5..f69b7e8a8e 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 15; +plan tests => 14; BEGIN { use FindBin; @@ -203,8 +203,8 @@ $config->set('disable_fan_first_layers', [ 0 ]); ok $all_below, 'slowdown_below_layer_time is honored'; # check that all layers have at least one unaltered external perimeter speed - my $external = all { $_ > 0 } values %layer_external; - ok $external, 'slowdown_below_layer_time does not alter external perimeters'; +# my $external = all { $_ > 0 } values %layer_external; +# ok $external, 'slowdown_below_layer_time does not alter external perimeters'; } __END__ diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp index 121cbb0175..ca9fc65555 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -102,6 +102,13 @@ struct PerExtruderAdjustments time_total += line.time; return time_total; } + float adjustable_time(bool slowdown_external_perimeters) { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (line.adjustable(slowdown_external_perimeters)) + time_total += line.time; + return time_total; + } // Calculate the non-adjustable part of the total time. float non_adjustable_time(bool slowdown_external_perimeters) { float time_total = 0.f; @@ -207,11 +214,14 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ const size_t num_extruders = extruders.size(); std::vector per_extruder_adjustments(num_extruders); - std::vector map_extruder_to_per_extruder_adjustment(num_extruders, 0); + unsigned int id_extruder_max = 0; + for (const Extruder &ex : extruders) + id_extruder_max = std::max(ex.id(), id_extruder_max); + std::vector map_extruder_to_per_extruder_adjustment(id_extruder_max + 1, 0); for (size_t i = 0; i < num_extruders; ++ i) { unsigned int extruder_id = extruders[i].id(); per_extruder_adjustments[i].extruder_id = extruder_id; - per_extruder_adjustments[i].min_print_speed = config.min_print_speed.get_at(i); + per_extruder_adjustments[i].min_print_speed = config.min_print_speed.get_at(extruder_id); map_extruder_to_per_extruder_adjustment[extruder_id] = i; } const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); @@ -442,6 +452,7 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; }); // Slow down, fast moves first. + float time_stretch = slowdown_below_layer_time - total; for (;;) { // For each extruder, find the span of lines with a feedrate close to feedrate. for (PerExtruderAdjustments *adj : by_min_print_speed) { @@ -457,23 +468,34 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ // Slow down, limited by max(feedrate_next, min_print_speed). for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) { float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed); - float time_stretch = slowdown_below_layer_time - total; - float time_stretch_max = 0.f; - std::pair time_stretched(0.f, 0.f); - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - time_stretch_max += (*it)->time_stretch_when_slowing_down_to(feedrate_limit); - bool done = false; - if (time_stretch_max > time_stretch) { - feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max; - done = true; - } - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - (*it)->slow_down_to(feedrate_limit); - if (done) { - // Break from two levels of loops. - feedrate_next = 0.f; - break; - } + if (feedrate_limit == 0.f) { + float adjustable_time = 0.f; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + adjustable_time += (*it)->adjustable_time(true); + float ratio = (adjustable_time + time_stretch) / adjustable_time; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + (*it)->slow_down_proportional(ratio, true); + // Break from two levels of loops. + feedrate_next = 0.f; + break; + } else { + float time_stretch_max = 0.f; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + time_stretch_max += (*it)->time_stretch_when_slowing_down_to(feedrate_limit); + bool done = false; + if (time_stretch_max > time_stretch) { + feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max; + done = true; + } + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + (*it)->slow_down_to(feedrate_limit); + if (done) { + // Break from two levels of loops. + feedrate_next = 0.f; + break; + } + time_stretch -= time_stretch_max; + } // Skip the other extruders with nearly the same min_print_speed, as they have been processed already. auto next = adj; for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next); From cbaf0ccc51133db07b47e1de0177b5db4fae2a02 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 25 Apr 2018 22:54:52 +0200 Subject: [PATCH 092/145] Refactored cooling logic for readibility and maintainability. --- t/cooling.t | 1 + xs/src/libslic3r/GCode/CoolingBuffer.cpp | 753 ++++++++++++----------- xs/src/libslic3r/GCode/CoolingBuffer.hpp | 26 +- 3 files changed, 430 insertions(+), 350 deletions(-) diff --git a/t/cooling.t b/t/cooling.t index f69b7e8a8e..2f444cf9d1 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -79,6 +79,7 @@ $config->set('disable_fan_first_layers', [ 0 ]); "G1 X50 F2500\n" . "G1 F3000;_EXTRUDE_SET_SPEED\n" . "G1 X100 E1\n" . + ";_EXTRUDE_END\n" . "G1 E4 F400", # Print time of $gcode. my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60); diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp index ca9fc65555..141d197ca6 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -89,8 +89,9 @@ struct PerExtruderAdjustments time_total += line.time; return time_total; } - // Calculate the maximum time when slowing down. - float maximum_time(bool slowdown_external_perimeters) { + // Calculate the total elapsed time when slowing down + // to the minimum extrusion feed rate defined for the current material. + float maximum_time_after_slowdown(bool slowdown_external_perimeters) { float time_total = 0.f; for (const CoolingLine &line : lines) if (line.adjustable(slowdown_external_perimeters)) { @@ -102,6 +103,7 @@ struct PerExtruderAdjustments time_total += line.time; return time_total; } + // Calculate the adjustable part of the total time. float adjustable_time(bool slowdown_external_perimeters) { float time_total = 0.f; for (const CoolingLine &line : lines) @@ -117,7 +119,9 @@ struct PerExtruderAdjustments time_total += line.time; return time_total; } - float slow_down_maximum(bool slowdown_external_perimeters) { + // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material. + // Used by both proportional and non-proportional slow down. + float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) { float time_total = 0.f; for (CoolingLine &line : lines) { if (line.adjustable(slowdown_external_perimeters)) { @@ -130,6 +134,8 @@ struct PerExtruderAdjustments } return time_total; } + // Slow down each adjustable G-code line proportionally by a factor. + // Used by the proportional slow down. float slow_down_proportional(float factor, bool slowdown_external_perimeters) { assert(factor >= 1.f); float time_total = 0.f; @@ -144,9 +150,8 @@ struct PerExtruderAdjustments return time_total; } - bool operator<(const PerExtruderAdjustments &rhs) const { return this->extruder_id < rhs.extruder_id; } - // Sort the lines, adjustable first, higher feedrate first. + // Used by non-proportional slow down. void sort_lines_by_decreasing_feedrate() { std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) { bool adj1 = l1.adjustable(); @@ -161,34 +166,41 @@ struct PerExtruderAdjustments time_non_adjustable += lines[i].time; } - // Calculate the maximum time when slowing down. - float time_stretch_when_slowing_down_to(float min_feedrate) { + // Calculate the maximum time stretch when slowing down to min_feedrate. + // Slowdown to min_feedrate shall be allowed for this extruder's material. + // Used by non-proportional slow down. + float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) { float time_stretch = 0.f; - if (this->min_print_speed < min_feedrate + EPSILON) { - for (size_t i = 0; i < n_lines_adjustable; ++ i) { - const CoolingLine &line = lines[i]; - if (line.feedrate > min_feedrate) - time_stretch += line.time * (line.feedrate / min_feedrate - 1.f); - } + assert(this->min_print_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < n_lines_adjustable; ++ i) { + const CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) + time_stretch += line.time * (line.feedrate / min_feedrate - 1.f); } return time_stretch; } - void slow_down_to(float min_feedrate) { - if (this->min_print_speed < min_feedrate + EPSILON) { - for (size_t i = 0; i < n_lines_adjustable; ++ i) { - CoolingLine &line = lines[i]; - if (line.feedrate > min_feedrate) { - line.time *= std::max(1.f, line.feedrate / min_feedrate); - line.feedrate = min_feedrate; - line.slowdown = true; - } + // Slow down all adjustable lines down to min_feedrate. + // Slowdown to min_feedrate shall be allowed for this extruder's material. + // Used by non-proportional slow down. + void slow_down_to_feedrate(float min_feedrate) { + assert(this->min_print_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < n_lines_adjustable; ++ i) { + CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) { + line.time *= std::max(1.f, line.feedrate / min_feedrate); + line.feedrate = min_feedrate; + line.slowdown = true; } } } // Extruder, for which the G-code will be adjusted. unsigned int extruder_id = 0; + // Is the cooling slow down logic enabled for this extruder's material? + bool cooling_slow_down_enabled = false; + // Slow down the print down to min_print_speed if the total layer time is below slowdown_below_layer_time. + float slowdown_below_layer_time = 0.f; // Minimum print speed allowed for this extruder. float min_print_speed = 0.f; @@ -199,335 +211,387 @@ struct PerExtruderAdjustments size_t n_lines_adjustable = 0; // Non-adjustable time of lines starting with n_lines_adjustable. float time_non_adjustable = 0; + // Current total time for this extruder. + float time_total = 0; + // Maximum time for this extruder, when the maximum slow down is applied. + float time_maximum = 0; // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable. size_t idx_line_begin = 0; size_t idx_line_end = 0; }; -#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) - std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id) +{ + std::vector per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos); + float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments); + return this->apply_layer_cooldown(gcode, layer_id, layer_time_stretched, per_extruder_adjustments); +} + +// Parse the layer G-code for the moves, which could be adjusted. +// Return the list of parsed lines, bucketed by an extruder. +std::vector CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const { const FullPrintConfig &config = m_gcodegen.config(); const std::vector &extruders = m_gcodegen.writer().extruders(); - const size_t num_extruders = extruders.size(); - - std::vector per_extruder_adjustments(num_extruders); - unsigned int id_extruder_max = 0; + unsigned int num_extruders = 0; for (const Extruder &ex : extruders) - id_extruder_max = std::max(ex.id(), id_extruder_max); - std::vector map_extruder_to_per_extruder_adjustment(id_extruder_max + 1, 0); - for (size_t i = 0; i < num_extruders; ++ i) { - unsigned int extruder_id = extruders[i].id(); - per_extruder_adjustments[i].extruder_id = extruder_id; - per_extruder_adjustments[i].min_print_speed = config.min_print_speed.get_at(extruder_id); + num_extruders = std::max(ex.id() + 1, num_extruders); + + std::vector per_extruder_adjustments(extruders.size()); + std::vector map_extruder_to_per_extruder_adjustment(num_extruders, 0); + for (size_t i = 0; i < extruders.size(); ++ i) { + PerExtruderAdjustments &adj = per_extruder_adjustments[i]; + unsigned int extruder_id = extruders[i].id(); + adj.extruder_id = extruder_id; + adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); + adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id); + adj.min_print_speed = config.min_print_speed.get_at(extruder_id); map_extruder_to_per_extruder_adjustment[extruder_id] = i; } - const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); - // Parse the layer G-code for the moves, which could be adjusted. + + const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); + unsigned int current_extruder = m_current_extruder; + PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; + const char *line_start = gcode.c_str(); + const char *line_end = line_start; + const char extrusion_axis = config.get_extrusion_axis()[0]; + // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command + // for a sequence of extrusion moves. + size_t active_speed_modifier = size_t(-1); + + for (; *line_start != 0; line_start = line_end) { - PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[m_current_extruder]]; - unsigned int initial_extruder = m_current_extruder; - const char *line_start = gcode.c_str(); - const char *line_end = line_start; - const char extrusion_axis = config.get_extrusion_axis()[0]; - // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command - // for a sequence of extrusion moves. - size_t active_speed_modifier = size_t(-1); - for (; *line_start != 0; line_start = line_end) { - while (*line_end != '\n' && *line_end != 0) - ++ line_end; - // sline will not contain the trailing '\n'. - std::string sline(line_start, line_end); - // CoolingLine will contain the trailing '\n'. - if (*line_end == '\n') - ++ line_end; - CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); - if (boost::starts_with(sline, "G0 ")) - line.type = CoolingLine::TYPE_G0; - else if (boost::starts_with(sline, "G1 ")) - line.type = CoolingLine::TYPE_G1; - else if (boost::starts_with(sline, "G92 ")) - line.type = CoolingLine::TYPE_G92; - if (line.type) { - // G0, G1 or G92 - // Parse the G-code line. - std::vector new_pos(m_current_pos); - const char *c = sline.data() + 3; - for (;;) { - // Skip whitespaces. - for (; *c == ' ' || *c == '\t'; ++ c); - if (*c == 0 || *c == ';') - break; - // Parse the axis. - size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : - (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); - if (axis != size_t(-1)) { - new_pos[axis] = float(atof(++c)); - if (axis == 4) { - // Convert mm/min to mm/sec. - new_pos[4] /= 60.f; - if ((line.type & CoolingLine::TYPE_G92) == 0) - // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. - line.type |= CoolingLine::TYPE_HAS_F; - } - } - // Skip this word. - for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); - } - bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); - bool wipe = boost::contains(sline, ";_WIPE"); - if (external_perimeter) - line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; - if (wipe) - line.type |= CoolingLine::TYPE_WIPE; - if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { - line.type |= CoolingLine::TYPE_ADJUSTABLE; - active_speed_modifier = adjustment->lines.size(); - } - if ((line.type & CoolingLine::TYPE_G92) == 0) { - // G0 or G1. Calculate the duration. - if (config.use_relative_e_distances.value) - // Reset extruder accumulator. - m_current_pos[3] = 0.f; - float dif[4]; - for (size_t i = 0; i < 4; ++ i) - dif[i] = new_pos[i] - m_current_pos[i]; - float dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; - float dxyz2 = dxy2 + dif[2] * dif[2]; - if (dxyz2 > 0.f) { - // Movement in xyz, calculate time from the xyz Euclidian distance. - line.length = sqrt(dxyz2); - } else if (std::abs(dif[3]) > 0.f) { - // Movement in the extruder axis. - line.length = std::abs(dif[3]); + while (*line_end != '\n' && *line_end != 0) + ++ line_end; + // sline will not contain the trailing '\n'. + std::string sline(line_start, line_end); + // CoolingLine will contain the trailing '\n'. + if (*line_end == '\n') + ++ line_end; + CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); + if (boost::starts_with(sline, "G0 ")) + line.type = CoolingLine::TYPE_G0; + else if (boost::starts_with(sline, "G1 ")) + line.type = CoolingLine::TYPE_G1; + else if (boost::starts_with(sline, "G92 ")) + line.type = CoolingLine::TYPE_G92; + if (line.type) { + // G0, G1 or G92 + // Parse the G-code line. + std::vector new_pos(current_pos); + const char *c = sline.data() + 3; + for (;;) { + // Skip whitespaces. + for (; *c == ' ' || *c == '\t'; ++ c); + if (*c == 0 || *c == ';') + break; + // Parse the axis. + size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : + (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); + if (axis != size_t(-1)) { + new_pos[axis] = float(atof(++c)); + if (axis == 4) { + // Convert mm/min to mm/sec. + new_pos[4] /= 60.f; + if ((line.type & CoolingLine::TYPE_G92) == 0) + // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. + line.type |= CoolingLine::TYPE_HAS_F; } - if (line.length > 0) { - line.feedrate = new_pos[4]; // current F - line.time = line.length / line.feedrate; - } - line.time_max = line.time; - if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) - line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed); - if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) { - // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. - assert((line.type & CoolingLine::TYPE_HAS_F) == 0); - CoolingLine &sm = adjustment->lines[active_speed_modifier]; - sm.length += line.length; - sm.time += line.time; - if (sm.time_max != FLT_MAX) { - if (line.time_max == FLT_MAX) - sm.time_max = FLT_MAX; - else - sm.time_max += line.time_max; - } - // Don't store this line. - line.type = 0; - } - } - m_current_pos = std::move(new_pos); - } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { - line.type = CoolingLine::TYPE_EXTRUDE_END; - active_speed_modifier = size_t(-1); - } else if (boost::starts_with(sline, toolchange_prefix)) { - // Switch the tool. - line.type = CoolingLine::TYPE_SET_TOOL; - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); - if (new_extruder != m_current_extruder) { - m_current_extruder = new_extruder; - adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[m_current_extruder]]; } - } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) { - line.type = CoolingLine::TYPE_BRIDGE_FAN_START; - } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) { - line.type = CoolingLine::TYPE_BRIDGE_FAN_END; - } else if (boost::starts_with(sline, "G4 ")) { - // Parse the wait time. - line.type = CoolingLine::TYPE_G4; - size_t pos_S = sline.find('S', 3); - size_t pos_P = sline.find('P', 3); - line.time = line.time_max = float( - (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : - (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); + // Skip this word. + for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); } - if (line.type != 0) - adjustment->lines.emplace_back(std::move(line)); - } - m_current_extruder = initial_extruder; + bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); + bool wipe = boost::contains(sline, ";_WIPE"); + if (external_perimeter) + line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; + if (wipe) + line.type |= CoolingLine::TYPE_WIPE; + if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { + line.type |= CoolingLine::TYPE_ADJUSTABLE; + active_speed_modifier = adjustment->lines.size(); + } + if ((line.type & CoolingLine::TYPE_G92) == 0) { + // G0 or G1. Calculate the duration. + if (config.use_relative_e_distances.value) + // Reset extruder accumulator. + current_pos[3] = 0.f; + float dif[4]; + for (size_t i = 0; i < 4; ++ i) + dif[i] = new_pos[i] - current_pos[i]; + float dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; + float dxyz2 = dxy2 + dif[2] * dif[2]; + if (dxyz2 > 0.f) { + // Movement in xyz, calculate time from the xyz Euclidian distance. + line.length = sqrt(dxyz2); + } else if (std::abs(dif[3]) > 0.f) { + // Movement in the extruder axis. + line.length = std::abs(dif[3]); + } + if (line.length > 0) { + line.feedrate = new_pos[4]; // current F + line.time = line.length / line.feedrate; + } + line.time_max = line.time; + if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) + line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed); + if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) { + // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. + assert((line.type & CoolingLine::TYPE_HAS_F) == 0); + CoolingLine &sm = adjustment->lines[active_speed_modifier]; + sm.length += line.length; + sm.time += line.time; + if (sm.time_max != FLT_MAX) { + if (line.time_max == FLT_MAX) + sm.time_max = FLT_MAX; + else + sm.time_max += line.time_max; + } + // Don't store this line. + line.type = 0; + } + } + current_pos = std::move(new_pos); + } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { + line.type = CoolingLine::TYPE_EXTRUDE_END; + active_speed_modifier = size_t(-1); + } else if (boost::starts_with(sline, toolchange_prefix)) { + // Switch the tool. + line.type = CoolingLine::TYPE_SET_TOOL; + unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); + if (new_extruder != current_extruder) { + current_extruder = new_extruder; + adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; + } + } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) { + line.type = CoolingLine::TYPE_BRIDGE_FAN_START; + } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) { + line.type = CoolingLine::TYPE_BRIDGE_FAN_END; + } else if (boost::starts_with(sline, "G4 ")) { + // Parse the wait time. + line.type = CoolingLine::TYPE_G4; + size_t pos_S = sline.find('S', 3); + size_t pos_P = sline.find('P', 3); + line.time = line.time_max = float( + (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : + (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); + } + if (line.type != 0) + adjustment->lines.emplace_back(std::move(line)); } - // Sort the extruders by the increasing slowdown_below_layer_time. - std::vector extruder_by_slowdown_time; - extruder_by_slowdown_time.reserve(num_extruders); + return per_extruder_adjustments; +} + +// Slow down an extruder range proportionally down to slowdown_below_layer_time. +// Return the total time for the complete layer. +static inline float extruder_range_slow_down_proportional( + std::vector::iterator it_begin, + std::vector::iterator it_end, + // Elapsed time for the extruders already processed. + float elapsed_time_total0, + // Initial total elapsed time before slow down. + float elapsed_time_before_slowdown, + // Target time for the complete layer (all extruders applied). + float slowdown_below_layer_time) +{ + // Total layer time after the slow down has been applied. + float total_after_slowdown = elapsed_time_before_slowdown; + // Now decide, whether the external perimeters shall be slowed down as well. + float max_time_nep = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + max_time_nep += (*it)->maximum_time_after_slowdown(false); + if (max_time_nep > slowdown_below_layer_time) { + // It is sufficient to slow down the non-external perimeter moves to reach the target layer time. + // Slow down the non-external perimeters proportionally. + float non_adjustable_time = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + non_adjustable_time += (*it)->non_adjustable_time(false); + // The following step is a linear programming task due to the minimum movement speeds of the print moves. + // Run maximum 5 iterations until a good enough approximation is reached. + for (size_t iter = 0; iter < 5; ++ iter) { + float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time); + assert(factor > 1.f); + total_after_slowdown = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + total_after_slowdown += (*it)->slow_down_proportional(factor, false); + if (total_after_slowdown > 0.95f * slowdown_below_layer_time) + break; + } + } else { + // Slow down everything. First slow down the non-external perimeters to maximum. + for (auto it = it_begin; it != it_end; ++ it) + (*it)->slowdown_to_minimum_feedrate(false); + // Slow down the external perimeters proportionally. + float non_adjustable_time = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + non_adjustable_time += (*it)->non_adjustable_time(true); + for (size_t iter = 0; iter < 5; ++ iter) { + float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time); + assert(factor > 1.f); + total_after_slowdown = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + total_after_slowdown += (*it)->slow_down_proportional(factor, true); + if (total_after_slowdown > 0.95f * slowdown_below_layer_time) + break; + } + } + return total_after_slowdown; +} + +// Slow down an extruder range to slowdown_below_layer_time. +// Return the total time for the complete layer. +static inline void extruder_range_slow_down_non_proportional( + std::vector::iterator it_begin, + std::vector::iterator it_end, + float time_stretch) +{ + // Slow down. Try to equalize the feedrates. + std::vector by_min_print_speed(it_begin, it_end); + // Find the next highest adjustable feedrate among the extruders. + float feedrate = 0; + for (PerExtruderAdjustments *adj : by_min_print_speed) { + adj->idx_line_begin = 0; + adj->idx_line_end = 0; + assert(adj->idx_line_begin < adj->n_lines_adjustable); + if (adj->lines[adj->idx_line_begin].feedrate > feedrate) + feedrate = adj->lines[adj->idx_line_begin].feedrate; + } + assert(feedrate > 0.f); + // Sort by min_print_speed, maximum speed first. + std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), + [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; }); + // Slow down, fast moves first. + for (;;) { + // For each extruder, find the span of lines with a feedrate close to feedrate. + for (PerExtruderAdjustments *adj : by_min_print_speed) { + for (adj->idx_line_end = adj->idx_line_begin; + adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON; + ++ adj->idx_line_end) ; + } + // Find the next highest adjustable feedrate among the extruders. + float feedrate_next = 0.f; + for (PerExtruderAdjustments *adj : by_min_print_speed) + if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next) + feedrate_next = adj->lines[adj->idx_line_end].feedrate; + // Slow down, limited by max(feedrate_next, min_print_speed). + for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) { + // Slow down at most by time_stretch. + if ((*adj)->min_print_speed == 0.f) { + // All the adjustable speeds are now lowered to the same speed, + // and the minimum speed is set to zero. + float time_adjustable = 0.f; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + time_adjustable += (*it)->adjustable_time(true); + float rate = (time_adjustable + time_stretch) / time_adjustable; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + (*it)->slow_down_proportional(rate, true); + return; + } else { + float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed); + bool done = false; + float time_stretch_max = 0.f; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit); + if (time_stretch_max >= time_stretch) { + feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max; + done = true; + } else + time_stretch -= time_stretch_max; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + (*it)->slow_down_to_feedrate(feedrate_limit); + if (done) + return; + } + // Skip the other extruders with nearly the same min_print_speed, as they have been processed already. + auto next = adj; + for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next); + adj = next; + } + if (feedrate_next == 0.f) + // There are no other extrusions available for slow down. + break; + for (PerExtruderAdjustments *adj : by_min_print_speed) { + adj->idx_line_begin = adj->idx_line_end; + feedrate = feedrate_next; + } + } +} + +// Calculate slow down for all the extruders. +float CoolingBuffer::calculate_layer_slowdown(std::vector &per_extruder_adjustments) +{ + // Sort the extruders by an increasing slowdown_below_layer_time. + // The layers with a lower slowdown_below_layer_time are slowed down + // together with all the other layers with slowdown_below_layer_time above. + std::vector by_slowdown_time; + by_slowdown_time.reserve(per_extruder_adjustments.size()); // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time). // Collect total print time of non-adjustable extruders. - float elapsed_time_total_non_adjustable = 0.f; - for (size_t i = 0; i < num_extruders; ++ i) { - if (config.cooling.get_at(extruders[i].id())) { - extruder_by_slowdown_time.emplace_back(i); - per_extruder_adjustments[i].sort_lines_by_decreasing_feedrate(); + float elapsed_time_total0 = 0.f; + for (PerExtruderAdjustments &adj : per_extruder_adjustments) { + // Curren total time for this extruder. + adj.time_total = adj.elapsed_time_total(); + // Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed. + adj.time_maximum = adj.maximum_time_after_slowdown(true); + if (adj.cooling_slow_down_enabled) { + by_slowdown_time.emplace_back(&adj); + if (! m_cooling_logic_proportional) + // sorts the lines, also sets adj.time_non_adjustable + adj.sort_lines_by_decreasing_feedrate(); } else - elapsed_time_total_non_adjustable += per_extruder_adjustments[i].elapsed_time_total(); + elapsed_time_total0 += adj.elapsed_time_total(); } - std::sort(extruder_by_slowdown_time.begin(), extruder_by_slowdown_time.end(), - [&config, &extruders](const size_t idx1, const size_t idx2){ - return config.slowdown_below_layer_time.get_at(extruders[idx1].id()) < - config.slowdown_below_layer_time.get_at(extruders[idx2].id()); - }); + std::sort(by_slowdown_time.begin(), by_slowdown_time.end(), + [](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2) + { return adj1->slowdown_below_layer_time < adj2->slowdown_below_layer_time; }); - // Elapsed time after adjustment. - float elapsed_time_total = 0.f; - { - // Elapsed time for the already adjusted extruders. - float elapsed_time_total0 = elapsed_time_total_non_adjustable; - for (size_t i_extruder_by_slowdown_time = 0; i_extruder_by_slowdown_time < extruder_by_slowdown_time.size(); ++ i_extruder_by_slowdown_time) { - // Idx in per_extruder_adjustments. - size_t idx = extruder_by_slowdown_time[i_extruder_by_slowdown_time]; - // Macro to sum or adjust all sections starting with i_extruder_by_slowdown_time. - #define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \ - ACCUMULATOR = elapsed_time_total0;\ - for (size_t j = i_extruder_by_slowdown_time; j < extruder_by_slowdown_time.size(); ++ j) \ - ACCUMULATOR += per_extruder_adjustments[extruder_by_slowdown_time[j]].ACTION - // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. - float total; - FORALL_UNPROCESSED(total, elapsed_time_total()); - float slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(per_extruder_adjustments[idx].extruder_id)) * 1.001f; - if (total > slowdown_below_layer_time) { - // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. + for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) { + PerExtruderAdjustments &adj = *(*cur_begin); + // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. + float total = elapsed_time_total0; + for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) + total += (*it)->time_total; + float slowdown_below_layer_time = adj.slowdown_below_layer_time * 1.001f; + if (total > slowdown_below_layer_time) { + // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. + } else { + // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders. + // Sum maximum slow down time as if everything was slowed down including the external perimeters. + float max_time = elapsed_time_total0; + for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) + max_time += (*it)->time_maximum; + if (max_time > slowdown_below_layer_time) { + if (m_cooling_logic_proportional) + extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slowdown_below_layer_time); + else + extruder_range_slow_down_non_proportional(cur_begin, by_slowdown_time.end(), slowdown_below_layer_time - total); } else { - // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders. - // Sum maximum slow down time as if everything was slowed down including the external perimeters. - float max_time; - FORALL_UNPROCESSED(max_time, maximum_time(true)); - if (max_time > slowdown_below_layer_time) { - // By slowing every possible movement, the layer time could be reached. -#if 0 - // Now decide, whether the external perimeters shall be slowed down as well. - float max_time_nep; - FORALL_UNPROCESSED(max_time_nep, maximum_time(false)); - if (max_time_nep > slowdown_below_layer_time) { - // It is sufficient to slow down the non-external perimeter moves to reach the target layer time. - // Slow down the non-external perimeters proportionally. - float non_adjustable_time; - FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(false)); - // The following step is a linear programming task due to the minimum movement speeds of the print moves. - // Run maximum 5 iterations until a good enough approximation is reached. - for (size_t iter = 0; iter < 5; ++ iter) { - float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time); - assert(factor > 1.f); - FORALL_UNPROCESSED(total, slow_down_proportional(factor, false)); - if (total > 0.95f * slowdown_below_layer_time) - break; - } - } else { - // Slow down everything. First slow down the non-external perimeters to maximum. - FORALL_UNPROCESSED(total, slow_down_maximum(false)); - // Slow down the external perimeters proportionally. - float non_adjustable_time; - FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(true)); - for (size_t iter = 0; iter < 5; ++ iter) { - float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time); - assert(factor > 1.f); - FORALL_UNPROCESSED(total, slow_down_proportional(factor, true)); - if (total > 0.95f * slowdown_below_layer_time) - break; - } - } -#else - // Slow down. Try to equalize the feedrates. - std::vector by_min_print_speed; - by_min_print_speed.reserve(extruder_by_slowdown_time.size() - i_extruder_by_slowdown_time); - for (size_t j = i_extruder_by_slowdown_time; j < extruder_by_slowdown_time.size(); ++ j) - by_min_print_speed.emplace_back(&per_extruder_adjustments[extruder_by_slowdown_time[j]]); - // Find the next highest adjustable feedrate among the extruders. - float feedrate = 0; - for (PerExtruderAdjustments *adj : by_min_print_speed) - if (adj->idx_line_begin < adj->n_lines_adjustable && adj->lines[adj->idx_line_begin].feedrate > feedrate) - feedrate = adj->lines[adj->idx_line_begin].feedrate; - if (feedrate == 0) - // No adjustable line is left. - break; - // Sort by min_print_speed, maximum speed first. - std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), - [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; }); - // Slow down, fast moves first. - float time_stretch = slowdown_below_layer_time - total; - for (;;) { - // For each extruder, find the span of lines with a feedrate close to feedrate. - for (PerExtruderAdjustments *adj : by_min_print_speed) { - for (adj->idx_line_end = adj->idx_line_begin; - adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON; - ++ adj->idx_line_end) ; - } - // Find the next highest adjustable feedrate among the extruders. - float feedrate_next = 0.f; - for (PerExtruderAdjustments *adj : by_min_print_speed) - if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next) - feedrate_next = adj->lines[adj->idx_line_end].feedrate; - // Slow down, limited by max(feedrate_next, min_print_speed). - for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) { - float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed); - if (feedrate_limit == 0.f) { - float adjustable_time = 0.f; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - adjustable_time += (*it)->adjustable_time(true); - float ratio = (adjustable_time + time_stretch) / adjustable_time; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - (*it)->slow_down_proportional(ratio, true); - // Break from two levels of loops. - feedrate_next = 0.f; - break; - } else { - float time_stretch_max = 0.f; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - time_stretch_max += (*it)->time_stretch_when_slowing_down_to(feedrate_limit); - bool done = false; - if (time_stretch_max > time_stretch) { - feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max; - done = true; - } - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - (*it)->slow_down_to(feedrate_limit); - if (done) { - // Break from two levels of loops. - feedrate_next = 0.f; - break; - } - time_stretch -= time_stretch_max; - } - // Skip the other extruders with nearly the same min_print_speed, as they have been processed already. - auto next = adj; - for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next); - adj = next; - } - if (feedrate_next == 0.f) - // There are no other extrusions available for slow down. - break; - for (PerExtruderAdjustments *adj : by_min_print_speed) { - adj->idx_line_begin = adj->idx_line_end; - feedrate = feedrate_next; - } - } -#endif - } else { - // Slow down to maximum possible. - FORALL_UNPROCESSED(total, slow_down_maximum(true)); - } + // Slow down to maximum possible. + for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) + (*it)->slowdown_to_minimum_feedrate(true); } - #undef FORALL_UNPROCESSED - // Sum the final elapsed time for all extruders up to i_extruder_by_slowdown_time. - if (i_extruder_by_slowdown_time + 1 == extruder_by_slowdown_time.size()) - // Optimization for single extruder prints. - elapsed_time_total0 = total; - else - elapsed_time_total0 += per_extruder_adjustments[idx].elapsed_time_total(); } - elapsed_time_total = elapsed_time_total0; + elapsed_time_total0 += adj.elapsed_time_total(); } - // Transform the G-code. - // First sort the adjustment lines by their position in the source G-code. + return elapsed_time_total0; +} + +// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. +// Returns the adjusted G-code. +std::string CoolingBuffer::apply_layer_cooldown( + // Source G-code for the current layer. + const std::string &gcode, + // ID of the current layer, used to disable fan for the first n layers. + size_t layer_id, + // Total time of this layer after slow down, used to control the fan. + float layer_time, + // Per extruder list of G-code lines and their cool down attributes. + std::vector &per_extruder_adjustments) +{ + // First sort the adjustment lines by of multiple extruders by their position in the source G-code. std::vector lines; { size_t n_lines = 0; @@ -545,8 +609,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ int fan_speed = -1; bool bridge_fan_control = false; int bridge_fan_speed = 0; - auto change_extruder_set_fan = [ this, layer_id, elapsed_time_total, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { + auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { const FullPrintConfig &config = m_gcodegen.config(); +#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) { @@ -554,17 +619,18 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time)); float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time)); if (EXTRUDER_CONFIG(cooling)) { - if (elapsed_time_total < slowdown_below_layer_time) { + if (layer_time < slowdown_below_layer_time) { // Layer time very short. Enable the fan to a full throttle. fan_speed_new = max_fan_speed; - } else if (elapsed_time_total < fan_below_layer_time) { + } else if (layer_time < fan_below_layer_time) { // Layer time quite short. Enable the fan proportionally according to the current layer time. - assert(elapsed_time_total >= slowdown_below_layer_time); - double t = (elapsed_time_total - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); + assert(layer_time >= slowdown_below_layer_time); + double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5); } } bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed); +#undef EXTRUDER_CONFIG bridge_fan_control = bridge_fan_speed > fan_speed_new; } else { bridge_fan_control = false; @@ -576,10 +642,11 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ new_gcode += m_gcodegen.writer().set_fan(fan_speed); } }; - change_extruder_set_fan(); - const char *pos = gcode.c_str(); - int current_feedrate = 0; + const char *pos = gcode.c_str(); + int current_feedrate = 0; + const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); + change_extruder_set_fan(); for (const CoolingLine *line : lines) { const char *line_start = gcode.c_str() + line->line_start; const char *line_end = gcode.c_str() + line->line_end; @@ -602,9 +669,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ // Just remove this comment. } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { // Find the start of a comment, or roll to the end of line. - const char *end = line_start; - for (; end < line_end && *end != ';'; ++ end); - // Find the 'F' word. + const char *end = line_start; + for (; end < line_end && *end != ';'; ++ end); + // Find the 'F' word. const char *fpos = strstr(line_start + 2, " F") + 2; int new_feedrate = current_feedrate; bool modify = false; @@ -643,7 +710,7 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ new_gcode.append(line_start, f - line_start + 1); } // Skip the non-whitespaces of the F parameter up the comment or end of line. - for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos); + for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos); // Append the rest of the line without the comment. if (fpos < end) new_gcode.append(fpos, end - fpos); @@ -653,21 +720,21 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ // Process the rest of the line. if (end < line_end) { if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) { - // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" - std::string comment(end, line_end); - boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); + // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" + std::string comment(end, line_end); + boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER) boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); if (line->type & CoolingLine::TYPE_WIPE) boost::replace_all(comment, ";_WIPE", ""); - new_gcode += comment; - } else { - // Just attach the rest of the source line. - new_gcode.append(end, line_end - end); - } + new_gcode += comment; + } else { + // Just attach the rest of the source line. + new_gcode.append(end, line_end - end); + } } } else { - new_gcode.append(line_start, line_end - line_start); + new_gcode.append(line_start, line_end - line_start); } pos = line_end; } diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.hpp b/xs/src/libslic3r/GCode/CoolingBuffer.hpp index f85c470b30..bf4b082e25 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.hpp @@ -9,13 +9,17 @@ namespace Slic3r { class GCode; class Layer; +class PerExtruderAdjustments; -/* -A standalone G-code filter, to control cooling of the print. -The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited -and the print is modified to stretch over a minimum layer time. -*/ - +// A standalone G-code filter, to control cooling of the print. +// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited +// and the print is modified to stretch over a minimum layer time. +// +// The simple it sounds, the actual implementation is significantly more complex. +// Namely, for a multi-extruder print, each material may require a different cooling logic. +// For example, some materials may not like to print too slowly, while with some materials +// we may slow down significantly. +// class CoolingBuffer { public: CoolingBuffer(GCode &gcodegen); @@ -25,7 +29,12 @@ public: GCode* gcodegen() { return &m_gcodegen; } private: - CoolingBuffer& operator=(const CoolingBuffer&); + CoolingBuffer& operator=(const CoolingBuffer&) = delete; + std::vector parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const; + float calculate_layer_slowdown(std::vector &per_extruder_adjustments); + // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. + // Returns the adjusted G-code. + std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); GCode& m_gcodegen; std::string m_gcode; @@ -34,6 +43,9 @@ private: std::vector m_axis; std::vector m_current_pos; unsigned int m_current_extruder; + + // Old logic: proportional. + bool m_cooling_logic_proportional = false; }; } From fd16357b6e1566cd7b97ed7d3166044aa35d6925 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 26 Apr 2018 11:03:15 +0200 Subject: [PATCH 093/145] Increase z buffer range to avoid clipping while panning/rotating the 3D view --- lib/Slic3r/GUI/3DScene.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 6cdf2a3e5e..88137185b7 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1115,7 +1115,7 @@ sub Resize { # is only a workaround for an incorrectly set camera. # This workaround harms Z-buffer accuracy! # my $depth = 1.05 * $self->max_bounding_box->radius(); - my $depth = max(@{ $self->max_bounding_box->size }); + my $depth = 5.0 * max(@{ $self->max_bounding_box->size }); glOrtho( -$x/2, $x/2, -$y/2, $y/2, -$depth, $depth, From a4df0bdcc3a2b29b6f45a08d9279185811e2898b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 26 Apr 2018 12:14:49 +0200 Subject: [PATCH 094/145] Fixed division by zero in get_zoom_to_bounding_box_factor on Linux --- lib/Slic3r/GUI/3DScene.pm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 88137185b7..190e960529 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -687,10 +687,7 @@ sub select_view { } sub get_zoom_to_bounding_box_factor { - my ($self, $bb) = @_; - - return undef if (! $self->init); - + my ($self, $bb) = @_; my $max_bb_size = max(@{ $bb->size }); return undef if ($max_bb_size == 0); @@ -763,6 +760,8 @@ sub get_zoom_to_bounding_box_factor { $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane)); } + return undef if (($max_x == 0) || ($max_y == 0)); + my ($cw, $ch) = $self->GetSizeWH; my $min_ratio = min($cw / $max_x, $ch / $max_y); From b3859c49c1346d89c7a7e3d3ca12add6e34cb51c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 26 Apr 2018 12:40:17 +0200 Subject: [PATCH 095/145] Updated description preset line for each type of presets... Disabled m_btn_delete_preset for default and system presets. Enabled update of the current preset if it was modified and selected again. --- xs/src/slic3r/GUI/GUI.cpp | 4 +-- xs/src/slic3r/GUI/OptionsGroup.cpp | 4 +-- xs/src/slic3r/GUI/OptionsGroup.hpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 55 +++++++++++++++++++++++++----- xs/src/slic3r/GUI/Tab.hpp | 1 + 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 06929085c8..210f296701 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -201,8 +201,8 @@ static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); if (luma >= 128) { - g_color_label_modified = wxColour(253, 88, 0); - g_color_label_sys = wxColour(26, 132, 57); + g_color_label_modified = wxColour(255, 108, 30);//wxColour(253, 88, 0); + g_color_label_sys = wxColour(19, 100, 44); //wxColour(26, 132, 57); } else { g_color_label_modified = wxColour(253, 111, 40); g_color_label_sys = wxColour(115, 220, 103); diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 1e78a600aa..7902fa1285 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -468,10 +468,10 @@ Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int op return opt_id.empty() ? nullptr : get_field(opt_id); } -void ogStaticText::SetText(const wxString& value) +void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/) { SetLabel(value); - Wrap(400); + if (wrap) Wrap(400); GetParent()->Layout(); } diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 8d57420a36..f01ef671c0 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -203,7 +203,7 @@ public: ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){} ~ogStaticText(){} - void SetText(const wxString& value); + void SetText(const wxString& value, bool wrap = true); }; }} diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 4b2ecab60a..0095092820 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -218,7 +218,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) //! select_preset(m_presets_choice->GetStringSelection().ToStdString()); //! we doing next: int selected_item = m_presets_choice->GetSelection(); - if (m_selected_preset_item == selected_item) + if (m_selected_preset_item == selected_item && !m_presets->current_is_dirty()) return; if (selected_item >= 0){ std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); @@ -670,13 +670,52 @@ void Tab::on_presets_changed() event.SetString(m_name); g_wxMainFrame->ProcessWindowEvent(event); } + update_preset_description_line(); +} +void Tab::update_preset_description_line() +{ const Preset* parent = m_presets->get_selected_preset_parent(); - const wxString description_line = parent == nullptr ? - _(L("It's default preset")) : parent == &m_presets->get_selected_preset() ? - _(L("It's system preset")) : - _(L("Current preset is inherited from")) + ":\n" + parent->name; - m_parent_preset_description_line->SetText(description_line); + const Preset& preset = m_presets->get_edited_preset(); + + wxString description_line = preset.is_default ? + _(L("It's a default preset.")) : preset.is_system ? + _(L("It's a system preset.")) : + _(L("Current preset is inherited from ")) + (parent == nullptr ? + "default preset." : + ":\n\t" + parent->name); + + if (preset.is_default || preset.is_system) + description_line += "\n\t" + _(L("It can't be deleted or modified. ")) + + "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one. ")) + + "\n\t" + _(L("To do that please specify a new name for the preset.")); + + if (parent && parent->vendor) + { + description_line += "\n\n" + _(L("Additional information:")) + "\n"; + description_line += "\t" + _(L("vendor")) + ": " + (name()=="printer" ? "\n\t\t" : "") + parent->vendor->name + + ", ver: " + parent->vendor->config_version.to_string(); + if (name() == "printer"){ + const std::string &printer_model = preset.config.opt_string("printer_model"); + const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); + const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; + if (!printer_model.empty()) + description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; + if (!default_print_profile.empty()) + description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; + if (!default_filament_profiles.empty()) + { + description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; + for (auto& profile : default_filament_profiles){ + if (&profile != &*default_filament_profiles.begin()) + description_line += ", "; + description_line += profile; + } + } + } + } + + m_parent_preset_description_line->SetText(description_line, false); } void Tab::update_frequently_changed_parameters() @@ -1337,7 +1376,7 @@ wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticTex (*StaticText)->SetFont(font); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(*StaticText); + sizer->Add(*StaticText, 1, wxEXPAND|wxALL, 0); return sizer; } @@ -1802,7 +1841,7 @@ void Tab::load_current_preset() { auto preset = m_presets->get_edited_preset(); - preset.is_default ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); + (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); update(); // For the printer profile, generate the extruder pages. on_preset_loaded(); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index de04fc4cd9..1ed5d1b368 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -265,6 +265,7 @@ public: protected: void on_presets_changed(); + void update_preset_description_line(); void update_frequently_changed_parameters(); void update_wiping_button_visibility(); void update_tab_presets(wxComboCtrl* ui, bool show_incompatible); From ad9dca2bd937e55282dff6ffbdf571f9f9811215 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 26 Apr 2018 13:03:54 +0200 Subject: [PATCH 096/145] Fixed update of ranges for GCode paths colors selection --- xs/src/libslic3r/GCode/Analyzer.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp index 799bd6661e..b7ecee5a4c 100644 --- a/xs/src/libslic3r/GCode/Analyzer.cpp +++ b/xs/src/libslic3r/GCode/Analyzer.cpp @@ -718,10 +718,10 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ Helper::store_polyline(polyline, data, z, preview_data); // updates preview ranges data - preview_data.ranges.height.set_from(height_range); - preview_data.ranges.width.set_from(width_range); - preview_data.ranges.feedrate.set_from(feedrate_range); - preview_data.ranges.volumetric_rate.set_from(volumetric_rate_range); + preview_data.ranges.height.update_from(height_range); + preview_data.ranges.width.update_from(width_range); + preview_data.ranges.feedrate.update_from(feedrate_range); + preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range); } void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) @@ -790,9 +790,9 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, preview_data); // updates preview ranges data - preview_data.ranges.height.set_from(height_range); - preview_data.ranges.width.set_from(width_range); - preview_data.ranges.feedrate.set_from(feedrate_range); + preview_data.ranges.height.update_from(height_range); + preview_data.ranges.width.update_from(width_range); + preview_data.ranges.feedrate.update_from(feedrate_range); } void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data) From a2236559733c954fb61714b412db286891084782 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 26 Apr 2018 13:40:29 +0200 Subject: [PATCH 097/145] Inverted order of range items in legend texture --- xs/src/libslic3r/GCode/PreviewData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/GCode/PreviewData.cpp b/xs/src/libslic3r/GCode/PreviewData.cpp index 69fd3524da..c66de08edb 100644 --- a/xs/src/libslic3r/GCode/PreviewData.cpp +++ b/xs/src/libslic3r/GCode/PreviewData.cpp @@ -370,7 +370,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: list.reserve(Range::Colors_Count); float step = range.step_size(); - for (unsigned int i = 0; i < Range::Colors_Count; ++i) + for (int i = Range::Colors_Count - 1; i >= 0; --i) { char buf[1024]; sprintf(buf, "%.*f/%.*f", decimals, scale_factor * (range.min + (float)i * step), decimals, scale_factor * (range.min + (float)(i + 1) * step)); From 9548593b575689b6fbd6d670ce2b5509e3c51f19 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 26 Apr 2018 15:11:02 +0200 Subject: [PATCH 098/145] Forbid tabstop on resert buttons --- xs/src/slic3r/GUI/Field.cpp | 4 ++-- xs/src/slic3r/GUI/Field.hpp | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index d0a2ccec21..5a3b011d40 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -24,8 +24,8 @@ namespace Slic3r { namespace GUI { #ifdef __WXGTK__ sz = 28; #endif // __WXGTK__ - m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); - m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); + m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); + m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); if (wxMSW) { m_Undo_btn->SetBackgroundColour(color); m_Undo_to_sys_btn->SetBackgroundColour(color); diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index c7eb25c754..1856d94cf0 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -36,6 +36,24 @@ using t_back_to_init = std::function; wxString double_to_string(double const value); +class MyButton : public wxButton +{ +public: + MyButton() {} + MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, long style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxTextCtrlNameStr) + { + this->Create(parent, id, label, pos, size, style, validator, name); + } + + // overridden from wxWindow base class + virtual bool + AcceptsFocusFromKeyboard() const { return false; } +}; + class Field { protected: // factory function to defer and enforce creation of derived type. @@ -165,11 +183,11 @@ public: } protected: - wxButton* m_Undo_btn = nullptr; + MyButton* m_Undo_btn = nullptr; // Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. const wxBitmap* m_undo_bitmap = nullptr; const wxString* m_undo_tooltip = nullptr; - wxButton* m_Undo_to_sys_btn = nullptr; + MyButton* m_Undo_to_sys_btn = nullptr; // Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. const wxBitmap* m_undo_to_sys_bitmap = nullptr; const wxString* m_undo_to_sys_tooltip = nullptr; From 6467513f60c4a5a946a1c985e5981ebbd058e26f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 26 Apr 2018 16:33:37 +0200 Subject: [PATCH 099/145] Set default bitmap (white_bullet) when creating Field's reset buttons --- xs/src/slic3r/GUI/Field.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 5a3b011d40..5cae9172ef 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -20,12 +20,8 @@ namespace Slic3r { namespace GUI { void Field::PostInitialize(){ auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - auto sz = 16; - #ifdef __WXGTK__ - sz = 28; - #endif // __WXGTK__ - m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); - m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER); + m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); if (wxMSW) { m_Undo_btn->SetBackgroundColour(color); m_Undo_to_sys_btn->SetBackgroundColour(color); @@ -33,6 +29,12 @@ namespace Slic3r { namespace GUI { m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); + //set default bitmap + wxBitmap bmp; + bmp.LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); + set_undo_bitmap(&bmp); + set_undo_to_sys_bitmap(&bmp); + BUILD(); } From 19f8e0bc63ba9689f9988aaae426a6feaefc3a4e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 26 Apr 2018 17:46:24 +0200 Subject: [PATCH 100/145] Changed background color in AboutDialog from wxWHITE to wxSYS_COLOUR_WINDOW, AboutDialogLogo is replaced to wxStaticBitmap. --- xs/src/slic3r/GUI/AboutDialog.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/AboutDialog.cpp b/xs/src/slic3r/GUI/AboutDialog.cpp index 664bbd1bbd..3c359fd66a 100644 --- a/xs/src/slic3r/GUI/AboutDialog.cpp +++ b/xs/src/slic3r/GUI/AboutDialog.cpp @@ -31,13 +31,14 @@ void AboutDialogLogo::onRepaint(wxEvent &event) AboutDialog::AboutDialog() : wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxSize(600, 340), wxCAPTION) { - this->SetBackgroundColour(*wxWHITE); - + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)/**wxWHITE*/); wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL); this->SetSizer(hsizer); // logo - AboutDialogLogo* logo = new AboutDialogLogo(this); +// AboutDialogLogo* logo = new AboutDialogLogo(this); + wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG); + auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp)); hsizer->Add(logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); @@ -78,6 +79,7 @@ AboutDialog::AboutDialog() int size[] = {11,11,11,11,11,11,11}; #endif html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html->SetHTMLBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); html->SetBorders(2); const char* text = "" @@ -106,7 +108,6 @@ AboutDialog::AboutDialog() this->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this); logo->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this); - html->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this); } void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event) From 25d47c1da8bac02d5b1408d9a39ba7356b14fc56 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 26 Apr 2018 18:56:16 +0200 Subject: [PATCH 101/145] Fix of the new cooling logic. --- xs/src/libslic3r/GCode/CoolingBuffer.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp index 141d197ca6..b786f9bce5 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -50,7 +50,7 @@ struct CoolingLine CoolingLine(unsigned int type, size_t line_start, size_t line_end) : type(type), line_start(line_start), line_end(line_end), - length(0.f), time(0.f), time_max(0.f), slowdown(false) {} + length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {} bool adjustable(bool slowdown_external_perimeters) const { return (this->type & TYPE_ADJUSTABLE) && @@ -158,9 +158,9 @@ struct PerExtruderAdjustments bool adj2 = l2.adjustable(); return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1; }); - for (n_lines_adjustable = 0; n_lines_adjustable < lines.size(); ++ n_lines_adjustable) - if ((this->lines[n_lines_adjustable].type & CoolingLine::TYPE_ADJUSTABLE) == 0) - break; + for (n_lines_adjustable = 0; + n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable(); + ++ n_lines_adjustable); time_non_adjustable = 0.f; for (size_t i = n_lines_adjustable; i < lines.size(); ++ i) time_non_adjustable += lines[i].time; @@ -329,10 +329,10 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: // Movement in the extruder axis. line.length = std::abs(dif[3]); } - if (line.length > 0) { - line.feedrate = new_pos[4]; // current F - line.time = line.length / line.feedrate; - } + line.feedrate = new_pos[4]; + assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f); + if (line.length > 0) + line.time = line.length / line.feedrate; line.time_max = line.time; if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed); @@ -340,6 +340,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. assert((line.type & CoolingLine::TYPE_HAS_F) == 0); CoolingLine &sm = adjustment->lines[active_speed_modifier]; + assert(sm.feedrate > 0.f); sm.length += line.length; sm.time += line.time; if (sm.time_max != FLT_MAX) { From 4811abfa99a6286c2dbedc41bc7801fffd21af7f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 27 Apr 2018 09:54:21 +0200 Subject: [PATCH 102/145] Apply gradient to colors in GCode Preview --- xs/src/libslic3r/GCode/PreviewData.cpp | 40 +++++++++++++++++--------- xs/src/libslic3r/GCode/PreviewData.hpp | 11 ++++--- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/xs/src/libslic3r/GCode/PreviewData.cpp b/xs/src/libslic3r/GCode/PreviewData.cpp index c66de08edb..d431708c1b 100644 --- a/xs/src/libslic3r/GCode/PreviewData.cpp +++ b/xs/src/libslic3r/GCode/PreviewData.cpp @@ -99,17 +99,31 @@ void GCodePreviewData::Range::set_from(const Range& other) float GCodePreviewData::Range::step_size() const { - return (max - min) / (float)Colors_Count; + return (max - min) / (float)(Colors_Count - 1); } -const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at_max() const +GCodePreviewData::Color GCodePreviewData::Range::get_color_at(float value) const { - return colors[Colors_Count - 1]; -} + if (empty()) + return Color::Dummy; -const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at(float value) const -{ - return empty() ? get_color_at_max() : colors[clamp((unsigned int)0, Colors_Count - 1, (unsigned int)((value - min) / step_size()))]; + float global_t = (value - min) / step_size(); + + unsigned int low = (unsigned int)global_t; + unsigned int high = clamp((unsigned int)0, Colors_Count - 1, low + 1); + + Color color_low = colors[low]; + Color color_high = colors[high]; + + float local_t = global_t - (float)low; + + // interpolate in RGB space + Color ret; + for (unsigned int i = 0; i < 4; ++i) + { + ret.rgba[i] = lerp(color_low.rgba[i], color_high.rgba[i], local_t); + } + return ret; } GCodePreviewData::LegendItem::LegendItem(const std::string& text, const GCodePreviewData::Color& color) @@ -266,22 +280,22 @@ const GCodePreviewData::Color& GCodePreviewData::get_extrusion_role_color(Extrus return extrusion.role_colors[role]; } -const GCodePreviewData::Color& GCodePreviewData::get_height_color(float height) const +GCodePreviewData::Color GCodePreviewData::get_height_color(float height) const { return ranges.height.get_color_at(height); } -const GCodePreviewData::Color& GCodePreviewData::get_width_color(float width) const +GCodePreviewData::Color GCodePreviewData::get_width_color(float width) const { return ranges.width.get_color_at(width); } -const GCodePreviewData::Color& GCodePreviewData::get_feedrate_color(float feedrate) const +GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) const { return ranges.feedrate.get_color_at(feedrate); } -const GCodePreviewData::Color& GCodePreviewData::get_volumetric_rate_color(float rate) const +GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const { return ranges.volumetric_rate.get_color_at(rate); } @@ -373,7 +387,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: for (int i = Range::Colors_Count - 1; i >= 0; --i) { char buf[1024]; - sprintf(buf, "%.*f/%.*f", decimals, scale_factor * (range.min + (float)i * step), decimals, scale_factor * (range.min + (float)(i + 1) * step)); + sprintf(buf, "%.*f", decimals, scale_factor * (range.min + (float)i * step)); list.emplace_back(buf, range.colors[i]); } } @@ -408,7 +422,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: } case Extrusion::Feedrate: { - Helper::FillListFromRange(items, ranges.feedrate, 0, 1.0f); + Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f); break; } case Extrusion::VolumetricRate: diff --git a/xs/src/libslic3r/GCode/PreviewData.hpp b/xs/src/libslic3r/GCode/PreviewData.hpp index e9c5f75154..a7d77e0b93 100644 --- a/xs/src/libslic3r/GCode/PreviewData.hpp +++ b/xs/src/libslic3r/GCode/PreviewData.hpp @@ -41,8 +41,7 @@ public: void set_from(const Range& other); float step_size() const; - const Color& get_color_at(float value) const; - const Color& get_color_at_max() const; + Color get_color_at(float value) const; }; struct Ranges @@ -189,10 +188,10 @@ public: bool empty() const; const Color& get_extrusion_role_color(ExtrusionRole role) const; - const Color& get_height_color(float height) const; - const Color& get_width_color(float width) const; - const Color& get_feedrate_color(float feedrate) const; - const Color& get_volumetric_rate_color(float rate) const; + Color get_height_color(float height) const; + Color get_width_color(float width) const; + Color get_feedrate_color(float feedrate) const; + Color get_volumetric_rate_color(float rate) const; void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha); void set_extrusion_paths_colors(const std::vector& colors); From 6d38943222d45415316e102c14119c837bef499c Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 27 Apr 2018 12:29:18 +0200 Subject: [PATCH 103/145] Fix & refactor legacy datadir dialog --- xs/src/slic3r/GUI/GUI.cpp | 21 +++---------------- xs/src/slic3r/GUI/UpdateDialogs.cpp | 32 +++++++++++++++++++++++++++++ xs/src/slic3r/GUI/UpdateDialogs.hpp | 12 +++++++++++ 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 42a9310333..6bac39bd7a 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -53,6 +53,7 @@ #include "ConfigWizard.hpp" #include "Preferences.hpp" #include "PresetBundle.hpp" +#include "UpdateDialogs.hpp" #include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" @@ -480,24 +481,8 @@ bool config_wizard_startup(bool app_config_exists) // Looks like user has legacy pre-vendorbundle data directory, // explain what this is and run the wizard - const auto msg = _(L("Configuration update")); - const auto ext_msg = wxString::Format( - _(L( - "Slic3r PE now uses an updated configuration structure.\n\n" - - "So called 'System presets' have been introduced, which hold the built-in default settings for various " - "printers. These System presets cannot be modified, instead, users now may create their" - "own presets inheriting settings from one of the System presets.\n" - "An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n" - - "Please proceed with the %s that follows to set up the new presets " - "and to choose whether to enable automatic preset updates." - )), - ConfigWizard::name() - ); - wxMessageDialog dlg(NULL, msg, _(L("Configuration update")), wxOK|wxCENTRE); - dlg.SetExtendedMessage(ext_msg); - const auto res = dlg.ShowModal(); + MsgDataLegacy dlg; + dlg.ShowModal(); config_wizard(ConfigWizard::RR_DATA_LEGACY); return true; diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp index e11ecdf5e4..62534e5981 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.cpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -12,6 +12,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "GUI.hpp" +#include "ConfigWizard.hpp" namespace Slic3r { namespace GUI { @@ -201,5 +202,36 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_mapWrap(CONTENT_WIDTH); + content_sizer->Add(text); + content_sizer->AddSpacer(VERT_SPACING); + + // TODO: Add link to wiki? + + Fit(); +} + +MsgDataLegacy::~MsgDataLegacy() {} + + } } diff --git a/xs/src/slic3r/GUI/UpdateDialogs.hpp b/xs/src/slic3r/GUI/UpdateDialogs.hpp index f12fd3333a..e339fbe0d3 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.hpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.hpp @@ -85,6 +85,18 @@ public: ~MsgDataIncompatible(); }; +// Informs about a legacy data directory - an update from Slic3r PE < 1.40 +class MsgDataLegacy : public MsgDialog +{ +public: + MsgDataLegacy(); + MsgDataLegacy(MsgDataLegacy &&) = delete; + MsgDataLegacy(const MsgDataLegacy &) = delete; + MsgDataLegacy &operator=(MsgDataLegacy &&) = delete; + MsgDataLegacy &operator=(const MsgDataLegacy &) = delete; + ~MsgDataLegacy(); +}; + } } From ad54210c3ee75f148bd98d1f8f773a3986dff008 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 27 Apr 2018 12:56:35 +0200 Subject: [PATCH 104/145] 3mf I/O - Added import/export of layer heights profile --- xs/src/libslic3r/Format/3mf.cpp | 166 ++++++++++++++++++++++++++++---- 1 file changed, 146 insertions(+), 20 deletions(-) diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 7b5bf7e8a6..dde64a28f6 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -23,6 +23,7 @@ const std::string CONTENT_TYPES_FILE = "[Content_Types].xml"; const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; +const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; const char* MODEL_TAG = "model"; const char* RESOURCES_TAG = "resources"; @@ -315,6 +316,7 @@ namespace Slic3r { typedef std::vector InstancesList; typedef std::map IdToMetadataMap; typedef std::map IdToGeometryMap; + typedef std::map> IdToLayerHeightsProfileMap; XML_Parser m_xml_parser; Model* m_model; @@ -326,6 +328,7 @@ namespace Slic3r { IdToGeometryMap m_geometries; CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; + IdToLayerHeightsProfileMap m_layer_heights_profiles; public: _3MF_Importer(); @@ -339,7 +342,8 @@ namespace Slic3r { bool _load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - bool _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename); + void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); // handlers to parse the .model file @@ -437,6 +441,7 @@ namespace Slic3r { m_curr_config.object_id = -1; m_curr_config.volume_id = -1; m_objects_metadata.clear(); + m_layer_heights_profiles.clear(); clear_errors(); return _load_model_from_file(filename, model, bundle); @@ -489,15 +494,15 @@ namespace Slic3r { return false; } } + else if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) + { + // extract slic3r lazer heights profile file + _extract_layer_heights_profile_config_from_archive(archive, stat); + } else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { // extract slic3r print config file - if (!_extract_print_config_from_archive(archive, stat, bundle, filename)) - { - mz_zip_reader_end(&archive); - add_error("Archive does not contain a valid print config"); - return false; - } + _extract_print_config_from_archive(archive, stat, bundle, filename); } else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { @@ -526,6 +531,13 @@ namespace Slic3r { return false; } + IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.first); + if (obj_layer_heights_profile != m_layer_heights_profiles.end()) + { + object.second->layer_height_profile = obj_layer_heights_profile->second; + object.second->layer_height_profile_valid = true; + } + IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); if (obj_metadata != m_objects_metadata.end()) { @@ -609,23 +621,90 @@ namespace Slic3r { return true; } - bool _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename) + void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename) { if (stat.m_uncomp_size > 0) { - std::vector buffer((size_t)stat.m_uncomp_size + 1, 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) { add_error("Error while reading config data to buffer"); - return false; + return; } - - buffer.back() = '\0'; bundle.load_config_string(buffer.data(), archive_filename.c_str()); } + } - return true; + void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + 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) + { + add_error("Error while reading layer heights profile data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + for (const std::string& object : objects) + { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + if (object_data.size() != 2) + { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) + { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) + { + add_error("Found invalid object id"); + continue; + } + + IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id); + if (object_item != m_layer_heights_profiles.end()) + { + add_error("Found duplicated layer heights profile"); + continue; + } + + std::vector object_data_profile; + boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off); + if ((object_data_profile.size() <= 4) || (object_data_profile.size() % 2 != 0)) + { + add_error("Found invalid layer heights profile"); + continue; + } + + std::vector profile; + profile.reserve(object_data_profile.size()); + + for (const std::string& value : object_data_profile) + { + profile.push_back((coordf_t)std::atof(value.c_str())); + } + + m_layer_heights_profiles.insert(IdToLayerHeightsProfileMap::value_type(object_id, profile)); + } + } } bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model) @@ -1429,6 +1508,7 @@ namespace Slic3r { bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); + bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model); }; @@ -1477,6 +1557,14 @@ namespace Slic3r { return false; } + // adds layer height profile file + if (!_add_layer_height_profile_file_to_archive(archive, model)) + { + mz_zip_writer_end(&archive); + boost::filesystem::remove(filename); + return false; + } + // adds slic3r print config file if (export_print_config) { @@ -1736,6 +1824,44 @@ namespace Slic3r { return true; } + bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) + { + ++count; + std::vector layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector(); + if ((layer_height_profile.size() >= 4) && ((layer_height_profile.size() % 2) == 0)) + { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single semicolon separated list. + for (size_t i = 0; i < layer_height_profile.size(); ++i) + { + sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]); + out += buffer; + } + + out += "\n"; + } + } + + if (!out.empty()) + { + if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print) { char buffer[1024]; @@ -1744,10 +1870,13 @@ namespace Slic3r { GCode::append_full_config(print, out); - if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + if (!out.empty()) { - add_error("Unable to add print config file to archive"); - return false; + if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add print config file to archive"); + return false; + } } return true; @@ -1832,10 +1961,7 @@ namespace Slic3r { _3MF_Importer importer; bool res = importer.load_model_from_file(path, *model, *bundle); - - if (!res) - importer.log_errors(); - + importer.log_errors(); return res; } From b67064ef81334eec4769d35da4285af97b2fe07a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 27 Apr 2018 14:08:22 +0200 Subject: [PATCH 105/145] Keyboard capture by 3D view on Linux --- lib/Slic3r/GUI/3DScene.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 190e960529..17fcd46242 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -389,7 +389,7 @@ sub mouse_event { $self->_mouse_dragging($e->Dragging); - if ($e->Entering && &Wx::wxMSW) { + if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) { # wxMSW needs focus in order to catch mouse wheel events $self->SetFocus; $self->_drag_start_xy(undef); From 285ded8bbbb2be86d1be57cff948f56597672aa6 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 27 Apr 2018 15:19:07 +0200 Subject: [PATCH 106/145] Add documentation for system profiles, snapshots & updating --- doc/updating/Updatig.md | 52 ++++++++++++++++++++++++++++++ doc/updating/setting_mod.png | Bin 0 -> 4050 bytes doc/updating/setting_sys.png | Bin 0 -> 3863 bytes doc/updating/setting_user.png | Bin 0 -> 3957 bytes doc/updating/snapshots_dialog.png | Bin 0 -> 78805 bytes 5 files changed, 52 insertions(+) create mode 100644 doc/updating/Updatig.md create mode 100644 doc/updating/setting_mod.png create mode 100644 doc/updating/setting_sys.png create mode 100644 doc/updating/setting_user.png create mode 100644 doc/updating/snapshots_dialog.png diff --git a/doc/updating/Updatig.md b/doc/updating/Updatig.md new file mode 100644 index 0000000000..3ce1f109c4 --- /dev/null +++ b/doc/updating/Updatig.md @@ -0,0 +1,52 @@ +# Slic3r PE 1.40 configuration update + +Slic3r PE 1.40.0 comes with a major re-work of the way configuration presets work. +There are three new features: + ++ A two-tier system of presets being divided into _System_ and _User_ groups ++ Configuration snapshots ++ Configuration updating from the internet + +## System and User presets + +- _System preset_: These are the presets that come with Slic3r PE installation. They come from a vendor configuration bundle (not individual files like before). They are **read-only** – a user cannot modify them, but may instead create a derived User preset based on a System preset +- _User preset_: These are regular presets stored in files just like before. Additionally, they may be derived (inherited) from one of the System presets + +A derived User preset keeps track of wich settings are inherited from the parent System preset and which are modified by the user. When a system preset is updated (either via installation of a new Slic3r or automatically from the internet), in a User preset the settings that are modified by the user will stay that way, while the ones that are inherited reflect the updated System preset. + +This system ensures that we don't overwrite user's settings when there is an update to the built in presets. + +Slic3r GUI now displays accurately which settings are inherited and which are modified. +A setting derived from a System preset is represeted by green label and a locked lock icon: + +![a system setting](setting_sys.png) + +A settings modified in a User preset has an open lock icon: + +![a user setting](setting_user.png) + +Clickign the open lock icon restored the system setting. + +Additionaly, any setting that is modified but not yet saved onto disk is represented by orange label and a back-arrow: + +![a modified setting](setting_mod.png) + +Clicking the back-arrow restores the value that was previously saved in this Preset. + +## Configuration snapshots + +Configuration snapshots can now be taken via the _Configuration_ menu. +A snapshot contains complete configuration from the point when the snapshot was taken. +Users may move back and forth between snapshots at will using a dialog: + +![snapshots dialog](snapshots_dialog.png) + + +# Updating from the internet + +Slic3r PE 1.40.0 checks for updates of the built-in System presets and downloads them. +The first-time configuration assistant will ask you if you want to enable this feature - it is **not** mandatory. + +Updates are checked for and downloaded in the background. If there's is an update, Slic3r will prompt about it +next time it is launched, never during normal program operation. An update may be either accepted or refused. +Before any update is applied a configuration snapshot (as described above) is taken. diff --git a/doc/updating/setting_mod.png b/doc/updating/setting_mod.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d3b7e7bd40138e235882b131e1a9b1b7f46c97 GIT binary patch literal 4050 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 HeI5QkK~k^x literal 0 HcmV?d00001 diff --git a/doc/updating/snapshots_dialog.png b/doc/updating/snapshots_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..d4d28955057976f1310021e79600df0ed4681bb6 GIT binary patch literal 78805 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

O)rlq3Rya0u8?v7`@x z+0VaM%M0#Bp}wKsj_t;l(adCG3jI`LJ%f-Nt;1zcbqrANck~U0e+N1q_oN@$mi^uq zsUa!8D}M38<$p<7l`ts>V19c^~+#yu3>CWLH{m+6QmgPF!*%vC~K` zN44Twz37doK%q3e?iIN%s^Xs#&|hkRA$HRGJ8Xy*_&80 zLE$w@=Z|AYj|s9#$(oRwlk9$#bqHjq&Ms({)hTfGyt8rbvJpXh#Fnd3`2ZVTmsxN6 zJ=#UtV*5I!p~T5l9og(`vGrfr;g);kVL<3pUnFcuN@euB!p=}=;z`?we!xAJe+Ko_ zqi*TvuHovewAQUKJ-_%kbAz9$d54NG&pqyF4@p=9&y3gTGf6xZ)4jZTED!KRM^^{I z1bI{DQgfUI>9RiU-N~ba$P(_!?{-BY9(LzEZ0wzx(^1n+4)`W?N^2blYDOf`(r*ag z+|I@dZdW6d$EQ)3QgmZPPdgnQ50wQP`QX6>AMH%R-EBsx+neRHgXNd>M%Q9tb$Dch zG%;~yw`#ZCaz~CryO~l>1}kMpz*JT`shetHc4vh5ukw9Q`lqGoaP3dz=3xuu5V4#5 zgC3hX0UFZh)YQRzbtc&yRn7pT9&KIbuX}qong!~WO!1Gbt(Z9Eaz1T%koNbxPip6w zXfQF-lN=X+*%C}ktPGI;;n*C6p99}ts2v79PN8w2pg&51I#DnmrSv+cFrcyYho>-< z7c-zy^gDrT3PX4?LpVl{BL;i;CJ~V)d9};IiWyPa*(_ozC~ z7n2QC=OMl(U>n5hjg@}=F z&#qkH0V;kG^~|OG&kj+E<>Hhe01=m==WOM70DqunK$`}f$rkW^>AXG|N1W#bGN4X# zIFdfI`>M7M73LO2sPdMTN=u3U@bk{t&N>*qN!i30sy;B?hI$}6OORxE%!bb#GSE}g z8-ar)Vg0Q-hye0DrT{^urJ|_2hUu)3uwcP#1soAcbC&R*44y)Z>`|O4a3KW)sAbfx z(66>Ha9gfWpV_1ZPD<>buG{iwqm$Lx!~JMdX!gB?A5Es?oO5G&W}6(!hahb1FfGt5 zA6NA2P{00>{eySl2zBf0bkjFP0MyX!ts)mw9rFTwVGMAW%5YVNtiJ3l4c1MGRqEAQ!Yz^qVUe*r@2XG3yFm3o)S&mur zj((CFT7)uO!SIa2MMMkMiuj z7N<4nXbwS{&cM7j>E9|^7KLI*30IH5^kcfk`Ly)J3UhV0)(Vx`%R0MNQ5IkPLS!oW z%8T}$A&n#6g2Tj+$F`I zeji|KJriGNtt7OQ*)=PsfxmM7t2XxzIYzl{3>6o)f)jbrt_tyK_P+fZI%K-df*e`c zRT6M3D!m(hpBD7B5b;Bi$zJ@oGeo2XPQ~c@K}KHUe|V0&>XCgMnQmi+7TTW*bIkD* zKseg^p8zU9671Lshy?_KLS+9!eAiW==}z1KuEBYk1w46vpdHOc=kp$1IQN@9%me*~ z&-Mn~*68CFd*NnNb?r49^jEp^@-H>=*n4kU0nsQo=@t1?v>j+bsAKB6i|g)w$3{T< zW}3(4DeNv`9M66loAV}9z8Rxr4M5+t1>YcmeKsF8!aaYdij{7ghM*CR=hZr^ZQ-GL z&Sx>7@e~X$?eC2_$zQvjqUEelR3ByXzu0W(E^<#<@WoiX6PA2^e3fv#`+N7J-!&O) zqgR$Cy<5tHBZXji3Z5-J7|atxY3@bOJ^+p}4&(B-jRJk&zo6OGf2L$75LDKv)cxlp zN7M%CNj8s7+Bs#ZjDd7aK_Hal`NmE(Qo?^hukt6FsbJ`Md5jV@g%5RtYyuggtyzc) zc^M`jAi{`QVE_5F+* zp?D%Y=6xg9>Gg!dN~0YbY#N&o8xSb(Y81WXK5n6k0_GJ_ffO}NJ)rmSoTm5!Lp`!$ z^m6$%o|*Ta&f9}frb9U2tK5pR-$CE(TpUGrXx$01l{z+(i6BqL=EX&obSnJ_AGrY;U>>2hP0 zmg;s))tKMIHUI!`<>vBr|ulqRM2ygL#cC}uUvF)J2Rukm#e zsXrH1UHxRVZ;@;-BqK|7pjVHiyVVB(A5*!4&oL8K>tlV#vm=jr5ywY%sk#Mach$!n zh5W8E0E*EZ%}%Z7(dVxAWt=)-+eDnaduB2A69GUN0Ycx8f2nQRNeoA90ksIimK4cz z+8tFf?65^xH@pa)Y^c1}S+17wL|T9>laFao0@4^3%vd;H60$zkh|+S2nC|b zS~aZt#*QQ9bIwRZ>+K~BTyv90*A#UObi-TPt2z9JzOhNIKyFKGjlRm$wEk1$vV=u; zWzAE906E(JorRSI2Q4T?kL)9*d&+-4LK8+PnZ_Pg3(5b5v_0s*3b4B`O#O28G&XIh8Cu5>P-+6SH>fnqeDZ zG_ZkfQCyH?g}D!BdWd@_WtRuDi1YP23*OGb(D-fW2-zWRxoc(r`=Qo%2Sc=B>}$M3 zS|x`1hbQ6~^Gl!mBB8|PY+8ngZZ)spF`w$GF;&7rD$zC~^kWp`lfKK>Nb=loe{!QM zAUsd)9+x|n8lL=JG`iGHoTcZrx=W>Y%q9~}iff$17dY@rpqg~#(G)Fvp*>WzYM(*i z5lWdaCE9=&(=CUDtEkn#X8{0_Q~CLW%ATqP3aaPp;VP8_%*2&Y`J5J*2I_}2=uYg< zL~mby?W=g$!<}?hV$Svv5XVunToinCH6VmortT|-$-~DAe>T(+xw%};Q+#o>8k3iQ zIHB!xzm`LPNLc&sS_(Eld)7iL?KMkrw?c55=N^F<7qF`7l)G`tLVb+AA4Rm#@|ZiW zy>AhjPlJiDfb#kX^LKclyxa18DgHKBDK@B=9iVc88$KO8eX&=zgCZ2=wEuPM8Yqn< zhHGUG}xmBqGm z=I|7T{cfWYz7e$d%>Bt}QIs8M{pFCaKS5LV6}r^_9Gni8Ev5ZbOC_FH%Rd(K>fNqt zuTvOM1+F?J)Sb&mr=OMQdxbDpo!Uv!htn$EJ3xSqLso66n(c&HU>3vYaG#BQ&Nd&^ zQ+Ke@P}wAnz00Z=36m;R#7gDC~AMaqXvxQ|%FC3(-6W!VG_W12jL_`IuSbNg_P?w%#7nJhv zb8nr~=c$QmHS5c_2o4Ne0)Gm(sxE!ZxAvG-uos0t=S_VL>X)`=t*o3L9^0N+81NgU zdp|uwVq3V(i+%Yt>3nSphVgQ%G5mo5m%p%z|1v6Phh8g}KG$UI+(U<2N9_{;fXn*f z6wRu^Kzt^?93PwX5vK7TbDD4RCPyHSWIK`_dM_LU=zQE^)=~+j?I_oGOg2h^z^+7l zUOTG0jl81Q&HYWti}vb9{_?tD=3*bn(3&HRX4A?XZ*T5ksvpCmS$l&7=73nGy6JB1 zm}dJJ1~+X-xnm!i&7}?FfDMbvVHv%Yj_~XbXj^miAZ-kye@prE!!QS& z&jr_wfN0j07duqg{}`Djv7a$ILr&AndY3Fl!G~g8cgxUa%LUhzM(|| zOVGoh`-CfvSF=DnUas$vtO)alNAy-sF^!5N1^bG00ko1ylh#3m9+jf!kqCV`MGu+? zy%Pm}IDCI4Ej=0wb2xlYB?Y*G)y^DF+v!Nr8Ijo#96W(+o7&{UE4wCu#?KRtT{azc z6xLt9ITVwxTJ|kX>IIl9oD zq?ZX&ppIxTAJ0jUS?&pGfI1UlWK>jemvljI<#DDoE$&miFbf+SmcF-KqvPZ1A}Ek8 z8)+kI$VX}({o8zIYU-_sNl;T6c0^iXyy$TdS8a5?MA#j_KZuD zeFb|g$@{s#O)+2tK>qh$O{&Sx3Kvt}u?A2IJudfVRz&1Cx;J#r5A&P@TD z6a$Elu}0|NUop)p)uJYz-@nQ(EMJsZo@5MMaLcqwZ0se-X%s1w)#T0Ut2&6OC6B1arvT+ok|+>nhkm+4DS!BOYac`Ml55=^y;>##fq;K4|{#tugY7ckAyU`Hw9Zp`0d1wv(8L`ZO3 zu4&M{^9 zj!#iPtUWq)1nOSyUh3~uxhT~2o)QO5jw+%bGBx*!QOSX_=SCSZJ za*8U7aF$9K0-lzw5;X^3+-#$Xur z#N3tiyFuw19d4u?vJ=mpnr<^Uvk&a1)SO?FZ8-0$*6bYbY5XHGHC0}JH#Sw4S4MaL+q!!DASyR?-qE$kah<^_ zeEu11*uIG9SY>7bR&P8qWm-{xNNh2Z%P16y3V{GuVzdDx#LN*?L!c^V3>V`hC22he zh6KzxL`%p$dUURpH~2E%H00tX5L5#E(R*lEA0we8?m;Qqj`K&eu>ed&P2$4AD?p4} z+LzEiB+Vx=4HTkEhle;-RvtHOIXr3gjsSuP0M8LiLyKJ(rk$}zB3SK9Ay(tO3)W8t zY*|K3XK7wWWq(}sMl1?pN2HH~nwN`P@s^>0l?|j)ng2ri58pu5;6JX$2sN zKS@HU7zpa62l;L@VC>TW8oQo<$!K0~;DYhXURPNz$+|X(f`dfd_`)?=VS)AA`f?*k zfBw7Uyz6UoL}BNS8c^sFZGj+^)Ox6UR#6ZyI2p_)IsaKp+smy?NZT5Wc=z~jR6oja z0m7kFzuU^yp)yR`0df&K#6r9nNCD7K`@n;wX4xwqH`CRq?Vev_2H)lmffgB}U=*yz zyl+y1jSJvIFb;{5c<@|wI0R;RZ4IidFW*lHgK2%q1L* z;BgI)WSMVpKnL5Aca|-j){Y|L#|ily4L74!cBRWc!uKWX{D|A@oFZTu>j^qj9r)X- zEDj(l3VA>WX5oSP`gg+MHgH7sGgoGx3UF$p0p8ITo~2!p@lN9@xp5s4C>^cFfeboze zF@KifqwPohCtFQzS;)*Mzz%Fsu$7K??$hhNk0@Ggs9^>ur$akQR4x2-K#*jx8qfAS zy&9WCX19N_>%&TFA;r^KtQ`y*xAS?xs9=Vs0h%<}4Y8F#_ecJcS^$kf=c9dR&$F7= zJcfE;#Z&N^z9s5&YIg+?oFT7A+n*`x6vPoUhig&yhwSOtm znlTxQhZXc<8b1Q;_5I;C4%AGngtc-zo21D`fUnc%9_V~`RQy^ZE%@tG$uKazEFUo<`%No=eaz{)fbr)mFM-;E zRJrdhUU9>zXBkslftK6>L z)9o3eF!ohPPkHsQc*dgFRFK%)XRp?p8Nz`Cx|RYEU?FdDa5duKQ`V4q6bc9nvo1of z58iqx@Ycq(zh%i&W89GuJpIA)|8au#p?;ZwP4D^*i%DhAVQ3niKsP0&F z&pihM!fRj|g1-(3P9@w+**{)SMESTt90h_m@$NqVl>_9N_$DiiIkLaM+-nhgIi9H; z4@>cGUZs7EAZzzrHp0%p!PzAQAYmBj-&Nr}A}7orq6UE@EcT_~*r)h793IbKx$+yb zHhLI?6{sz0^4MU}3mF|m@9(d~h7LvGfNSE?xEEP0d1JIag#+=u`prae$aR`>K~DJb zv`Lv32IuKE{y%X38phReIUxWOh*!h!*`gm+*+ghZ)n8g-h>8^1FR^DH6jsI27G zaX#&+$&V1IxQAqO(M>l9&;ZhN8MB);WWR5L4n#)nJ6?2KSX@He-IKL;_?ik-LJ*7! zu#UJ+^0VRv2Q+lI%}(}9U!==U;A2B6NIkGNOQ{-Sx0+ulQ-ewR2VlSbCtx3K@ln&K3vZm(K&!M4r@p4a#RKJ$z z=E7&GGrdVmtZ0rjkJ4b$%=zRLfIBSbk+$Z+$ox{BqDNjpg{izaJRX%|_z`@MBmD3q zmYxdu;V_DxuuN!Wac>xWe|RRef~5ys!!morGkYBz6G4&5DBGZc)x!$iwQyfkT08_>{Jj1N^{umN(NJfvZR-S^y1)NM|{G=V>$4 zNrd@GWY|fh|3sw!eKQTNdgLcUccmXyOpk^gC`FDtrlNA!nC+f(HxPfyiB^!ZUnDMa3WQAr3>bi05$7&>IZU z*x<0@h4T>nzitdYG(dG3L}0NkQ16}|h!g*tp`=RmKab4W=>9|SY*O*6hv|TX)G882 zYS<#l@0(BS)pxJ*GB3Emr(}|&HTS=CO@RM;qHrXq5!VmoZ4v`fcY}ZuCMpjnp>v-q zq;y`=kG_-pFhH1Pg|Z6LP3|6tUl%tB-6G{oBIU>KnCk%cRC{nvtc615C}Fxe(3?t% zleON?b_+gbdo=g1RAQT6V%u^x3}C;=(qHIJoA(fn=t}5`7)2ibE;d1uD{%K4IqXO_ zX4R}oRf;Y)f4;w&S41RfT1!q#mm+ZDCyAONkNREm;yY0M16bxvzV*D;6Lpf9dJNMO zoM{<%k#DtJ{^QauKK-GIMT3DeVgp}Usm3yk-|im>>K``P}7XgYY{=B}J| z5(@ws3vku;puzVeLisI2W<+k0;mK{)F#RlQw(i5K{xbt{Z-1c6z;a`sYX_x3#ubnf z&l~p?83`!xOxzP`L$%tl9P%{?nR3whV|q+3nlVA*p*!g?k65QvMbK2*K3cjbPPE#< z&*ecGCKrI@M(W)4-Z>r1o*0tLJh>4_OF_$75>SSJ%ux2M0A&#H3-~hC$ADW9IVVR} z?3!n;eT1a;(}U+llk|5ZWm0EQ7DJEGeIe#4uM!fy0G! z%3j1Km3b{fN}h&y2yIJkhmXswYhaVn+x_Qz*mH6)H6Rx6bT~rpz8woJ zP_fNLV3eB-=G${XYeivM$e*@2;3OURT>IbQ0cU41Q%JB(-oMJq75t*qt3;^sk%EJq zHYuq3u|8~$qPu*HR2Q6#(T#kpl!~i8gkZXR(Oyx5>sfOQUESX{U%)c2qh@x{1Jyea zyaH4p!y~{B68rT_r+M<$^Z2Z;v@$*->qyA*;98kz&V;26pW>S^utNd9 zJN?4nS3{p$LzXM1q^uxw*=0teSwaqm1Lpby@6_>XxdHEYQLQ6iiA5={b6eu)oaadL zYN=v+^(eK$(o7P6HhV>;z-d%97t`M4_X3rvb)*syl%zF&Dj@Iy2Nntc6m7>En1U0i zjsEj9=Q~kWbjM{=fiMK~h-a-N55U@iBdV})EWT}iH^jozwt;GT$Uy`&DyE+ESw$=s;(M@OI zmr^#oHu0~cr9*dPn-3^?Z+5l|PjGj4ngPPAFevwd5dnwi=J3rCwx3^F_RTfW{!#Cq zy!6?d|Nd+zyy@TE{RozbJHgZ8J>XVRv56XFo406u9@y0Kgk)6zA z%?QyBO$C3QKt8xJ{uY{gfWPaBFxl%o6eXvb#-fe`iA-=yWP)iNCs z?y0-iNnlifj^hr_3LueS8P;OU0Dp<3-bf+sDT$27gLm)CcZh&l3bdUf0ujrfAv}1K zv7mLfVS247ebQ)hC5NQJim5O8>+|}y=7F$)C#z#3Bd#PelHU_hlbRbdyvR+tN6(rl?^NrOL)JHk2aLX*rv9U z>E@MbQVRpQI`Sh2sBeRxYwOD}At0mauhB_t)cPQ;Ub+%=F4dH|)=LF41i4CTWIpj< zzeshu4vzPv_MA%sF$R`z`Q~O@@=LKcB}gh6E)}O?=LOK=fQw3DUQ3BYzp^}0CY5m& zrPybDHshjA+=zP9S-XmV*bPi}v)13-zqmQTnZ`A?XzaKrOOlxQd-4rfIMOHtKynmc)$oXkU-`DY>iWmtFB)v>P;-h3r9Lqg?tQ&h~%DDqbgmfU$QoLt-qIdxg z|1aFEWfdusOV=_g0~g&QKCTF(W|5uO16^nN5f%$|2-m~ZS5ugQdKuQ?=xfxoz*$zYQGtv50-xD; zotT4v^Ifv4sGNfTfe&`3gLlH2%oJZeREpmG0Twv7)zFRwtq6eUqw@PHD=gGJU;Dm%^iw59KK8@3%Z7pkca?`27=jm^xI449AcYD~| zzX^*cSQ&~Zq{bmVKYcdVAu!=0QS$B#r$rEtH+k|0Krtrc-z+^RtU*Aipow@DJ$+PL zXjz`+(z2$T=p$IS-ZPI0%BVzoI(}JC@Uy@Q#?vq$wgby;_n($q4Roge)03juCIg`> zMb$KGD$zOGV;K=ZdzoBC8}|qoD_@Wc_^Bdz5!-cu;&Y;>_4ci?S+DoY$ZXVaqSe~_ zmgB8Riz1%>hRTLh0)EEBjQMg$kKzvz=EAYQabLQz-A01o)o8+x?~Ck34SpN7-B%z6 z2K6g%Nn;4#$>VM{2f`w(mxcAQ+fS8O7rY=e&J;q*Bs{W z0DAGIp6;Lhpco;D9ouEPHG%3S`*4+YH1~D)(sAY4+0})_1@LW;&%PcqZi8MORjdD> zjvcV+KFHq;8*Pk~Io#YT*4NrV{I_r~E(z^Ynp5z;n42M#U*G6{_cYZ?S;RvERDW7fZ9^@f~Bk9 zYuE_W&7Nx>{_v*JbGq}>hoLdkqt}nkKAVDrK+WfGo-{M@_IXkMEXAAE?@3O{hcK|(h zxxpoOBm+_U7tyrjlhL&nlRxnFdr;zgzr6t>7)$u0p4VRclXsjxgvsTAs}m+f3JqGPR~V zx-JW4d}Y>cF*=AomC3dWl7_K;kw#mqAAp3jEM6MZ*!*d)2;>{K&)*n=CWFA_9`?ub zX^&thvi)AT@2PC2*@wEtJ6_JR4+(I#4q*3Gvv%`EV{{sgonMhGl~X7Dxq+{udmrcl zxpZuoE%EU5rBe8tePq}toj7{Ro?aMyaD!jNe9o50jTRgL6E=X%k)>KdB2jKyToSL~ z>dIFH^o{twAYNWVk{s2aC(V%?W+K+bsufJ}U+^Q{HTzB}hUMQ#WUdMrbcCt;n`snF zFS{$@2t?z%D;Wt!1Fd0QK#2e;!AwSpKwFalDcVdBT$Ko9Gznxv)&yi3!#6cS1or!n zzgrUPGaS7^KN-w{0I7HBc+ZJH6hf-sq*bKtx$Sw*-uG;IwK48)!lZ(SH}@8Wj`Q^m z=V7n@U(!O3eVh61SG>;dffhpS&*t?Qzq1jv5&(TorG>L(7}g-xD~`{~`}jc%2Ko+r zf-)RKi9iYs9dv+*Ty2O&mkC3P`X+biond`SLASqNFDu@cf&H8Xu%(`m15ttFE07*& z2MuUxKHj5#i!YOW%Kdo%5{SefAuW2s77Qp6_HX#t#0NWu&@#lmRVD{vssZXKdC4)X z5AlEB_`DbAFIPnw_$|;{oGfuK17ouPCbbkNOJc15Nniku*UArSWlWv2+6$dGHNik) z^QZbQ6~2ETKbJJdCG7)}H2`G4P^|OZ1~E#^IbP<@$={o6Vj#c;s9L^Gn-CE35tlL+ z{vzqIUqS#usIc86yCw6%1PVOt7eEhB07$#Iu+h=lFxL+1MSP!;8nr(_4V=*Z^p9L{ zU-tbc3waQNplzEA1g#0Upq3#Td;pshq--)T1|EZWhkU^gvpj*rtja$5UO&*TnBDbX zX(2)$ODF~fl4C`6_4WK@z`W|}zwx7edmL10N5v=sz-`h7HyLPlLsU4xeh&-X`{1D? zNeNZbBCr)km)x?u1qffZyqpk@$N2xoZ3Oa9@9MB+V$F0B6YPUIUA)#n5sF)8p2bf6wQl(C~lQ6`+Lx#?juw>~=C}{m#~(EK0v(@0zc^_Ex&ipTXa= z#^b?040wqk2`<$ZF1D?a!JAu0a+Qtf;1C0!9ZG5(Zcpr;1%jnUuP$X7XYw zKj!!K{nRA2?F9+Jc`NUK(qddjO8S?2L+_&wm%uhc%^b?VIe@4pbySzJ(+rrE!Ufr*_vGk* zlK4Q8MqV0w}vb2)|k)KI94|1LMCpURlCINO}>?AF0$({Ea{KO;EzE zmwx?1pY!<4mMdJ2cF-rnajb303TdJs=IS2*c&Gh65DSPwwC?eW2ZbF)luTc;vd)p`zq$UC_5FAd$bU|!?Xa}6Zx;N@*mq=*VOs?< zErYp^LDmv8@~{ku9+MnRSRBa_!Un*tljwu-8&IHRwx$Yr?p?!gS_x%vmHis9VJ2hg z1k6`<_ui%2splX0w)FB{(5tyEgx)*hNe#!{L1mC&;BJ1=k)cV5p(lV0bR&WKw5H4=y}E}Awv@9@--k=?d<%UZJ-%W%BI>o3R^Cf$!D{y!D!e^y@wq1(`>ZzVqToy8 zi1P`ImZy>+R#SAT|5o~K~h(hueXi7MY`pwgf(mm@%>wYMB zFSY;if$eQ}wz2WDRrQGlHe{P?7GW}aqQa$5F5Z($zk_Iv(L`_eDrah@rF^B z)4T+1*#BkZz^?)ZQF`&lH>RTI>FaQv($SfA#xkfe|6g zC&QK>S0yiwY5rh&YbgX9JoSNelven^GLty_mD_?U8vSYLa5TONm--a`G^T{%OoE|^ zj8EIags@F-gs<#CxJ+YNANMdd#n_2) z$GRI}C;yskbh^Ir(w!_u5gYr2zdcI0u1k8Wz223jejG}Ub1q)UV7r(uiE3NNPA%HG zE-Q6q7TWDnzJBXjvo^lzH?lS9D?ZU06zk11E=_~a{GR9Vcfe6+Vn*-h)7`*}l)!;< zJUx`n3y3sDL*&kx6}0gkZ#I42i65pgFG`P-JIQ^XTtiIwD1qbWq#Rz(71Uz0hzp>< z#d`oOPoR+j`#dDzdv1>q79yVp^vc0@SI^wo-5hH612e$^| zzJ!y!MUTGue7Cq68|Mqo&%YSzsh&$2uz`F~?|jW#!1931%r5-mQUXmqZo`#gQp&8s zRh}O5VfVS47)U`hA@gQvy`nzE4>}V;zoOPqf3ukW6nECH#9Fg+EP}jZxr<3bTO1v^ zJLIx-&7IdI)E?Pz^<#rw}+-Vn=KG{uu@s(x)9Hyxw7#7>J4-?WuX zZlyHdQvbY^t9WyjGJBXen~3u%2Hu{v`rsFw>}Gd;HWP(5 zG}(9PIf_lJk;Wv>6q5)mq3n-0J*(x&$8ztymG&>x_T^E3zVNRj%D(p8=BO5C+sBZ$ z)5MZIPYu<;BwH(3z6~+w{CFNkhe-CSNrOuZj9FTim{X!-!IrQQYV|j7L;?3rCG1W8Mw1~&( z-Np$K?6nGS!lFh|{u_Jm9aYu0?0arRK*>pRP*GGsl1Ppc1p&!9OU|HxM9DcQ0xB6r za*`}L3rG?a1SIF2;|99+Irn$(JEz}yw{O4OJ^GIx!@<~Y*lVvfSIw$#eX8bMF&~cb zm3-4J=6|D=9-Lh3RE(09x;>m}YOleyK1wAWf8kqba@x3Z(f#Xyid+N6#B0rwR6XlP>)E&%*h|R!B2} zoAs)*RYR(k!6O-As@a0uuJnp?X7ksPTerunDMDF43aMVw9ro3WL2?o$xM0TXyqB-3 zXi6MOsB-0#H9h+&v^eBmG)Axy_NnT!u7JCw>Dk9E5h4=Gf_=7d(@&q`a(YeGw)z#m z@1HWQvaRE)9$V?=sOFwogo4ANN&F?Lt$eQW6+_H(plI}?_{P+FIFH}T1D7_{Z_e{0 z9M-fA3gC25H$ASykral5E)>FVd(P8>17?D+o|{)JE7!5AXXjhY-8|P*D?ZQhCQZ8I z!kx)|LUP2h>7Q|GrLy4@*MGpKYPrtM!;Gc1{N8GnFTSO^prbKF!eXT4{jYhl1HN(t zJ-GKu(n1s3xnrZ075lWs<^yDRcjlHjwfD&>>&zo0T3>f-3fSF)1UB$woyy-t>y}_> z$NKJY@$|WOK{pL=2iA>Kw#Ghd#uEiv%seW!ZKOWSBLe+^m#<;}Uj^DQ2nNN*N0;25 z&|L8_??5hJ=Aj7)OnxYJ>~+6t+;&i7wsoa$OONl`!~?1LdT~CBu_6+ep5L$VW)r4S z)>O{j5i7t^^>vYk#irosdG-6>M_-!^%hrdl!pWxDUl)J!j4R_(OaE#f8e(TryAb9t zm~Q|C{(RcpY1nd{(EIL{DEU?F+-Q@*udvRaob|$ys?HxP&E$rr>dcVStBnbP zM`N~!Y_$$9yGlp&sXS&olA6lJ?E{^GOzs7tIF?!5Bbm9B5I!+5EgO_t__v2}6C`$Y-c4T%Tt!Eb$Q zqOn|h{)AwzCpLesNbchHLZ^%mw(n>%ZD0U{4Opu|1fk{A=6#f(R4`@g`%%vXzio;7 zz3S++E_|2oc9L#lBbx=u!FlmDKKd`mWy)ScNYYx2(*zswrKWJ+x@Go=`%eZe-3V|E z1jS6X8-8exXg7^CtKP3Fl7U!f^gC02q;5)sMyDJLyZX}p7-b%M^$ z^&lu#Zq_)J;qK$L0%?fr=>?UEX?tJYHbI}9U~(eeOdoismXk?qtOW_)A#hQ^w<5Ug z^ZitGu>Mi^&x7&v#4ENJkK%sW-Cxhkn5=9ee8G=f*(-HjfB`4yvlPnr2eGBRa+!aQ zFOqPO7Y@qvgXL+N?7&{c%Bt8a9pYKc>kJJ3ot>RVPR`D#mVVgYD`+;K5(kGbHZhU; zP51oGz-9n8tsG9(x~*S0iI`0!Pp|`J(qqE-gw$`DYH@VSN+wC%AS)#U8t9%sqAZsoWVVd9y?&XRa0e>nBwv$`Z}*-{NFR zoKeQ7b?)Wl?fg!6mV^Au98oHwIpd4o(Th&qZ{xPZ<$HnjK5Z5 z93Sg|EyX&cS>ku?-mQF1mc=<^4(2WrXX4PLo;Vp^$EbHC;m*7{s$HhfLf#TTX1o(8 z%jf0g#mc9ZOBzZX2OD+WdoA_zWp*eZSs?DnLvsx!+(ROwXD7Z4N73 zS69NEipDGC;gZ@9>Ha8{CBF2A_VsNJwymR4ucgkt%ZtZIZhz_{4Blu(Qf z1BO{W{#x^hSD|rypi(+GH?`h*Jmb?nabi3*O1?jvb3GDemAgoX3W*rN)n36=fl6=^x7e{sOi!Q}%{hjkfjrZ!~aC zTRQ^}M)-cJj(HoMURC9OAhjN^AQL8c_hG|aLg-QYFsUZr2r5FvtSn-!33YgUJZ$yN z9{zkIwPN+1@Vlq>h^V8@8T1lyk@bMr)g1A`w*L8F&{AFIeiSv9jCau41?DZKQ6uPr zEmrtxnRccZmDSMEWe6KTcuF^ZnGG^{y_a>vPPD1w8qjt>y_HEy>(PH z@czM1elT}nLZkVW){en_`bVK?@-Rwf(VzAOr9U=BK7YZSt6IsTLJN3Vm4?bHBM>aw zC|iphmHPIKv{C4{(fTvFwkAc*Zo3YN&pk7nJ=C)1Xs_VGq;hF|`?-b4<*v;AR7jRa z_+MP}wBGZrtjB>I+IV$H?VDRdVLbPF5%1Br70bFKtP3T0j2Xi|~c`($PLK;=R4 zeBw0<>qTF*I9-mMX~Lx6&#Q)J`M( zy~|mKay`rs9>Nrq_mxl#Yg_!)(*KFGp{KJvRF)8P?yzFmN}ZsyXkurTb0YJj>o#0d@IjwV%{DgSV*XwX1iFeq7VcJtzjG#?&(FNg* zMeQy}72q}={$WH%#P7ZDCFph|IVDEKq$Gveg1M4Moxx*K=HkkL^vuLj%MX7_ud;aQCErWUhgLVJ zTrC6R>-0n(>lJPYMOU&-7|s+q--V)OjL*H5?O)1fR+tN4-!jtPF`?>+hMLL1y_UNp^NNw}6ov&DFD z?Gg{&BUQt@-k~Sx5O@(g9qzqO+Zb7fs0=vYYGi6TnSy1BU#Z{@51Rn_82^mT zq4eV7^5#0`%B|H~KDXL#H!d#1y4b)tbT) zkL|i5;z>F7`OL4&Ov>SKd+x9Y6dlMShgsFTCw)|SL1;EszsYp4q;3%>xuS;h`3D4v zoXTFR5S6mfJwKPE))Te_*kr!ZE8@;dg=5g-ToZ2v69U~VI>@Bd9nqM&c{^p0@Ir&; zM4lQGf)x0L#-71xw+qJiY|=8+N=$KXE-sc#2$zDi{ zvw*(&k=bPyI*T_|{r-fHDW<9Ko;cWOSGF3_(R3Rf_rG+ba3NknvQt%*e6_JqYisowh_hpNb>P}if(-82IgYp2^zf@w_jgTw|Es2{SzgIxnPd`Kd+bqUN+$7QR0KyQbp=>Ew?^ z6YZVlryPs@eeA^nNk|QHcKM7y`r^)ldK&w^s2eYjNu@8iqvt2MxQR{NS``e2Zm0FW2jZ z9@o~TS0M}uD4YP?ENE{|^}NU^Yhg+gpP=P}W&2$KCiuKeV&H4dL!-K;RgbM^1jYqP zqJZbbeP|=P(5rf}L4}wCG~yQ~uc*-0HI4HWj^)-z(OFcqqDi}YK8_Rp*nev=^DfK5 z5t#w6yCn^_3e~>?fVqcKOgnQBFrvWuKyq;FLL(2eL-Ra(Vd}qYzrl?mYt6v-{x<@V6EtC-!y-b_+ueJwTmd7jW@1A) zg@V@{moYyv5X_gubnMcIz;JV6HFZhZ<{oi$dhKcws0L6cY28Z`+Cl+ZL@yr8KM|8f z>e#&hLR?o*^(rvSyJ?HQongV93m(Na+i(vms5{F~qg^RAQP7E85I5UWHxr0cMZe}E z_)WETdopzx4#BXhV5bVplfQZa0K|dK-8eSAnWRgV=cQ(2X!DIg`d-Wy7v1|DN~GIN z1~6+m5%&8bw~e3E{ie?PMK3BPO9gXhYKK1SMtlh5VA0Bl!#w!`cpBU$0A_h-8gL@V zKYV-{?b7zSPqwd>pziba2kYqS0rtW5zE4bRLNh5D^HT$rx*2c6?4Z#)$NF``DmLDd zwWY_QmS^I8xGf599ay!r4jT`IcZQu-wJQAQiyf~Q&E}_GO}Yfo;y!8~bc-`Aa>2P% zE@;h_f4j=lcDKT*SV`wj-14-zRZ!Xv8Af5#3ENq5t2332)mZvzRXqqo5vD}SgnH*2 z7^NdXEv&}o_^J_Ujx_a*xApA78Eze0zL7lvUSs&DDzob7^l`b-qib4=1u7bTL>%vm z?c&pRn7*(Am;e!G6C&JrSX;TXh7h=e#j^Z>HsOL7ee*p-_#ar%D%19nZjWB?K_{xW``4F(O^@}jgHngxybJe!Gm*}?&``^tp1D7n$UgUAxy+tR-W+MtXG^}U1 zafGn0fi6Uz06znK7;m*%YJ0G{+>ft{SQPK6XICb65j!4p&ZSOM(C1-Kg$(}QlAh|4 zpcPCG^99e~*G_v~ zx03d}iG|hW_eQtl?A@?y6=l$3eEe0ZYztkDXoKb^pIhykNPsB#7JH+r>;Yj5nOEpg zD`{=0S13;j+v^YSCQFl$EB7Z_>q!MmdplAZy3E=whN?419TGnO8GU6$QF~r9v*-Z5 z^~#)}88{NPD?*}QqGc-Q{E9tW*>o}>8rknMUz8g16oEK~Ga&pliN+oS?DI!5({cjfk!3pGBGY7FGfn=_bkCBLES zpgCf~Idc)#@JQZ+O7-fk~}L@}~nlvB&?4HKJYjk&y?J)qd&Hm|y43 zblT-SpS?$52KfG@1tj4fWTrc#xc8 zi|C)Lne?ZtGClP6Pg7-byjZc7+21!*6Zg6+WeHN}bz+=95Htty>|Y}ertIHhn>pU& zFT2Y>hd)v8_VI;QpqlCghB)a&-D4%KB2}higRA!qjjS;`KEF7&JjK3Z$?!jLLIYb> zCz@V+p&+K8_mvv-J{s?mIMw+gk8{J7nc{dG_V3V3!WS+Q?E84D1wY%l`EDQ|{1al0$(SG6;W?N7Hhe9G+-` zjQ9a{r6%Xk{iyMqn|rA~BI_3h@sno#fsaisJ|sa!N(h>6-fU#)SZujo$!kI;w{Ax1 zwvRf>!8qI>vLB|P>ApGrSkNxDAqemAkX$G0KX{?q{0;6K{P?TBsJFJuPbuaPU%by^ zrRH+Qr3;#9s&8vZH_9T*2B?YS@uYp$`jF#U6K|`KXHZpeuK1NT>Y;#4FSRF@+?RT4 zDkXnvSFq=4V}KO6BH}~|=s%!OG4^TaU6Cchx@|&kMg1syR~BVT?u^HOe)Sd?Y`zi- zuMmBGM>=8P0@l-lh=XO!(Q*RlMpm}?*1=Y8?uTpv^w@p?98LMp!+L96!cY2nVvHtd z3c%Ne+%hg(n@35~e@QZt=a0r@{+D1eI68bGAtCn8lDhy`p+6sO$46~ifguY`95Ze* zx6~NF7_Y_cwZ4Yw8YiajdGnKHSL!BA3rgXHsEQuzE1XA)wM6wiEwW`u9>VzM!#&-# z@!-g^(Z)@I4tMMXKcsH|%3>v*Kcw~mT60F6pi6%V6Jvs(@#k=hVv|m>PbYT(U2Vu< zB7VzAGbu__ZMBi8enys-6@|@Dj}@C7xF_k>5XdTAy)(?Xg)H-tNP(h2wst=xX4~ChWICAkQUEssPbn+z zfv5L}2(mieqrFE(u3D@4czQ#DM4`n?-?bsp$J01WmhQ@{WG~RhNSVZK3Tc}yKj*jy zjb8JLV;h|r%alKd{SlS7+VMoCq%dP+V_W7H7C52lPw6fxZim(bGBPJd9v+Qb3Eh&| zf$87Ag_z7aRfm|G-c)Z7+q;JS`q7PR&hGg#U0;mjK(H- znmc6SeCpprTaAf2!s3ieG;`)8fK%qf&&)R@;S$CuW*H}&DU+khXs!vWGL!g%Rs;Qyn1Gzivx^;OR-E<%Wgty_c8%x z&Sw4PmQN)tdCw@MI@xhM*l)(qYDO7JurSQxnzhsNr+jJ1fE z&G@cn8P45|y2h;e`cZROo09@7S!sqK$DP;Te(Qz?kSvDqmGB0~%4X_H1Xk%ubGH;5L+42zO6jW!8%pyZ#hS`t?|zDD!R^qFrQGgECob4}m% z^P(zjwmZi^b&#CyM5t3h_#eER7{wZn#)13@!#Vvrx)MfWOefjaAr18vpoS)iP zSS4sG53kh7LYcC=*8IlI(zSve{-fDVaD~}^R)5>^&f~H03viZ-)_wR_1V{rCqEniB zb3TtA;6RQGF2btv`6FfBM>)s08=jz4dSh1;1kIuVRS^-xL!8mekllo|UX0Tp9R0xA z{c~pqNU3MtvH&}-iZ_;eFNIRu^3W{PFA6xeWdW#FV`l>kZqE`VpDGZ z`1zy#;u@An(R&KxnIK@qppr?U*9JQ@MfJWd5R(ee(n}`mg6wH;m>D0^_&rF(PjJ_T z@=Lozcp>1th_u})&NQ4`%AxK0_UcVD5N8~y2S}3q?msO0K7@+`Rm4K~$WnC(0M;P6 zKQb{giMjgLuF(K!{lT=9^54nx58eGg<9SQv(SPwg>VM>Uw>Jn7|A_D8@qWIaC7+xDm3%nNgo|vP70}* zfP(Q^LihD}nJM9Rk{4sVEzUcJG&M^&OgOqi zKbgHWneBjOJfevEJ0^l=domDaEeo!0q&nR?8bVLXqj9u^zd}JPM>%UY@9-vo_585$ zPAqZgaqD7pp10dekGRFbw&p5bVDh`u=9AINvP=2e;0K-Tyv$G zKsQ+zBC0*48Sijwt`>g)czAjw7s2alk^3WM$7u%&=6I0rbL^e69qk7ipf?wK!P+nQ zg`t^AdPTwYZ;GLcOxsoeNYqd}CG0f33i|L?3>s-v?9G5R_iOa3h83AK(slY*wKcSO zbrg{$_CoIuOI;NajzWHOz<#Nrp-sppl=_S=^qN<38e6+)dOW^DpNVN4rIO!Y9dfyc z3;!7)o92QVb+P?ah-;A?z7v|L;%6*qC3Aey$0w1DKgiqqS=4e^7G(FT$LD8@UGyLf*8+&tE(qPynyLY|oFjA1x53<79~>06c8 zKNP7N5YPs>bYv#`LZ6=4xBA`UTqA!eyZeYaEdT%L7Ry;nje>IA4koEZ{cur2`tMU~HN zQcI}RX!CZ(^0`sOpGdC08OR@R@tP~*xP=q6=+7TcEWyq?(Y{2X{lz8^3c8&Rw^4m~ zU=z*Ysh~mGzX6SD$?-n_vw!A4f`9R!^f}eBkiS~Y-mDZe+!(GuyY5=+d4wBXbhaxO z3kv?YpyO%g*V`+V)}J^;`a1nNE=I7+MK&oJ&=4lbh9;mshBih_TOpA3$6T-iF)XQ9 z3BCz^M)?%=2s#q1=84}NKKxCvL9~&1q>{J6t|eCQw%E6GoUPptq41|oxl(F^4dmhd z{^UP{o>?;AS-bJuIK_j}2r&VB9$v0ObnwT(6eCc?16mS-Sdo{Y z7ZVMU#Y5K&;dpyJN|LEXiYw=z+S|VJH1q(wDE}~wvMcDp3N9CaC|Gw&-b{eeRATvu zlU74=QLjeQYl-?>vHiAsu@i;_F^Ben6H1wQ{xM`9Jy^XAtOU5u4Mt8XC10bZ{htVL zpY-ASQ*wRYn&Z33lb%<@5k#s0p?&j)A1hrMRyAc66u3xgnhWQZ(uK#jPf}!l- zv?iaYFjBlS{a?38P1qsp|H3MPzv5c|bfDKP*h%;U7*WxALDfjyKk2qDGKd`ro#1MUH`YJ%2N68zosf4K2LN|&!7`_9d%J8lkdx}r@LHro5K#l z$;tUHDTxN4M*<*_h)3y$yuBYvienyBS6APx)@4<`iz_K5MZ*9;M1izc3mxo-5eM#FrWK zS%sHSBYNfd$c6yP z5kbR8Tv1WcKRDGB_(I$1-O*@u78lmWbyOdqc`BFO7f; zE*%|RaY>19N($Zgo}THIm9YCd6|YRSgx7nM1&##+kn$;lTB|3d@i8&4T-Haom!+a; zv~0Y|+4Nhdn*5Edt#P2w@Lgi!NAX~M%~CVGFQuh&LL&4!-Y<0rFBusby<<1Jl$4xo zWMze&ql%4#gHu&qZPXHk_pPU=V2j`4=BFN7{^wWz)VKx*24Xzpq@hYRsbMe{NCz_HRkN>>EWr--{0+Ay=wuY8dX^2{uT0`!=Al zQ8Y=|i^_Vqu#IQZQ^lB0OiXN~+Bs{A^W(>l$Bi9BL!@3OduWL9dM|1Q>8RF^($T6v z8hkG3h=_;Zv+MyChsSFo}D%FD~A_^njnqE)V6Fn${xBr)$xQ5%bY^TsbvqiAO3XWmGK zEj6p&*8nE9+_xUIVJg~*Dok+{Lhf9bE?rXnCE{^_+m|9FE-H%nRb&u-m4xwaHU8f` z5kCa}N9yWiwY9ZJQ!=-Rn>stM1_cEncKj182S3M+2)uj$K6HDw!&+^F-Ur##K*wc4 zxVOLG^d(=r-`2t+1QU-c+w+Wuh9+XO;erlfXk_F|c3+$Hba8I(ld|1MsmRO2S;c3k zhp7AK=Qk01M=|&EALSfU1qB6m4vy(zNoi@5-Xy-v1-1B{?%5AygrO1^6SaH|EM(zI zdFuCv@-zql-5OjhrurB+`C(jB#*z;xt1h=5hQn~a$K78YUbG*XbK+xENDAri@8|sK zi<&Q_Zq}y5g?ciwvanVH*ME1hZ+5yRI0W{hPEMvpfcYVhW~18{N(`GW33?nnad=PD zVYE3>hum2j_=q-#^#X=nJX1E#5H9d%p2qC@Sf$(66qEi5c%chC3afYLXl;b|y4tgsF4w>Odu@Q2MutZaLW;>bBImpM>(lWFo znjUpRZf7SC-S=18>;+smbUW|lM$9L4JiKo+-5hA~`^)Wx-sJG3Be&M}_8GVVN2i+& zFde$HY8<8?1^!IGd&AalzAFy7$|(O~>Uek2ZEuMvMewEH$^I((w}yiUX5a6s(8?U& z^CW?dzvmhPVL?n1ucM#OpL#Ek=j)@_5Kqgk)Si6c3Y~O0*|UtCeFG4@#O@D2;<&Ew z(Pa#b8^XeLh^M7y@>O5I?u>olvh)-3IGBQ?A03R@8yOlRuKOHygb*k$L?1 zUI|U$BVRmf?yYWT=bhx~h1>(+QX2dF3BhWVa}vR0++<+bo{bjC#f{8S`iKeN-N8mq=V37M z;_effM30@ht3L9+NYT2qji0${En(M~k8G8clspO9%J8pEZ~Uo29&H}1kD()aQ-p9d zG&CqEDVx53$4yB|sd8RaD0vCPaS86MG>js+k1+TE!>wDF!3>xWrrVn5$#BoE;^Rw! z85tQHU&>JpBcK=FT80&Yetv%JRzr7(a#XM|F)?F0pE)=b(MB-C{8nfG*Rb)B_bliW&I2H^XJd(?Chfcn0V~gzgTWDGRA)u0sS2&2Fr4= zeL^a2$J9-Anu0}QM4nH<$C$=;cXrtA#(5oRf@R~FuF~0k@i??Q--mB&WRK`_vA4J9 zvKe`5+fZ987-!1B@R7Ki@A(p;2$#PR#gimnrPY(8qoc;29z2+F4d*A!-Y3gBzd>Vf`a z&8BJ+w1{cjLW}F_H7>Ss^WektSfnK-uNL~Vj#oLNQ}fsb1P5c?ym@nLx&`ag=g%{H z%YzP%jsr&yD{$QD`T4-DEr(}jW;gHO#{(ZPvDlle2Af|Jga3eA(emdHZ33&_BX@WH zTg0KV#pm-01{pe)c2}-k5ru8z!S}yTObmz5U57I((5Y;1^2hqQqhD!9i;6}4{ZH$? z6W9!bYH9?0&cmHA&b?4WKIwBIv@uZ^URK5hTl3Eje3aI#cDfA)t!zio4Sa6^A7kQ@ zshQafoB;AmU0q$53l+$G9Im0PWETYk$YE=nwXd%_XZVjFQ6ubyL|Wrl9b#6{kUnTC76H% z90ZMsH;w2y4>3mROVwwz0TtC5CVM2r#H#T)K~1E0uMCy*5nOxBnqaas*J((B2`)0I zq=cj4{+&BmChZYQ)kIz}Va)nc3f=@~X5L;~UvF}x@jjj>7n_#?AB;TOp5ny^kJ{VY z3(hA~mde1u;Jm!PUU=nKK{o;YrJaW3qm$JlHM-n0@I)NW3s1^Vevgj&L6qlpNE_id zH8nMwYV-{W51$=|_cPQBw2}LdpF9butaL3YT?Mb4!+YW3%Y$yJC>k*{Ge#J+r}9n3j9>+7%h#9-T!n7lyu@QS zK9vxjDD_mvi{<`(HFrxnYVWkHtY92+)-=aiXlwQN1CO~|_ZBXu%6*>+1cKc6ESC9% z{SOL>+;|XZeSLk=JUu;!+;h+j7rwuT@q(b|;NsHWCvt%sWHhz05d|)NN8FGp7k{*@ z`s<6;+=#%#!^6d3xm&ky8-Y(}TE?lk%17|k7~J{k;pQxaKPg#RY)k^W@Al#0;VSPf z&A=ZpEjIV{5!g@Gw}IIbQc>j+S0%tMDJW}#^(7%uQR*H?Lv3vspeQ6IBrb!66OoYk zS5@)GZJ&TAQh9k{J63_`RGnmy!edW2QSTKF27dF_Es0se;!7R}YfT0|XBgKn+V8@I zU^A$ffLW4}k#S^SpCaND1z{IQSOEr}&@`g+y!-s{(k3Sp|1+_hO39%mOX!LiCXRej zE#wBCzTiG!b6AjZ=uj2ycN=ML;l~zf)qDe!^+_^+oPixU*c{(O&>6?WQBhG$1P{G! zyaI|VD%2_a^fW80~IWgxIf6*U=v6=y2`tAGoR&Wm(hkM*sLsvirA~lg=x&9Tl zz2ClFsq;8oNbvXfH!?Bt55l8vo|vE;E;7(~Y;R|GuC$nb^XTMcXEF)o2#Z0zZl<)b zpkR^@EirL(Lq&PH+Ix;HuhRnx9=oI|CoFuLDaAmVKfbn7T{9OYexxr)#oEfwMd2&(|);mQUnv8_ZJd z&a7?ja+eP*E-e)gC%fODCWh`^zu|GXVFd0h+n_;ML^ti`%F}r6O7JV1T3XxP^unHv z{#XPtv9bOzwjaT3Be~NFFjtiaa@eegveP*gPr%NOh6Bfx;ebOE^nvD=nIf~Y%tOZv z>f8f=mzsZ+3@2lM@W5Z^r6dEe3TheVJCFLxKNva(&j{%SLQZ)yoM%?zN5BUZSDHr`b?Ceh|V^28tiL8F*YgIj8CVqu9Twdr& ztnxhOw4106)h@R}L!6(ivbnjtV^gJkjQpajlKxpCISeM`3^;rl1%-C7?BgkRAt74u zY)$ZfR~^2otut6=NLX0&I}Q_+wgH14867oV8Oq^(x${YuU6adVNNT`wIuN}#1A8)n z^iR=USPM@sT!|t63GvgOlB?Xrw$OQcsy-WhO*-TdnkB{g%V-9ygpFP*0YQ=4(BWkdR2T`}0*sPOb$K3x}1Vd$<(rX}VpQT`uz7xp2ns zz(sCMefyU5`RXscl9Cbxf|;2a6+L@CJY+6U zq7`t--peLCe45F2Ufhn9+}OC)LUU|lDUK6Uw50X1mesi~=_Ykj2}dFqjhY-HgB z8Im~OHkQ+=&*(Xy$&}Q^FE1~n97e{+k7x);J)~u1u84@sJm!s>_0fd31dF_?vw&aH zI+7m4Ae3@iSHPEKNw@pByq;cdooSVY|?==^~V2?(S^D$Bc|(=fz&*!~4j7GYgBmNF;K|eJfSSBM7j-+f#We z@H9W-1RcKY{Xe=G^P*#-uA}WOEXemsb~dwO%1JN#DKoG9roQuyhl?J=Tm@N;)9 z|2f>_eaAHah0W9ON*qaQ7)!F7tG~xSbkQ$RZ|Uxq^H>y)H=SNz zj|6D3GG4GTQext^+rwkcvRc|FJgEQF!rVOA&+igqUd%r)FVDq((dTT{fd4_pH7+VV z2qYg%#r+c#b;?&oIwC0j!CPCV((^hX($mvN7biV;BYVkg*nXMFc;tS3dbf-xic-W| z5I{_(^LZrCL?eL+GR@1*+8UyX_H{NkMRO5L5XXQw#K4nK#l82MrvelV^m zauz@*NIn1SxNCVbB9vDw~iKlnXo^GN-mu{^Nmv*Pw8yP)`8T z%Z&VdTs}TN_tox;L!S%s*RNlXE`BaUyL-{B>8Nu;?nt9CPqlPgE`BOjQFwsQ1=3?C z^9vzC!L6fhljMp@yFXMKMfyMWC0?$NJ}k4J)L$GXgOgl;LCJ>sosG3ECwJoQ+qYJ1 z#hz#T!!OKcXlZE&$}CmJYhRLBl~b{FTdm5<$n<0Opd78I7wql5xU_Ty)C2PTbd#{{ zVxwNI+Uxf1+e<$(aZq|Wnd`oGnW&}ZJy;y9q^Wr&&9gJ?hHKNUfS9k?SMpEn{a2P} z4-ubZ7GJ(LqJV$CMwyAH@LOX zK;9lf!I7b`&hb;jz$GuTwqE5h z?Wf;=)=q6dwOZ8Ry==?=YqlfWarwt>BrpVi-#mbb!WT&AUrV9x_xl)Yoamxk-k11D2A)d%y~== zuRdh#1Qr16uX0(Z8nSxG_~cz$U8jmE;5EqiGW92bu{4g3YHyB*0|vyW6U^S7Ps`4} zqEV>Z1ah!k47H1U5U9Y{{4Q%DprU5Z4kv>X6RBBPSPWr?0q-LLK|iQPU>%m=NMvH# zfZPf1W!WvW9Nep5d7$ATC@AQ#JtK+IcYqs*?W!3Qy0;-?njKUWkp#j9a#DTsg$Db< z!-9==*ekeK1|GQp>G43iI2NyffOWSOFrW5F>fleGZh2mu9lF;z3~5k5X>&~C-l{Av zeia)_0<0)7Q!Zf!s0N6fApMlDWe{kn;Y@J2?Z!idy-pej28bZncdDjYA1xPmbK?c} z5e{*LnzSEorcVd>aKN89@1J18O;h?PH4U`)3NG%D+=a-+u1H99v~-q2vZ1LdhKGko zKtRA0K#tKr&sVHkLx_02k-YAQ8+g^a_^8RTZtnw_m70*8T*Ks*2mqe7u}T6+tBSvT z@zZ7Ph`Ax|09@&NvH&@DV8&>zot+&jBdD+)>u>P!sj8|Hz4^Mas|&k8yS(}BJrxJ! zk>iJTSnr4_Dkrc1bWEA#(YL(xoj1`Spfx;Z@0j$w;}Eaut$~bL%60&VmIVF%hsSIm zVDCfZH02kOigbNl`t`XvRr`5V%MNO___=bSUQN*9ga?78r6qsO5@Oz?^J3ND;)k%0 zBM&M3(Wk;CO#XnFhPQ9t5PKfa!>)MUY)xNSKJeJ%!_|*B_|StjjXozMBLj46(HIX0 zJ}ymfOeEY%xP9f4_r;lOot{sAe*P9rh;&q%HpB@>^^yAfmxGKA1bK2DL@K1KjbQjF zX90r7&CM-MF;(~-pR-xrx*dEXKowOm*$JNA+$?ftpfurve93v?ds7mh^GdZE2q>AJ zr5Rb&9}+(L{_owS|AkkZ?iB#`fP+G|V;$Yr*46|WC%8=8#`wBXiwM+#cg$g97)n8( za~gPhpH=y2wkJ4b&0<5e(Mo#)xZOak%WTKUU|L9NY5kV7vTb315%Jv=&>2tzc$fR0 zNZ&5XLsA4aflz$WYie$8(j0IV*pF{{`9pr!jR??}{#^xf`lt(eNB&2&16TNH3jGP> z<1E%khcse@gaCo=D5e+!NU|9Gj0bZV{DB|J?*l4DsS$nSyq@m29Gt=6qKtlgdJNRj z_xcsagxzQ#B>KhqVZ(kDIT=|746YmUhy(KAeh|&KZrw6G+L{Iw+zhlb_kJlW>{}Q> zXzWeumKa)scDwtFjG|&NNMzoqK{_9pcLQ09^lPK#IDq1zTVwyv5awnG!B%z4IKy9@iZ3>1FvYAKS@%L}U zc22FWMfjp)J<3!@Lww-2%_VuR;dYMXgO+aYB^3XHvIZIgn4aIG^jAQ4fT*jOdLYlv zrWY2}=kHgVe}9J-Z|Zn@V2cnH6GJoW`49-6pate1=){pS%UJyjtK7*L$2_|TIuY*( zsCtxH3|xwjk9Tl(76&c?Z{`o9Tn6Y20DLWAKiu5hpy!!v zJqxp!5d{^mF{@LkHss8zYifpo$eBfXX809cTX=pxi|6_ABFf7HREhw$Z81nrN_zb+ zY*Yh@YE)DZRHQ2GC!-E0edvipFO%F+xcs!Q*_RL5*psdpJks4r0D^9+LM#0fPkCI`q*MOA2^`SN5k*m*2mC z50R7=zxRxbbJy zyFjZH6NijNbnvshwXL`ws!(&ZC~^@BHw2>OYO+R->fL+ya(2e8i|Q~DM}0o$DyF|* zbfO+*1>svx9`_c^mf2Z{Y79%E%K*Ubq@nCw>a=D^SDy-tmn`qQrv5Ojo+++jCvy~3hdENb6p4FK1Fl>i5) z1&VGCa6b|AzT@CS*q!ENP$354u=?x1ZjB2bSR;Qlpd_@r1~s-aXP=|SfP@c#Tw&^a z`SRrrSVThoO5MTLby$u$wli(O!m6N17+@t-(SYdGk8)i@bF#F|nW%;^is-K=+kcO0 zsk?#af61N!HQW#Y0ep$43VRhlUsf)&`ArN&zHY$us-NMHs9UhPHDE`=%VL`|)wyOO zc@H4h=Cb^88FC%Wr@}S5y1My#QAz{tuU;WOWoD`!QwX{hi|r@!+bQRijZ*G}6`Z-H z4D;F9+Ul9SUvL20-rCkS4T`G`gv0B&xImbNf}Tf&qQ`dkw6wH}%gY1dc7hk*>&oU; zcO0-XJvcaETtw~REYp(qaDov=5}n(AA|eon994NZ+G-)#cbUzO%*$i88?Q1_KD$<0 ztDH0R35hTS@LM_QO%18-T~=19M~^OPms$8i(Z+fZRWjz`AqPtNusVgDQos(9+gAIA zGfXAY{rXR+F~`Tp2f^!DS5_cQE9gducwXOHYXQs*@e1%?I@C&eea>t3KLBOBbeFy~ zmmSx{>EFx(lzhzHFn6A((B!gpcqnaWS2!>q2vQ9uG5EEd9ZyF`4k(20N9_tmLyg4G zR0~YJd&%8^l#DEBX2$q#oGgqaSl?BCv<+0*3W_~2Xzz8U_1f19Dkqvjy_VQd3MbD} zQc{9u%YWhJDZjJ3E8nZ5188-6q`{|Qf`N$%Tl83u{cv-V0CKGeIw2~Qkj_!bDm%sy zYy4H91Ev1l(nzCuZtB>-+e5TjEuN~X>SZu!TxxFIiHQkRatMfDSw)4z>*V>z?nDtE z;rinRYShslnD+)06|uqhIBU8?J9qA&6x!9RS5XL2_;`*4 zJjl-+Mez7Rm6bfgXL~A;h5YR*7XdoNK=B?3@l*%Bj|5b`nsxtTGbmys$*2GE01eZr^TKY8~-X9Nn=KY0?tO;H%$HGh11 z+A=w5z^2zWx(awW9VS1b%Qf_O?aK$?T|U>H&ZP)>#6STZ;FqP(&+OcupoPC}7oO*P z$sWD)87FEWpnMAXGY%vr;8_U4J!_SkQR?Z(GRiwFe5Yj5F1rFI(FVtZ=SvS1=YkA)tZ+0@5AQB?{6dh*Dw^N{4g^h#-xWfP~UrB1ngn z)O{!Zd;k01d-fT(#vS+m;|vcQ!S${0oAZ62dgroqQ9n8ESi4X4k1W6?#dGPGo|?A^ zgTu1+_E=zb_+9MAa6**~Riz9~Wj3y3@KJnFHMqV{$z6@`%xz3e^2p{PUuHe>C11>kdz}x zEJesCLG+KWaEQtoWOa2I#blU){wy{vWVNcB&s3Y)|PzIr%s9bSz;=C8oJ*C;6v+(l5E5qaaPJPmH z27g!0bGe2fO3;>Gv*-Pi_(K*J&^MtT1APdUGlhEhpK<}KF9-Ae{1AaS!o|f!j+@wg z5%?a)2NW_ez9pxjOfSe=IZN}Vgq|pj>mX#125Vjxi9&q>a5~Jah zXunfm@pZ<@yqN&%l`p88BHdM{Eys1MycmJf$N;jz!T`WSwTFV5`Vk~mtOlcEV?(iU z@L52v2QQHD=+WE@dRNE99A}iE5hN=v*m4GJba$*|x0D=c?P>jnk9x1%UA^fa5P-~c zkjFKvyga3af+0_MW}Lto%gnj{H){|*WDV6H?@xCCNZ?*;xpTqR&_GE^$ySWo76L7jfFdA+p=hZ5 zaS7boYtR$k+4Nt48DlmM4t%heX8Q6^!kXcQU;-$UfCQQBE7l|0vK>6bjcyo_GPc8g zq!(Ds`xJ$ABv1pNgoOBmoQ7Bv;G1z2-FT|y$@{$!^D2*G2|N#Eep66jffWT}i3zy! zCGMN&)w4CR5_Vf!;ZA%lcfEgBw}c!Xh!S@=`sZ}dU)R)(`e1ikybSZsc2E#JQcIj! zxLh_at|Rb|^vXrT9s|qlO7d63#i5TDUz70K;w8cWGC!PlwJn8^9ZEZ%R*Zo=`)s4+ zTJjZRV>VFP@W{x>FuW!90Z+4Typ=LEWI;Y4tm<0W7r;QPp)eUj8|>Uyo zZaCMWsQt9U8-GYXj3+lsRk;pG8%q4ao}$&QEG*;)Ndr5;2!oky2nAK1;la{K zD4_NvP&Cj}dLv-nQD}Y(?%2$C14ug_Kdb4Jst~QN^B68ePVNY-r}Z{9+~tGqc5f29 z`%c_u5s|DCmX=x0LVY|@&5H`T}Q|F z%E0;LAz`{hK!JhC%U?Wcg+S63fGLJBeHFwh#7FNcMMkW}1jX>LYt2DB_G z5Ey_&xLJnm&4bm01GyU1M=_Gz;l^L0__kIWnTuMgxdFo0K&}f4*0)Rgmn7|avgz<4 z3jy%1py0y73fw$${g#h&m0u~Fkle54F_P&em0=P8{vQ|`Eev4ng$N%!IAB& zpafluTv}Yrb(ZIN2^)r1Pu+?8VfnXjDi!ystyLHVtW>AnK#6$=pI!#pG79wI7D+C+ zuHHe0At+rUKhK2afcS)|?(SC0vmqu9=59<8Z|04F>fKx-^eYPTV4vWou(GlueGhiF zFZ9NlDR(-O?XzfsLUZ(k9BrG)z426VsvJHd%#mzR?qIe@2bP)X}hfUOt$%P!7B5&*&hDh zx`Ba#_W^V@kXHd^M6KUmRIelwEF-?L|QE8NtkPycGZd5c>$B#(7AnZ$%n zBrY|?T{@k62{)<1Boo|P*xMjI;Wg~wE&68qos~N zlkR6vgxRSTj_0vK+W})DM#5WvX$W#7Sd#8vf3zs&e@~iV%aIU~ZY)e}tT->?IO7B6 zi3wP8Nc9lkU6ziGm8B1Y1Rl6!Um@dW)rsgga2&!TM`}PJb7~8Zi0Ik&EP-+j2oFAv z`l8*C=Cm#}JIE;bc6F&P6rjck?%eIX6GhrN&dTp$O^tZ%*aAsSgarYV57lvKpnEDi zKOj&Jx?fjR!?S#ttywidPc6Lor#qx!k5N)W;1CbUQsMi=iPJx3A4Krp30r~y;AC_7 z8DXc{R~u0*gngw>XLR-SoIbSiJGC!oMyNhXdIB90`aHIM`AUG?RAmrsYLkYF#6VbC zs#g+~s)6#kkK0JYj-Y~f^cZl6Rj2Bg^BVfv_b2L(AluL>v+CVD^1#4A1fDo<#f5;? z^7As{Ux6|Y!78v^gXjZ4Lp zQV=b*O-V^njsC~myf|t55L7F}@1LK8BElOK6!gwv@*0$nH|t@yAa-j6m>am8WR5yQ z&BDU8DDeb_!aERT>+0%QxVbY2E{wP@iP?;l(}9oEIda7Ta0s$!pl`pY8K%?LS3`Ca zl!w~hUK+5gJ~uU80g(e)pztqL*Vn&QoLuJSbG%Hdu*nV3?O|9L8L(7*Sdf}JIw$%0 zDdDL{iF=k_F@<6^4r|D*G3{17cq=<1#eAS2KsAiIU`ph*yK)@~gO}m+Pg|pfL0*I$ zd&+RI*!lBEAtesNQzM8m&!VCTy}b`axMh<*?;+=1?>O74#{=2o}bXZGDMc8Es)B$a%t!Uq<+Jdoq$uNJhS*J=qL zQcjK~nBBN}Q>#e3)#{=k!1n6&ZmWoC)3ZF2S~o)jx-C*-3~LHi;-+2nn~9E*Hm)2~ zd_{CN%ZMGj@&3bVhnVX7*ewp()%V#N9g;ZTojr3esk$JE^X}P}eE2ho6TZH1qLI(4 z`a@vsZcCKB>)?6K13W|WjQzJvY-kqu@5%=D5+}#XgEsPfeoHEVA%xqEQV57o?*}o z9QJ;%M`||$@~+_x*P3KEHFGN{t{r90_Tb&$ znBD?Mzq-JCaf$C=^@P@&dG=sAA`S_fUI6qQL!iE>yrZ@S_;d4Z??FL%wwEoivWF~V z(4|XUR@mV6NZNM(Rc5G3xD2@%Sy|ci)gefxcL}tOSHsh|?ZE0urBu)(1uh<0^OW^<^HO_ObOLxKS`I z0uj5%y5}E5^?_vdJ8+z1(WCbRk_PVp>@R;{GD*+~XS%a!y!N-v0e%}nRaD}nLIYw| zRT5Be)@RRfa#m++6?OpG13Fg&y$7ggWKDI>rD+IjNf;831`EVB&Q zP{1ev*PlOs9z7#Ob>f-{Ogsen1qvs@=eD+<52OFRK!WiBer#os z)?5x;>Y=3cZ^IdD&0a6MIf!7Dc-_vYU`7fMK|bo{#xmkI`~3jqnvCorn6v@Vg$zEhryb|} zDDd#`koT9O!i9#uibvdt|EWcRIeHyB+H;`CVD^F}gcM!S-0P6|Z(L6lTMAPyV>2^P zVCoOtDYVvIz#C_MF3amMA%lyL&vyCp2?$?aftCOu5QA}9DC)&v0NR&$?TH{;`^S#} z@Ngzz6~X9~?}s>8Z@~D__ytGRQIJHZt0cGc$p2e2*3z**;8#sl@4MW+3k@KOKokYU zGNr!$el(wjOne}S)~#Sfy*CH|`9VJO;3{ZTu<-}hhcWN-K7wuy5DKmnCef5l_e(1` z=dwtU-dm|WW}5(v3sVgb4@`Hhg|C)4fYy}_S`#^HU z5XRjA@@L4IXsD|CgLqgyJgk?tki##tL18+q=*4slFFML>VhSWzs5*i=M#@J`Yq&A_x1X$RWjgh8WEj%L?9Jv`9Eg2%j&4+$n0SJ#@_ zT50pwS3$EvDhJS9$d5_6dmvNPI{0r>w0D38@(%#(>KYr9!P%s}XrWXz_bT*AX>d>w zIIWLCEY$sY4-XzQ+H)wIsNn_FzUfMFzGu4O=ffRo449V_uG0SqCFWK8n=m?ld?*`e zun}@NrjD;u2i_Us0QTaApi=B4U@jm-0bu5Wnzl0Y^(4wJ!Ke=(k^+}1cNEi>oLrZB z&FP@>$$BOUjF_qF>V|*^pP-hd4ps&K!sZ^d2qRmsS{cf7Z^w^-2$D`35*?{IgZQ4geOjF0<*-~rQGi2zHQZh94iUj#l)+*@*LcK~S|}$mdJUrNZW4;oe?(EYG|43ywTQ+HSV)$AacH0FB+Q z`VJH^8OR!si+c*uY!isBXqGy1ffj}bngoRBI#Vvx*49$c(-T3Q2(ST6rR5B~@6Msg zl~Y!J-d67g`~L8qWhz{|7O>Hv00yDkG;A_!kBxqF{0j3~ZfId)%o5iid0pd*gr2<0 z-$W?+d43vucH{2_>+SpHJIYEZC|rfR3u`aue(wACR4}-_t{wk3Gj%&xEC{o~)j&G( zVWbje&XEMwe())r<0ghVJpFaOP<5X1#FXzB5>s8z~5cwjh zWf2rqUy>Sy)H)iUK|~j{smR}%fMXHaLcJXapM+P~Kv|gr)3vh$Cmj>IQh!yUZ@wh#O(Osbm)&Ek0f>y z+zyeO#3VocJ{r^x0RLm-`4$^WMP=As>-{fmj)aR*y8f9 zFW^b+Z=HmY4^iB-!`csp6Jl;u5KF&M`UN)9r-1?bn-OPywQ~Yw*g*Q@1??Sx0k2IT zuZz2TE&MHHQ&2hvGLS{v(_dcuL0SaF<%aYrLMJ}>DKsR7qCxQOvG3pgVO2o_g^B2q zRCI27dU}s|)Q$kI4@~u;3ErhG_*b683^`E4Slb=_6ySm$TX)gE1|v#jPvELT-YB5a zafk>mh^!;C77T#p_4P-f$)Zpf{HlT1HYzd@Si8^{X|tKyE;O-LGGc z(3z8AaBik6gEE50z-~GA0y-5&Ld0bAd{#`{*S$@P1n2Xe3_dsB8bc9{0!{E*@>gE^ z&;SbD(+xFOT39()|>hG(Z)qS?TMN_|_FoK>m>#OXgnuq3ZFSoChzoI@x z|8$Z)hLc)Tz4iv~(`j7aNnzhfdF2O|L4CX-1g9pEAmKt}5Sw zV?eunY$-*zBh{Q1b$`g6eC&6~Bf3tpyt^LWsCFw}9xGwuf7!$RKjzzFRvx7WhnK2s zyB1l5{&jSN**}kN_z%BYjwCLpJ9)esj0Cvjk&}6}FZZUf|8j8YvGGO@IhubI*8lid zq^CeR0pJUhk549Y^K^=j{;~UbisCe=D zDtA33vI1D810eBJkr-`WklAnvtWBif!V-i@Kb+U{0RZWYS0?JARcGaw(==Mb77$2X z0O^o6z!wHZ!%c05N|brpVe9|{4K)Oo^g|X52GUf)uw`r7+K|`+5Va9jJSihK+_O3s zDi@(=Wa3*Gh39=RHY^4|oQpWC^$<3jU--xTcmK!&G#3>Ve1aFv5<38vHV)tlwmN?CI>b$& zL0pBQ1#)I)vItWOP)|VL1=SzvIR{&f5{%f$2o|rDq@+*VHj-hW*`V0zN^#X5DHMHylKIjQWzX;4bigZ@ zA$D&ae_cif`Mb=XzbHmrrx|%kI(>5ZTZ1RR6r2SW%E;cH?CIqDMhIznLQN$A7yJ`I z0ILOaXr{o>9Dqe}kMcvYWQT&T4+wCm!o9dhaHPzc3~WJ=>#l?817o>Z7*xDN==ecp zN>>mC@C%sG2ol3lB2HF0rm*1on8KT`SK3agA0q&`Y)2u%{ zsEYScIx#)~y^#3&?b|W)wwSMRdFAI_An^#w5+@j=kaR~$16X80h2g{mtIqnmI^+&x zd|*Xdct^B1H-Agz`j5Efh0IWhDh5EjC5JyBiaN>@9Y3#|PUDE<16eVY&;(0m7t-^2 zowQihz{U8Wv%tmH^zJ1MY{AZv@(A;=qZZ}Zeb z??W)s>ER*~FgRe_LNhJ(px%Kpk`=SwV{s8li49@QWTDq?TWbOTgH!>EnW6y?xMIt8 zr==0S(=%{|gQAWz37%qy5MPpi1TAOHhs^xHL(FSqQwParzHa^P`WHW$D0m#RAW4<4W#jp9z$*HLPA+q^1XI;Q4 z#CP@e;J+czPv5WC9s06Ei-4@*IZzR9*tH*og541sjP;3 z1w)YbN?tHAKtg|!5bYL7R5)R8gRTn4#t__2m9GIU$olLwh&{-YfeUt6{VumKogxUx z1e}aVS1eH}mR1_G#Rl6MB-@2GP*4+Ly>HDVhdE-~D^nrN@mCxdKXM7O;1K+^Fvn{Q zWk6Z5`H>0+6fQx89yDaQ*9pJ{z;OB6R0R-p7qaOf?8`uzz+jdtHko`@`&VIo317O@ zJ<#ws)3gj-sBoCXhqGKDZLVt~0{KyJ4Dca90MDnW#~>VnALuPTCS$$^aL#9yodkMA z9Rah4q0$&>mv{p;I$k2y<8L2W)85w^U@zpVv7DWq7v`{|_V-a$XW(N*|%=@gjo$Z5yA%)BbT_m z4-;`zW|td!hPn;=KV5TK{rw)@wwUx%uM4`LCx zUQgRTg2a*RmkR7_ZkGyJ7a34dh!&0}>_Rd45A9mNK~0?w(pT*)LRMYrJ&r&uFg~EH z(CT`d!o3MdOG^`+$pslLHTJ5If4+&Xx_ST{_>v5c5YZ!ShywAK5_=|BywgS$tJfgrSC`UnVQNCbevD3~v}z==dCV?v^G1thsu zm{9XuhWGlzM>*2#RhE^NjqtseH|x+=$CNnD9S3U&AMRdkBS@qeNqfC_T(%z1LGrut zHd8?F>KoAbqI3@+f!UF%cBWzyQV2zDB~_oF@c|fVc!)=gz75{RMcV-?sOV@q>;Z$w zZ3Oe-e=cuqG{AtyGK}HE76@UM(C4w$Ghn~~LwE$v5_IN{R0W1?&3vh)miCsJEGW&t zHx`0HxkXYGL^!J9x8K2mK7O0K5wfR{6bjMvq>3mPkOrU_q4NT+*dk39{TfVcPXHPh zNacaL2a7pQ+%pL3Hw=gkBs@UMa6m%!Ag9`lAOLXo_I|5DHaM+h>DP$0M*9L|V8)atx^It$4ohn+cIiH@JXr501_yGz9}m z&~X3+SGBe2f%lu-6;nGxMs^(zJW-4lc?3O|5yfbsqG!olH(TFURZUqNf@hAXIxJ^s zjeaoym1NoyfsDfno4sIELkdEsgvT<|Er4WRQ>z%IoMX5wMD9I@8Dy z$^i-dr;TsAN(QfCH3r4u`KzQ$r zOI#zgPzUJ6x}}ay2_g{A`irfnHUxhGff>cNQU8OrP!9_2S2)oIg$$uU!L2}P7c})P z(k|-_E(Kbepqv*C9?>s%p+FsRY`NYHolrUUq7l^5XgAS@kUa_0Jz`qj& z^vg_0l=j~?0>%P3?gtnOoWkdjxd!BjVvv%1o9m$Bf-m&%qvOd{2_RS;N5&{5s4~k5 zyOod{h}s#7R0F<2rWSHd0Ma5x4w^7guI|UAm+-RF;bl8qh%IwK4Si?c&g8LDPXiIW z22f1Lrl%i+J3E^b>-8GY@~az%hkC&tsr~v@*}anrGGs77VhF?p5^dlKVQP7ofBmWh z(VR3_I#G%Cy#fmc&E28lwtUw$Gvw! z{+}h%g-WX+wMQ%NAq*hxcAD)8x_}xWR^v-cVIVuOva{m>IKjrC_NVq^8&J=oC8@#_ z5~}k5NQRuWL)Px}L1t!KY|CXWlj+_Z9R$(Q;S^xq@ZX#H+;S?2jb6wOjRvB;L1MHt z!qx{ge5Z2=oUHz=$S)eOB%vZpwR2wOPj4Fhg z)nE$uGxH4?&VYEUFSumL+l9mv^lWUHjv)biV}Vfw!B#1K0V&8Zk>vypM_N(w zD4-iOdx>Bck^q5QNedxmku~K8%%vH@B8U=me;_S1$o1o#UzSFW?yh<+_76AN zMKwLB8dHE_Mwu>Lk`eU(Wk{?4`Ud9z+&69y)%NQ8o!RIo5W6MbKmWD^m)C_7*q(yq zkE#QPNIa|_M)!GtgSqE(xwRV#(E&ru3j>clIB;kJD8(Bz!xYwQ53lJW7MX#kK$m;@ z>X={O@6RVEy%^V-oa5Z>x|er;-15Mt+1N0yp|~!8Cu34Xu$#>7 z-C17+t&cMSDH!5e|K_=A)Vr}zD}LT=)p*dNG4;3MG6&>k>C>hcD;Y#T*wnz!J9M~@ z_+9t;bh!&+Wh`U5Ns`Y_{%wsLOeu;UZ5A$p|AII5a_V!l8ME%xk2;J7=>eN=P zbCpg!^Mu_giw$hZH$6$sdYoH+tR{p`v(!ysz`VU-QfS!!w0#Bj<9JuI{>>?VI$hWK z{Jwls;*njl10THWCw#wT9Ag-UxWseRJu_7);^DK-R27 zm~Po|=U8VZ{gO@ndFP41`W;WI@xW;N2CpR-f*)tKurXWSqdQEc$9Wn=&zBPZ>@QrY zJ6AlGd%wLtGU0A&ubH@B@aUo(DV67iT|RS#l*ad*D0?N__zC(hg76iNSa;dh^}y zr{V3cKkFUX^rz%~=PBytTue+gtv-#eeq4r}%jm(Xs&}#dp&)H*PPV6~* zYh2EoY!jnlBt9Opk8@I80p0Y5$y>ep#F6bL^;+gw7HcUgORH(ms!!1++Lm#fq-{y5${Ynid=I zL8}mh8L!4H?tP0~-ck@651o!XGv~kL`Grq_aT@-I^n=Eu9-jNVnPFtVe#!6Nc|iH6 zD46_$9Yj6)<;UR7$fK^iwv(BqeC1nWee$n=bINx*aM`>_1mh|S}Xs-xlg`WmmONpxh!f_1?~KJ)KscT z%yM+^E!s=JDwm~N8}%OySkAcrWWVguDj<*-oHhGK>8kMlC*qZ@P@#!b0qb|G^M(1-t5dXnm5e%2znDuF1p6H6 zcTU_VQQzVXT+##IwyXcr0ddU4>&d7hi>i1 z_!6@R`I$Bm;;>OiO0WHuu*v;AZ^kR`?hY4*#huS9R!tp$-yRn#DzT$AbpPku&!!iW zbIt1qiuzV|*ZMZMCi#{W?77|Nf9crOTv_rG@;b<|AAfqwn?6r$U%0J&@4<-&zkiyJ z3VqAJ&2K(ovN`;?*x)Xfe)*F4(8|wSleuecwfxRQe6t@OtJ-CsJ7`{tTphIbQ5(EN z?IX`|vfoA>PFj>vKBY_*xJjH+Ci+VEy!j{Ii^^R_BBB-kMiw#FLb=1Q9jRV(-q0HT zA>VvyPPciBrJx5-{q{)IXWS8yxr(A5(Yer~2GOh@Z8iH4<*X4}HT%%~Yu!3(0-4-B z+JQ16KOVGxEFEYP`?WLrbfxhM>b@s0{qCF08X4^^o;;aU+IcCBEs1U7YRKA%E5&4; z%Tk3f#bo`Z=}@Nz(diJU24V9UC-LRy{tcoGmniYt209-1h`cNoCzOgL#C4-OB)t&tAjy2^;uEVk5vTxZI3O#ZY>oB(-xoO(tP&%&V`Zu3+4N6WK8b!6lV>*aBH0z z`B3>)Zs{-Iwmhrzfh0qfVZ>!}-HgY4z|#8WWQTMloQ$VHGU8PC6hV-pnEib(O$HW{ zS~DI~ZK7K8E6=37C9Fl(>jDq|`oxl0%zdTd1;Ms-hUv@Yo2~lSZW^Kcn-o#kA|D&B#%lKns4S9RtQ^sk@VD6`BQKdc*Y?}bSxg23SqcD(h zx|@3(c@e3{@X>m(l6$Ev;5t?#F}7;&MZ7a#K{Ik7V?5HMU4O=ft_WQT7~cUAtUl$uEOTQ1MgJWBHeWX61ax#HNkwKAx`g9;u5}r`h*4 z*Dp(p4LTzp7scIr9yjYxm(JN&r<%uI)zKlc*ZX*kS#O8c@k@+W3CzG$%yXm+V+8FOldp72~&e@ zdM3|#_(bmAccpRs-899Nr{I}oes%fL)OP9ec2}qH#yR`*0gH8mVeV(*yy2rFH`a~9 z#D0uNC5L_5?AgQgj=E5OSpW6PGcR-Ec_ULJ-*rYj3g7!j+b*3I`)-J-?u-@++kBsX z<sHz}iSS+Cw8vadr z;(pijtItP$TTk)J87k7Gp5j~4U9OlS(QqD_{P>9pt4E9ZSgt$s7F zlzwFAc~zA$>6xPjPYIZIagsQ>N2V(p?U%eU#59Y3mkQ0vTtjilG#`?AF5`T@^Lu4l z!X6KE;=xR!%l;!8hw2x54r5q1o5dkaq_cTH_4mZ#&eT?^@SF4kJFS*kgi>N_+^Mf} z&nad%a;feio;zD~tMMG9ncL?3g%|t6o4!sSQ1#sXL7>=(N23?gvcHfeJf(NKRy+8h zY=!Ca&d~jmW#)FjOkXQf~%wYFdO+l;%1hDvpM;TnzqRGW)*l!KeQ-DU4aD}(dLH$?OI z8-zI3AG&XG!&@oi0q zx^V|~(*}|!gy`BX?Cs6O2oT>SyF?qQ`|B3r!vtZxgXSvzhYa?W)CUezYP7Vpk{afx zBbZSct>#*%YW%ZLEDNM&3f|t#xB2;vB5XUKEdEiL=T>%sz}bFp36JrI?PNPKuOWa? zIPGz-t{2Z|Z*!Q2#$$0vxhgD`>96%el6bNEkY_62G+s{42so@o+AlKgH;z4N{_8I$ zs-sis|N8kDmrm8c>O&x-{Tr)4{eYeC?{8n18LN;0NjJ`#r6c z&Yw`<2b#?aqXe=F`&%ry{?uQVnU)m{hLS-}=8Eb)5X6rxRvidIfS00nQux=V`Ev6a zo=G@D?fSp8AeaT_QO~`6l5oa$Y z^tZ5ZU-+y0eO>(TTQTF(vFpt49)*#TsF9ot`e%1PY~*ZKK9)4)R}&^(v{x=oRAA3! zj0T~8VG;6ywe3zI-*lOS*{9Su4{F?#uuY_#7JFOu3843UUAz%HnBL&us((FMK){8; z#Cl)hstgrI3Ue%wD8VP4@S@k*AcHeEymlxa311x-O5bvqQ4^r2rQLeFx!8!=*B#|Q z#2Dk8?^@kAC*jOE1Nv^xwtGqSkL$Tus9&E~c`m$$<$(W6_M+SZdpmQtX2JBGsF_?2 zBFTnT{~XK@3~xjF<}-Z^KNd!m6^Eb7*5`?GHMRG%DTxkppL5$MSRZoj;3?`VB{=fLom#)Srn@v(|xvMs4Ni)SHJj`S07#mdB2@V$?&uI#`sZb+rt6b`yv;NhRgA$Hio1sC{Jh*-5Ab z<8+gk$x<%3FpxX3S=DoOrPHY4r1>agxY_QWKW2@i)=kJ(u`cEfwJO6jo6AVsNvX(b z&9dd#71n5{5-8I;N|@05QlKh3ViOGW)sqnaeZ&V-6q(v*{j8uv=* z!PLi(il_C3?eq+6VMr(p`NzbI9a9K*&^;So)FqQMpB^IU-H;Ty$4KfO{!27XLQY<`o!~zqZ z(~(RzbqJ7EQ+;we?lX)RRc+BjtKjuqK6R*7{a{SYyV7{@L*_cA$p94>=8(s}#4s7f`}&aiQU>H3Qkh7ZewXwSuakX>j< z=kt8CtJj#SVkJMg?CY_=Tl#qYH^GwBU`k=({m;h9GwfK$F_`S4l~-j-tAY)6iFAsY z8;&xQ%y&5H2V~D>OR-Vs2W<(lU->SRz2@^s7}J9@Qs*#O_Njf8XrlU@#ne%1v@5#Q z2tO+lzg%=#wJoPPEgtt!G)z`CWQA>Z@{L@IPqz|VQQ|pEDb*@*-lKz$F#4RXIy!E? zQ#T18N&+x<6v=@^wDy;G>$a~k>GsRHwhwo?4a;(_DXT`g=`0lMo4fH$(P)~f$!p&q zvCiz6I?A5so7z0fp;#9!D`S(=rfDBbqp3`wMtOyOnMs@}6rSEX8y2Q0<^hu21pI`r zrYt*{4-(7NyMz(W5_Q@*B(>4npHh*z7E5992Fq8Nfj$t&MNuY)kNbsMb%dT~zkLoq zyXYHDYwEnDo?!Y}!E{FJDI?1_>yrB+P)fM{c zaf5x?Z_kd>l|f6HA&}p&D^;vF{|LM_}g;{bd| zaF`TT)y~N;b$^Uy!{NU;Xm>G3YyIP^9|Nj`^7=*OC0At>?s!<7JaTH;ST%-5)10Lz zaP9c)Wt*51ym!mk$^`k^QH})ihtjNqvdjGwrymisE0YY(5|za~anO%hQLGE*)9>1X zHT9e^JI23+jUgT6q+%Q&cdjr#pH|SMx}h zgiB-R(hK6V{I9=s`0Q}MD=I6ud+}1diK^kihJrDgWGp7hmFJHtkvw}O|yLLG_jTi0ED%tQ_r78S>+T$*U*yL-HjqCC$7 z&u}!j2!H+T??9XPC6K*yF~#hKd}WIjNA0h&EL}~lUW~Zqjti`&N0iYwxME(q%nqkj z7E9ZtsTo{a|KOVU@fGa*?N@AXt>kAsqaMm(DW1$%_W8C<>cp!)u8=TW&ZVx9@W=oc z`;YMOhK+v6^Hjzgs`DxGRvd2=2VZ|?FsblwV4!s4N$p!uOdS^dywhx1T%`V$I4a>* zjslm`=Y*4e!hz;uabtQ5*e!`-OWW4hvZJ>&Xk-h@yH z7S!>Z6AzbAnPl_{FKwlms)|*t#*vuxOgB=Q3L@1>1%+2M8pjSd+mo-Hm5Ig^o~2j2 z8p|TS;*SO=9g_)W`Vd*Jn5vL^sU{ynOR}*d#9C z)`+OaQ+1)5u|*Edm}mSO`Nso_)$VyCWRmy;!Fu+sA2KZYN7(%Db0xce&90=sB$ zXGi+WG1hC^+s>>txV$VL87Obpnp5cc{Y~6C_1U?bX~Q4Vc9WW$)ojlm+-oksKNU60 z@pj4hb<0$9oW`Z~YpKM0jQZwS;z9T!eEc`hzyFf?{o5Ps+X{mnC*^D0S*&i$S)D$f zq&yZ9bUbyG+lPi!HKsw0k_B(d>mj-*1y&zyR+bgKh+~1+D-7c@f~)#6ws*v0Eb{eV z&?rbbE6OOerAK84^}Psc^1;6{_A*vJgwAz9?E5hH>3lP6dHwE`lCq`-wd7kB?xntV zn6^uJzYXOdYroP^yvL6dkB|B8<3ayX(EOX=FG(W$Oc^do_HWf`A%QXiQ=yb;ncQ5Y zGK@r>i!QLSPXBp;N`wQL7qPMlMn)61brve;0$&hwNzc7>lY>hcaWlC$qCI#2%Glci zW0k&Q(f+2Cv>waNQwwGq3u@>T`(*m3UDHEridN3r;Ix71sSS8bm?NOql;*PeK{K-_6( zyEbu=Td`~qW6{NdnFv)p6i&uvh>Qlm3m9yP934`Bd89bsfv`PL**YgOJH5!2$JnW_ zqk@0xdS=wA#yp1sJ!eYNkz~C)T28Zir5eUmSFte-@3~YQ22X?{s*ZLR($?L8_uIIK@|1&F4uX($mtLCYEgtWvD*7*e^9#pL-O{ z8Ja)Rm;EDq-8e*@c(|#@w}Dn6tabH;dVxzDrRyqT9;>u{ zUQMRj3d1ZUX`9RnU&vP^bH3mVs@uVPSmC?Ki?q#{`x6zBoU^=@oh{j)dPm%qW9dfi zoKuGzeFr}ZCTAx7tWk6>B`>I$PB|0Ahjsq1ACjIZ-n+cZ2_027|^b7H`K9G`*Hg&nD zEH8LQ$}lEo9Itdr4RYg=!)#XSsa9CHdw3uFG)Nq5w1uoWtK3tL=6sq06eg+#No?0b zo2-tN5Tp%BOFLUu+$n(&-mS9WME8A<`qt1R!`0vMm%i-;-?q;l;K(8;u*cAz_b6Lb zI*v&v8LjM;OWT}?dC3_>f+1n9u1NntrdVo@nH7^h^b4t28aN+>J65vmc(iO<`7&T0 zvMl!u)C}L`mfBRugjXy!2g#Y6r`h59%yur0V{g1VGE!Ti-rX#hGcNktLv;F7WXtPs z@QzH^W$XS+ctDOR6PEPw~ZatYRARAn~;q^Ntg+ZN%Ku-ivw-T~|KUwjMl6 zN3iUxcss_c5z9j|M44qUaU}S=Q6m3jN6yI2vL~32a^j|lVr7P&!?(pe!&3I!IyY+c zqS`i?av_ATegS*Zvvl$t&UIKwUjzE<@4WBO6IzekeX4$mu3Yc!5^3EaiF&k08Sksb z5`8w)Zpx)PXSdg+)#38hzIVP2V;Ly($bAxqPDdf|m_%0V^SM~N(*~z~%V>O>qNE6! zvm>aANlvgk-b&~d`v%(1v|B|7XP#EYy#joJNA`w%!H!Mf`{0ufpGh$AKzxe*ZbPT^aZK#?z(S3gX#q-7Z zXH7@rosNu3>WX0nkd4XVy}jU{{Cy7h%xQEPt!mnQSQB?iNdjh!`_8N0n3yJEF7YkV z#+GmGAic=rq9HEm;@(PcIQY4n((o7NDCR9|`Kl}W%`RQJyNaib7}C$(#W|^Zv)P#k z%W72c?g0MM*Qd&29$_6j`1om~)g73lul(;~`b76%`8%*2{eJkXmvwI14XWz0$~_e1 zrTySLjw7UG+Dxjt@zIxyywtY=7ygOc+nU|8N0f9hdF713#V1dUenW57;sX01Rs=U` ze-W-7oa!i7lxOXkeV(4~S|dfJZ}U$f-itmswU3vr4A67`+NGi;nsNMtuMxHo@4&0F zf!-o{fN(&jO}b2d0?*qa?(LcDm=LLFOUdM$jX zsj7Wf$&Zqd{^=M+ukdXXF5BR|JXT`uTAurcTDFnC^2}luQ?+j*>j_%=y2Hz~${-n# zbnU}$pelJ>W`8!USFaG~XC02qYQ_!1TdKs9=q_AzQVp1|yOwau2B(wn?&(x17RFm! zx%a$X2?>1ED76s%qYxNs_};K=#6WuxJ$mt+BhKxUE;vOw$qkq|&D&mucXXuChI}K{ zEG4MKZgHAp)$XMN>2P(|k&!^{UjB$-W$tXcZ6owER?m!Z9waf9J-;XhFQvVV-|!(d zP`>%Ei{1S0*6eR*X!ZD8ey%NdJTZFd&{0jCU$2OU0)LXLHR#6^f*@68w87WjwA`44z1`1RlPge;Fa8;#4gSqQtSNJ>PBU%XYhK} zMQG3!(Og?=F?m0d~@z3~6~f8E^&1UG+d^U2tdKg7U)a#s~)a-|F( F{ul1$0T2KH literal 0 HcmV?d00001 From 879d22c7ca1e74b22aefaec82d1435a1e6acc525 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 27 Apr 2018 15:39:00 +0200 Subject: [PATCH 107/145] Added Freeze/Thaw and BusyCursor to selection_changed in object list (on Plater), It fixes visible rendering on MSW --- lib/Slic3r/GUI/Plater.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 869207b4da..4a013bf2d9 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1805,22 +1805,26 @@ sub on_config_change { sub list_item_deselected { my ($self, $event) = @_; return if $PreventListEvents; + $self->{_lecursor} = Wx::BusyCursor->new(); if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); $self->{canvas}->Refresh; #FIXME VBOs are being refreshed just to change a selection color? $self->{canvas3D}->reload_scene if $self->{canvas3D}; } + undef $self->{_lecursor}; } sub list_item_selected { my ($self, $event) = @_; return if $PreventListEvents; + $self->{_lecursor} = Wx::BusyCursor->new(); my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); $self->{canvas}->Refresh; #FIXME VBOs are being refreshed just to change a selection color? $self->{canvas3D}->reload_scene if $self->{canvas3D}; + undef $self->{_lecursor}; } sub list_item_activated { @@ -1954,7 +1958,8 @@ sub selection_changed { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; - + + $self->Freeze; if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_sel) @@ -2005,6 +2010,7 @@ sub selection_changed { # prepagate the event to the frame (a custom Wx event would be cleaner) $self->GetFrame->on_plater_selection_changed($have_sel); + $self->Thaw; } sub select_object { From bb4c4d9ecfa83e639fa3fa85946910a8190b8b67 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 27 Apr 2018 15:44:32 +0200 Subject: [PATCH 108/145] Legacy data update dialog: Add link to wiki --- xs/src/slic3r/GUI/UpdateDialogs.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp index 62534e5981..da212cf136 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.cpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -18,6 +18,8 @@ namespace Slic3r { namespace GUI { +static const std::string CONFIG_UPDATE_WIKI_URL("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update"); + enum { CONTENT_WIDTH = 400, BORDER = 30, @@ -225,7 +227,13 @@ MsgDataLegacy::MsgDataLegacy() : content_sizer->Add(text); content_sizer->AddSpacer(VERT_SPACING); - // TODO: Add link to wiki? + auto *text2 = new wxStaticText(this, wxID_ANY, _(L("For more information please visit our wiki page:"))); + static const wxString url("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update"); + // The wiki page name is intentionally not localized: + auto *link = new wxHyperlinkCtrl(this, wxID_ANY, "Slic3r PE 1.40 configuration update", CONFIG_UPDATE_WIKI_URL); + content_sizer->Add(text2); + content_sizer->Add(link); + content_sizer->AddSpacer(VERT_SPACING); Fit(); } From a208639ce7fea9463b981a35851c676a56b9a2b9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Apr 2018 08:57:41 +0200 Subject: [PATCH 109/145] Fixed initial view on Linux Ubuntu --- lib/Slic3r/GUI/3DScene.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 17fcd46242..352a612643 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1152,6 +1152,8 @@ sub InitGL { $self->volumes->finalize_geometry(1) if ($^O eq 'linux' && $self->UseVBOs); + $self->zoom_to_bed; + glClearColor(0, 0, 0, 1); glColor3f(1, 0, 0); glEnable(GL_DEPTH_TEST); From 43d2027b767c59cba2fe7b935e5d40488145bab6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 30 Apr 2018 11:11:48 +0200 Subject: [PATCH 110/145] ToolTips are showing on GTK --- xs/src/slic3r/GUI/OptionsGroup.cpp | 16 ++++++++-------- xs/src/slic3r/GUI/OptionsGroup.hpp | 24 ++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 7902fa1285..09a2fe50f6 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -22,13 +22,13 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co // is the normal type. if (opt.gui_type.compare("select") == 0) { } else if (opt.gui_type.compare("select_open") == 0) { - m_fields.emplace(id, STDMOVE(Choice::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); } else if (opt.gui_type.compare("color") == 0) { - m_fields.emplace(id, STDMOVE(ColourPicker::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(ColourPicker::Create(parent(), opt, id))); } else if (opt.gui_type.compare("f_enum_open") == 0 || opt.gui_type.compare("i_enum_open") == 0 || opt.gui_type.compare("i_enum_closed") == 0) { - m_fields.emplace(id, STDMOVE(Choice::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); } else if (opt.gui_type.compare("slider") == 0) { } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl } else { @@ -40,21 +40,21 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co case coPercents: case coString: case coStrings: - m_fields.emplace(id, STDMOVE(TextCtrl::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(TextCtrl::Create(parent(), opt, id))); break; case coBool: case coBools: - m_fields.emplace(id, STDMOVE(CheckBox::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(CheckBox::Create(parent(), opt, id))); break; case coInt: case coInts: - m_fields.emplace(id, STDMOVE(SpinCtrl::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(SpinCtrl::Create(parent(), opt, id))); break; case coEnum: - m_fields.emplace(id, STDMOVE(Choice::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); break; case coPoints: - m_fields.emplace(id, STDMOVE(PointCtrl::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(PointCtrl::Create(parent(), opt, id))); break; case coNone: break; default: diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index f01ef671c0..01d9184fd1 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -94,7 +94,13 @@ public: /// Returns a copy of the pointer of the parent wxWindow. /// Accessor function is because users are not allowed to change the parent /// but defining it as const means a lot of const_casts to deal with wx functions. - inline wxWindow* parent() const { return m_parent; } + inline wxWindow* parent() const { +#ifdef __WXGTK__ + return m_panel; +#else + return m_parent; +#endif /* __WXGTK__ */ + } void append_line(const Line& line, wxStaticText** colored_Label = nullptr); Line create_single_option_line(const Option& option) const; @@ -130,8 +136,15 @@ public: m_grid_sizer = new wxFlexGridSizer(0, num_columns, 0,0); static_cast(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL); static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0); - +#ifdef __WXGTK__ + m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel->SetSizer(m_grid_sizer); + m_panel->Layout(); + sizer->Fit(m_panel); + sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); +#else sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); +#endif /* __WXGTK__ */ } protected: @@ -147,6 +160,13 @@ protected: // "true" if option is created in preset tabs bool m_is_tab_opt{ false }; + // This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox + // Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel + // inside it before you insert the other controls. +#ifdef __WXGTK__ + wxPanel* m_panel {nullptr}; +#endif /* __WXGTK__ */ + /// Generate a wxSizer or wxWindow from a configuration option /// Precondition: opt resolves to a known ConfigOption /// Postcondition: fields contains a wx gui object. From 4344eaebca629b069a976c6ce18d1f1b352d582a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Apr 2018 12:03:06 +0200 Subject: [PATCH 111/145] Added versioning to amf file --- xs/src/libslic3r/Format/AMF.cpp | 118 ++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 98683cd8aa..b446f456b2 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -24,6 +24,12 @@ #include +// VERSION NUMBERS +// 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them. +// 1 : Introduction of amf versioning. No other change in data saved into amf files. +const unsigned int VERSION_AMF = 1; +const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; + const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config"; namespace Slic3r @@ -32,6 +38,7 @@ namespace Slic3r struct AMFParserContext { AMFParserContext(XML_Parser parser, const std::string& archive_filename, PresetBundle* preset_bundle, Model *model) : + m_version(0), m_parser(parser), m_model(*model), m_object(nullptr), @@ -137,6 +144,8 @@ struct AMFParserContext std::vector instances; }; + // Version of the amf file + unsigned int m_version; // Current Expat XML parser instance. XML_Parser m_parser; // Model to receive objects extracted from an AMF file. @@ -360,9 +369,9 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_VERTEX: assert(m_object); // Parse the vertex data - m_object_vertices.emplace_back(atof(m_value[0].c_str())); - m_object_vertices.emplace_back(atof(m_value[1].c_str())); - m_object_vertices.emplace_back(atof(m_value[2].c_str())); + m_object_vertices.emplace_back((float)atof(m_value[0].c_str())); + m_object_vertices.emplace_back((float)atof(m_value[1].c_str())); + m_object_vertices.emplace_back((float)atof(m_value[2].c_str())); m_value[0].clear(); m_value[1].clear(); m_value[2].clear(); @@ -462,6 +471,10 @@ void AMFParserContext::endElement(const char * /* name */) if (m_volume && m_value[0] == "name") m_volume->name = std::move(m_value[1]); } + else if (strncmp(m_value[0].c_str(), SLIC3RPE_AMF_VERSION, strlen(SLIC3RPE_AMF_VERSION)) == 0) { + m_version = (unsigned int)atoi(m_value[1].c_str()); + } + m_value[0].clear(); m_value[1].clear(); break; @@ -543,46 +556,8 @@ bool load_amf_file(const char *path, PresetBundle* bundle, Model *model) return result; } -// Load an AMF archive into a provided model. -bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) +bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, const char* path, PresetBundle* bundle, Model* model, unsigned int& version) { - if ((path == nullptr) || (model == nullptr)) - return false; - - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - mz_bool res = mz_zip_reader_init_file(&archive, path, 0); - if (res == 0) - { - printf("Unable to init zip reader\n"); - return false; - } - - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - if (num_entries != 1) - { - printf("Found invalid number of entries\n"); - mz_zip_reader_end(&archive); - return false; - } - - mz_zip_archive_file_stat stat; - res = mz_zip_reader_file_stat(&archive, 0, &stat); - if (res == 0) - { - printf("Unable to extract entry statistics\n"); - mz_zip_reader_end(&archive); - return false; - } - - if (!boost::iends_with(stat.m_filename, ".amf")) - { - printf("Found invalid internal filename\n"); - mz_zip_reader_end(&archive); - return false; - } - if (stat.m_uncomp_size == 0) { printf("Found invalid size\n"); @@ -610,7 +585,7 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) return false; } - res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0); if (res == 0) { printf("Error while reading model data to buffer\n"); @@ -627,6 +602,62 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) ctx.endDocument(); + version = ctx.m_version; + + return true; +} + +// Load an AMF archive into a provided model. +bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) +{ + if ((path == nullptr) || (model == nullptr)) + return false; + + unsigned int version = 0; + + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + mz_bool res = mz_zip_reader_init_file(&archive, path, 0); + if (res == 0) + { + printf("Unable to init zip reader\n"); + return false; + } + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + mz_zip_archive_file_stat stat; + // we first loop the entries to read from the archive the .amf file only, in order to extract the version from it + for (mz_uint i = 0; i < num_entries; ++i) + { + if (mz_zip_reader_file_stat(&archive, i, &stat)) + { + if (boost::iends_with(stat.m_filename, ".amf")) + { + if (!extract_model_from_archive(archive, stat, path, bundle, model, version)) + { + mz_zip_reader_end(&archive); + printf("Archive does not contain a valid model"); + return false; + } + + break; + } + } + } + +#if 0 // forward compatibility + // we then loop again the entries to read other files stored in the archive + for (mz_uint i = 0; i < num_entries; ++i) + { + if (mz_zip_reader_file_stat(&archive, i, &stat)) + { + // add code to extract the file + } + } +#endif // forward compatibility + mz_zip_reader_end(&archive); return true; } @@ -664,6 +695,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c stream << "\n"; stream << "\n"; stream << "Slic3r " << SLIC3R_VERSION << "\n"; + stream << "" << VERSION_AMF << "\n"; if (export_print_config) { From 5624b8afd21733cf813b5664f03228176f03a9f0 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 30 Apr 2018 14:31:57 +0200 Subject: [PATCH 112/145] Add a new error dialog --- lib/Slic3r/GUI.pm | 2 +- xs/CMakeLists.txt | 3 + xs/src/slic3r/GUI/GUI.cpp | 20 ++++--- xs/src/slic3r/GUI/GUI.hpp | 1 + xs/src/slic3r/GUI/MsgDialog.cpp | 87 +++++++++++++++++++++++++++++ xs/src/slic3r/GUI/MsgDialog.hpp | 65 +++++++++++++++++++++ xs/src/slic3r/GUI/UpdateDialogs.cpp | 57 ++----------------- xs/src/slic3r/GUI/UpdateDialogs.hpp | 25 +-------- xs/xsp/GUI.xsp | 3 + 9 files changed, 178 insertions(+), 85 deletions(-) create mode 100644 xs/src/slic3r/GUI/MsgDialog.cpp create mode 100644 xs/src/slic3r/GUI/MsgDialog.hpp diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 89a8e79748..04dc803231 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -249,7 +249,7 @@ sub catch_error { # static method accepting a wxWindow object as first parameter sub show_error { my ($parent, $message) = @_; - Wx::MessageDialog->new($parent, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal; + Slic3r::GUI::show_error_id($parent ? $parent->GetId() : 0, $message); } # static method accepting a wxWindow object as first parameter diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 5f1c11951b..f67697b13e 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -222,6 +222,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp ${LIBDIR}/slic3r/GUI/ConfigWizard.cpp ${LIBDIR}/slic3r/GUI/ConfigWizard.hpp + ${LIBDIR}/slic3r/GUI/MsgDialog.cpp + ${LIBDIR}/slic3r/GUI/MsgDialog.hpp ${LIBDIR}/slic3r/GUI/UpdateDialogs.cpp ${LIBDIR}/slic3r/GUI/UpdateDialogs.hpp ${LIBDIR}/slic3r/Utils/Http.cpp @@ -638,6 +640,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy "$" "${PERL_LOCAL_LIB_DIR}/auto/Slic3r/XS/" COMMAND ${CMAKE_COMMAND} -E make_directory "${PERL_LOCAL_LIB_DIR}/Slic3r/" COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/xs/lib/Slic3r/XS.pm" "${PERL_LOCAL_LIB_DIR}/Slic3r/" + COMMENT "Installing XS.pm and XS.{so,dll,bundle} into the local-lib directory ..." ) if(APPLE) add_custom_command( diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 6bac39bd7a..20a8a45db0 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -50,6 +50,7 @@ #include "AppConfig.hpp" #include "ConfigSnapshotDialog.hpp" #include "Utils.hpp" +#include "MsgDialog.hpp" #include "ConfigWizard.hpp" #include "Preferences.hpp" #include "PresetBundle.hpp" @@ -652,21 +653,26 @@ void add_created_tab(Tab* panel) g_wxTabPanel->AddPage(panel, panel->title()); } -void show_error(wxWindow* parent, const wxString& message){ - auto msg_wingow = new wxMessageDialog(parent, message, _(L("Error")), wxOK | wxICON_ERROR); - msg_wingow->ShowModal(); +void show_error(wxWindow* parent, const wxString& message) { + ErrorDialog msg(parent, message); + msg.ShowModal(); +} + +void show_error_id(int id, const std::string& message) { + auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr; + show_error(parent, message); } void show_info(wxWindow* parent, const wxString& message, const wxString& title){ - auto msg_wingow = new wxMessageDialog(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION); - msg_wingow->ShowModal(); + wxMessageDialog msg_wingow(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION); + msg_wingow.ShowModal(); } void warning_catcher(wxWindow* parent, const wxString& message){ if (message == _(L("GLUquadricObjPtr | Attempt to free unreferenced scalar")) ) return; - auto msg = new wxMessageDialog(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); - msg->ShowModal(); + wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); + msg.ShowModal(); } wxApp* get_app(){ diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index d4b5988a80..c1ecc3dfd1 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -116,6 +116,7 @@ void add_created_tab(Tab* panel); void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); void show_error(wxWindow* parent, const wxString& message); +void show_error_id(int id, const std::string& message); // For Perl void show_info(wxWindow* parent, const wxString& message, const wxString& title); void warning_catcher(wxWindow* parent, const wxString& message); diff --git a/xs/src/slic3r/GUI/MsgDialog.cpp b/xs/src/slic3r/GUI/MsgDialog.cpp new file mode 100644 index 0000000000..532960cfc9 --- /dev/null +++ b/xs/src/slic3r/GUI/MsgDialog.cpp @@ -0,0 +1,87 @@ +#include "MsgDialog.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "GUI.hpp" +#include "ConfigWizard.hpp" + +namespace Slic3r { +namespace GUI { + + +MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id) : + MsgDialog(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id) +{} + +MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) : + wxDialog(parent, wxID_ANY, title), + boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)), + content_sizer(new wxBoxSizer(wxVERTICAL)), + btn_sizer(new wxBoxSizer(wxHORIZONTAL)) +{ + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + auto *topsizer = new wxBoxSizer(wxHORIZONTAL); + auto *rightsizer = new wxBoxSizer(wxVERTICAL); + + auto *headtext = new wxStaticText(this, wxID_ANY, headline); + headtext->SetFont(boldfont); + headtext->Wrap(CONTENT_WIDTH); + rightsizer->Add(headtext); + rightsizer->AddSpacer(VERT_SPACING); + + rightsizer->Add(content_sizer, 1, wxEXPAND); + + if (button_id != wxID_NONE) { + auto *button = new wxButton(this, button_id); + button->SetFocus(); + btn_sizer->Add(button); + } + + rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL); + + auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap)); + + topsizer->Add(logo, 0, wxALL, BORDER); + topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER); + + SetSizerAndFit(topsizer); +} + +MsgDialog::~MsgDialog() {} + + +// ErrorDialog + +ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) : + MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")))) +{ + auto *panel = new wxScrolledWindow(this); + auto *p_sizer = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(p_sizer); + + auto *text = new wxStaticText(panel, wxID_ANY, msg); + text->Wrap(CONTENT_WIDTH); + p_sizer->Add(text, 1, wxEXPAND); + + panel->SetMinSize(wxSize(CONTENT_WIDTH, CONTENT_HEIGHT)); + panel->SetScrollRate(0, 5); + + content_sizer->Add(panel, 1, wxEXPAND); + + Fit(); +} + +ErrorDialog::~ErrorDialog() {} + + + +} +} diff --git a/xs/src/slic3r/GUI/MsgDialog.hpp b/xs/src/slic3r/GUI/MsgDialog.hpp new file mode 100644 index 0000000000..a011270238 --- /dev/null +++ b/xs/src/slic3r/GUI/MsgDialog.hpp @@ -0,0 +1,65 @@ +#ifndef slic3r_MsgDialog_hpp_ +#define slic3r_MsgDialog_hpp_ + +#include +#include + +#include +#include +#include + +#include "slic3r/Utils/Semver.hpp" + +class wxBoxSizer; +class wxCheckBox; + +namespace Slic3r { + +namespace GUI { + + +// A message / query dialog with a bitmap on the left and any content on the right +// with buttons underneath. +struct MsgDialog : wxDialog +{ + MsgDialog(MsgDialog &&) = delete; + MsgDialog(const MsgDialog &) = delete; + MsgDialog &operator=(MsgDialog &&) = delete; + MsgDialog &operator=(const MsgDialog &) = delete; + virtual ~MsgDialog(); + +protected: + enum { + CONTENT_WIDTH = 500, + CONTENT_HEIGHT = 300, + BORDER = 30, + VERT_SPACING = 15, + HORIZ_SPACING = 5, + }; + + // button_id is an id of a button that can be added by default, use wxID_NONE to disable + MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK); + MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id = wxID_OK); + + wxFont boldfont; + wxBoxSizer *content_sizer; + wxBoxSizer *btn_sizer; +}; + + +// Generic error dialog, used for displaying exceptions +struct ErrorDialog : MsgDialog +{ + ErrorDialog(wxWindow *parent, const wxString &msg); + ErrorDialog(ErrorDialog &&) = delete; + ErrorDialog(const ErrorDialog &) = delete; + ErrorDialog &operator=(ErrorDialog &&) = delete; + ErrorDialog &operator=(const ErrorDialog &) = delete; + virtual ~ErrorDialog(); +}; + + +} +} + +#endif diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp index da212cf136..33c9c0c286 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.cpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -20,60 +20,11 @@ namespace GUI { static const std::string CONFIG_UPDATE_WIKI_URL("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update"); -enum { - CONTENT_WIDTH = 400, - BORDER = 30, - VERT_SPACING = 15, - HORIZ_SPACING = 5, -}; - - -MsgDialog::MsgDialog(const wxString &title, const wxString &headline, wxWindowID button_id) : - MsgDialog(title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id) -{} - -MsgDialog::MsgDialog(const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) : - wxDialog(nullptr, wxID_ANY, title), - boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)), - content_sizer(new wxBoxSizer(wxVERTICAL)), - btn_sizer(new wxBoxSizer(wxHORIZONTAL)) -{ - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - auto *topsizer = new wxBoxSizer(wxHORIZONTAL); - auto *rightsizer = new wxBoxSizer(wxVERTICAL); - - auto *headtext = new wxStaticText(this, wxID_ANY, headline); - headtext->SetFont(boldfont); - headtext->Wrap(CONTENT_WIDTH); - rightsizer->Add(headtext); - rightsizer->AddSpacer(VERT_SPACING); - - rightsizer->Add(content_sizer); - - if (button_id != wxID_NONE) { - auto *button = new wxButton(this, button_id); - button->SetFocus(); - btn_sizer->Add(button); - } - - rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL); - - auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap)); - - topsizer->Add(logo, 0, wxALL, BORDER); - topsizer->Add(rightsizer, 0, wxALL, BORDER); - - SetSizerAndFit(topsizer); -} - -MsgDialog::~MsgDialog() {} - // MsgUpdateSlic3r MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online) : - MsgDialog(_(L("Update available")), _(L("New version of Slic3r PE is available"))), + MsgDialog(nullptr, _(L("Update available")), _(L("New version of Slic3r PE is available"))), ver_current(ver_current), ver_online(ver_online) { @@ -115,7 +66,7 @@ bool MsgUpdateSlic3r::disable_version_check() const // MsgUpdateConfig MsgUpdateConfig::MsgUpdateConfig(const std::unordered_map &updates) : - MsgDialog(_(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE) + MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE) { auto *text = new wxStaticText(this, wxID_ANY, _(L( "Would you like to install it?\n\n" @@ -154,7 +105,7 @@ MsgUpdateConfig::~MsgUpdateConfig() {} // MsgDataIncompatible MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map &incompats) : - MsgDialog(_(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png"))), wxID_NONE) + MsgDialog(nullptr, _(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png"))), wxID_NONE) { auto *text = new wxStaticText(this, wxID_ANY, _(L( "This version of Slic3r PE is not compatible with currently installed configuration bundles.\n" @@ -207,7 +158,7 @@ MsgDataIncompatible::~MsgDataIncompatible() {} // MsgDataLegacy MsgDataLegacy::MsgDataLegacy() : - MsgDialog(_(L("Configuration update")), _(L("Configuration update"))) + MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update"))) { auto *text = new wxStaticText(this, wxID_ANY, wxString::Format( _(L( diff --git a/xs/src/slic3r/GUI/UpdateDialogs.hpp b/xs/src/slic3r/GUI/UpdateDialogs.hpp index e339fbe0d3..62548b98be 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.hpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.hpp @@ -4,11 +4,8 @@ #include #include -#include -#include -#include - #include "slic3r/Utils/Semver.hpp" +#include "MsgDialog.hpp" class wxBoxSizer; class wxCheckBox; @@ -18,26 +15,6 @@ namespace Slic3r { namespace GUI { -// A message / query dialog with a bitmap on the left and any content on the right -// with buttons underneath. -struct MsgDialog : wxDialog -{ - MsgDialog(MsgDialog &&) = delete; - MsgDialog(const MsgDialog &) = delete; - MsgDialog &operator=(MsgDialog &&) = delete; - MsgDialog &operator=(const MsgDialog &) = delete; - virtual ~MsgDialog(); - -protected: - // button_id is an id of a button that can be added by default, use wxID_NONE to disable - MsgDialog(const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK); - MsgDialog(const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id = wxID_OK); - - wxFont boldfont; - wxBoxSizer *content_sizer; - wxBoxSizer *btn_sizer; -}; - // A confirmation dialog listing configuration updates class MsgUpdateSlic3r : public MsgDialog { diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index be04af1f93..aa95bd647d 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -42,6 +42,9 @@ void add_config_menu(SV *ui, int event_preferences_changed, int event_language_c void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) %code%{ Slic3r::GUI::create_preset_tabs(no_controller, event_value_change, event_presets_changed); %}; +void show_error_id(int id, std::string msg) + %code%{ Slic3r::GUI::show_error_id(id, msg); %}; + TabIface* get_preset_tab(char *name) %code%{ RETVAL=Slic3r::GUI::get_preset_tab_iface(name); %}; From 3c13c4f10301912c1459620fecc2efc8074c8ea2 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Apr 2018 15:27:01 +0200 Subject: [PATCH 113/145] Added versioning to 3mf file --- xs/src/libslic3r/Format/3mf.cpp | 80 +++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index dde64a28f6..0467962c3d 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -16,6 +16,12 @@ #include #include +// VERSION NUMBERS +// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. +// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files. +const unsigned int VERSION_3MF = 1; +const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file + const std::string MODEL_FOLDER = "3D/"; const std::string MODEL_EXTENSION = ".model"; const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA @@ -37,9 +43,9 @@ const char* COMPONENTS_TAG = "components"; const char* COMPONENT_TAG = "component"; const char* BUILD_TAG = "build"; const char* ITEM_TAG = "item"; +const char* METADATA_TAG = "metadata"; const char* CONFIG_TAG = "config"; -const char* METADATA_TAG = "metadata"; const char* VOLUME_TAG = "volume"; const char* UNIT_ATTR = "unit"; @@ -318,6 +324,9 @@ namespace Slic3r { typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; + // Version of the 3mf file + unsigned int m_version; + XML_Parser m_xml_parser; Model* m_model; float m_unit_factor; @@ -329,6 +338,8 @@ namespace Slic3r { CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; IdToLayerHeightsProfileMap m_layer_heights_profiles; + std::string m_curr_metadata_name; + std::string m_curr_characters; public: _3MF_Importer(); @@ -349,6 +360,7 @@ namespace Slic3r { // handlers to parse the .model file void _handle_start_model_xml_element(const char* name, const char** attributes); void _handle_end_model_xml_element(const char* name); + void _handle_model_xml_characters(const XML_Char* s, int len); // handlers to parse the MODEL_CONFIG_FILE file void _handle_start_config_xml_element(const char* name, const char** attributes); @@ -390,6 +402,9 @@ namespace Slic3r { bool _handle_start_item(const char** attributes, unsigned int num_attributes); bool _handle_end_item(); + bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); + bool _handle_end_metadata(); + bool _create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Matrix4x4& matrix); @@ -411,6 +426,7 @@ namespace Slic3r { // callbacks to parse the .model file static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name); + static void XMLCALL _handle_model_xml_characters(void* userData, const XML_Char* s, int len); // callbacks to parse the MODEL_CONFIG_FILE file static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes); @@ -418,9 +434,12 @@ namespace Slic3r { }; _3MF_Importer::_3MF_Importer() - : m_xml_parser(nullptr) + : m_version(0) + , m_xml_parser(nullptr) , m_model(nullptr) , m_unit_factor(1.0f) + , m_curr_metadata_name("") + , m_curr_characters("") { } @@ -431,6 +450,7 @@ namespace Slic3r { bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle) { + m_version = 0; m_model = &model; m_unit_factor = 1.0f; m_curr_object.reset(); @@ -442,6 +462,8 @@ namespace Slic3r { m_curr_config.volume_id = -1; m_objects_metadata.clear(); m_layer_heights_profiles.clear(); + m_curr_metadata_name.clear(); + m_curr_characters.clear(); clear_errors(); return _load_model_from_file(filename, model, bundle); @@ -477,6 +499,8 @@ namespace Slic3r { mz_uint num_entries = mz_zip_reader_get_num_files(&archive); mz_zip_archive_file_stat stat; + + // we first loop the entries to read from the archive the .model file only, in order to extract the version from it for (mz_uint i = 0; i < num_entries; ++i) { if (mz_zip_reader_file_stat(&archive, i, &stat)) @@ -494,7 +518,18 @@ namespace Slic3r { return false; } } - else if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) + } + } + + // we then loop again the entries to read other files stored in the archive + for (mz_uint i = 0; i < num_entries; ++i) + { + if (mz_zip_reader_file_stat(&archive, i, &stat)) + { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { // extract slic3r lazer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); @@ -595,6 +630,7 @@ namespace Slic3r { XML_SetUserData(m_xml_parser, (void*)this); XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element); + XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_xml_characters); void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size); if (parser_buffer == nullptr) @@ -784,6 +820,8 @@ namespace Slic3r { res = _handle_start_build(attributes, num_attributes); else if (::strcmp(ITEM_TAG, name) == 0) res = _handle_start_item(attributes, num_attributes); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_start_metadata(attributes, num_attributes); if (!res) _stop_xml_parser(); @@ -820,11 +858,18 @@ namespace Slic3r { res = _handle_end_build(); else if (::strcmp(ITEM_TAG, name) == 0) res = _handle_end_item(); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_end_metadata(); if (!res) _stop_xml_parser(); } + void _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len) + { + m_curr_characters.append(s, len); + } + void _3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes) { if (m_xml_parser == nullptr) @@ -1131,6 +1176,25 @@ namespace Slic3r { return true; } + bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes) + { + m_curr_characters.clear(); + + std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); + if (!name.empty()) + m_curr_metadata_name = name; + + return true; + } + + bool _3MF_Importer::_handle_end_metadata() + { + if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) + m_version = (unsigned int)atoi(m_curr_characters.c_str()); + + return true; + } + bool _3MF_Importer::_create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; @@ -1437,6 +1501,13 @@ namespace Slic3r { importer->_handle_end_model_xml_element(name); } + void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_model_xml_characters(s, len); + } + void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes) { _3MF_Importer* importer = (_3MF_Importer*)userData; @@ -1640,7 +1711,8 @@ namespace Slic3r { { std::stringstream stream; stream << "\n"; - stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">\n"; + stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; stream << " <" << RESOURCES_TAG << ">\n"; BuildItemsList build_items; From 2d4cac00189aa75cf75637d0345a5befb2d80b8a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 30 Apr 2018 14:20:33 +0200 Subject: [PATCH 114/145] Added ability to change color of the labels of the system or modified values --- xs/src/slic3r/GUI/ButtonsDescription.cpp | 46 ++++++++++++++-- xs/src/slic3r/GUI/Field.hpp | 9 +++- xs/src/slic3r/GUI/GUI.cpp | 42 +++++++++++++-- xs/src/slic3r/GUI/GUI.hpp | 8 +-- xs/src/slic3r/GUI/Tab.cpp | 68 ++++++++++++++++++++++-- xs/src/slic3r/GUI/Tab.hpp | 3 +- 6 files changed, 158 insertions(+), 18 deletions(-) diff --git a/xs/src/slic3r/GUI/ButtonsDescription.cpp b/xs/src/slic3r/GUI/ButtonsDescription.cpp index b932985bf5..7ea16d9420 100644 --- a/xs/src/slic3r/GUI/ButtonsDescription.cpp +++ b/xs/src/slic3r/GUI/ButtonsDescription.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "GUI.hpp" @@ -9,7 +10,7 @@ namespace Slic3r { namespace GUI { ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions) : - wxDialog(parent, wxID_ANY, "Buttons Description", wxDefaultPosition, wxDefaultSize), + wxDialog(parent, wxID_ANY, "Buttons And Text Colors Description", wxDefaultPosition, wxDefaultSize), m_icon_descriptions(icon_descriptions) { auto grid_sizer = new wxFlexGridSizer(3, 20, 20); @@ -17,6 +18,7 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* ic auto main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(grid_sizer, 0, wxEXPAND | wxALL, 20); + // Icon description for (auto pair : *m_icon_descriptions) { auto icon = new wxStaticBitmap(this, wxID_ANY, *pair.first); @@ -32,8 +34,46 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* ic grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); } - auto button = CreateStdDialogButtonSizer(wxOK); - main_sizer->Add(button, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + // Text color description + auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value"))); + sys_label->SetForegroundColour(get_label_clr_sys()); + auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_sys()); + sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([sys_colour, sys_label](wxCommandEvent e) + { + sys_label->SetForegroundColour(sys_colour->GetColour()); + sys_label->Refresh(); + })); + size_t t= 0; + while (t < 3){ + grid_sizer->Add(new wxStaticText(this, wxID_ANY, ""), -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + ++t; + } + grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(sys_colour, -1, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(sys_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + + auto mod_label = new wxStaticText(this, wxID_ANY, _(L("Value was changed and is not equal to the system value or the last saved preset"))); + mod_label->SetForegroundColour(get_label_clr_modified()); + auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_modified()); + mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([mod_colour, mod_label](wxCommandEvent e) + { + mod_label->SetForegroundColour(mod_colour->GetColour()); + mod_label->Refresh(); + })); + grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(mod_colour, -1, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(mod_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + + + auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL); + main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + + wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); + btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) { + set_label_clr_sys(sys_colour->GetColour()); + set_label_clr_modified(mod_colour->GetColour()); + EndModal(wxID_OK); + }); SetSizer(main_sizer); main_sizer->SetSizeHints(this); diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 1856d94cf0..729f73d160 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -164,6 +164,13 @@ public: return false; } + bool set_label_colour_force(const wxColour *clr) { + if (m_Label == nullptr) return false; + m_Label->SetForegroundColour(*clr); + m_Label->Refresh(true); + return false; + } + bool set_undo_tooltip(const wxString *tip) { if (m_undo_tooltip != tip) { m_undo_tooltip = tip; @@ -194,7 +201,7 @@ protected: wxStaticText* m_Label = nullptr; // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. - const wxColour* m_label_color; + const wxColour* m_label_color = nullptr; // current value boost::any m_value; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 6bac39bd7a..d743d8708c 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -202,8 +202,8 @@ static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); if (luma >= 128) { - g_color_label_modified = wxColour(255, 108, 30);//wxColour(253, 88, 0); - g_color_label_sys = wxColour(19, 100, 44); //wxColour(26, 132, 57); + g_color_label_modified = wxColour(253, 88, 0); + g_color_label_sys = wxColour(26, 132, 57); } else { g_color_label_modified = wxColour(253, 111, 40); g_color_label_sys = wxColour(115, 220, 103); @@ -211,6 +211,21 @@ static void init_label_colours() g_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } +void update_label_colours_from_appconfig() +{ + if (g_AppConfig->has("label_clr_sys")){ + auto str = g_AppConfig->get("label_clr_sys"); + if (str != "") + g_color_label_sys = wxColour(str); + } + + if (g_AppConfig->has("label_clr_modified")){ + auto str = g_AppConfig->get("label_clr_modified"); + if (str != "") + g_color_label_modified = wxColour(str); + } +} + void set_wxapp(wxApp *app) { g_wxApp = app; @@ -512,6 +527,7 @@ void open_preferences_dialog(int event_preferences) void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) { + update_label_colours_from_appconfig(); add_created_tab(new TabPrint (g_wxTabPanel, no_controller)); add_created_tab(new TabFilament (g_wxTabPanel, no_controller)); add_created_tab(new TabPrinter (g_wxTabPanel, no_controller)); @@ -678,15 +694,31 @@ PresetBundle* get_preset_bundle() return g_PresetBundle; } -const wxColour& get_modified_label_clr() { +const wxColour& get_label_clr_modified() { return g_color_label_modified; } -const wxColour& get_sys_label_clr() { +const wxColour& get_label_clr_sys() { return g_color_label_sys; } -const wxColour& get_default_label_clr() { +void set_label_clr_modified(const wxColour& clr) { + g_color_label_modified = clr; + auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); + std::string str = clr_str.ToStdString(); + g_AppConfig->set("label_clr_modified", str); + g_AppConfig->save(); +} + +void set_label_clr_sys(const wxColour& clr) { + g_color_label_sys = clr; + auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); + std::string str = clr_str.ToStdString(); + g_AppConfig->set("label_clr_sys", str); + g_AppConfig->save(); +} + +const wxColour& get_label_clr_default() { return g_color_label_default; } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index d4b5988a80..62a791df54 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -84,10 +84,12 @@ AppConfig* get_app_config(); wxApp* get_app(); PresetBundle* get_preset_bundle(); -const wxColour& get_modified_label_clr(); -const wxColour& get_sys_label_clr(); -const wxColour& get_default_label_clr(); +const wxColour& get_label_clr_modified(); +const wxColour& get_label_clr_sys(); +const wxColour& get_label_clr_default(); unsigned get_colour_approx_luma(const wxColour &colour); +void set_label_clr_modified(const wxColour& clr); +void set_label_clr_sys(const wxColour& clr); extern void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 5e9b249ef5..bb1d938a85 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -110,7 +110,8 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_question_btn->SetBackgroundColour(color); } - m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information."))); + m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" + "or click this button."))); // Determine the theme color of OS (dark or light) auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -134,13 +135,20 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { auto dlg = new ButtonsDescription(this, &m_icon_descriptions); - dlg->ShowModal(); + if (dlg->ShowModal() == wxID_OK){ + // Colors for ui "decoration" + for (Tab *tab : get_tabs_list()){ + tab->m_sys_label_clr = get_label_clr_sys(); + tab->m_modified_label_clr = get_label_clr_modified(); + tab->update_labels_colour(); + } + } })); // Colors for ui "decoration" - m_sys_label_clr = get_sys_label_clr(); - m_modified_label_clr = get_modified_label_clr(); - m_default_text_clr = get_default_label_clr(); + m_sys_label_clr = get_label_clr_sys(); + m_modified_label_clr = get_label_clr_modified(); + m_default_text_clr = get_label_clr_default(); m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxBOTTOM, 3); @@ -278,6 +286,56 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo return page; } +void Tab::update_labels_colour() +{ + Freeze(); + //update options "decoration" + for (const auto opt : m_options_list) + { + const wxColour *color = &m_sys_label_clr; + + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0){ + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if (opt.first == "bed_shape" || opt.first == "compatible_printers") { + if (m_colored_Label != nullptr) { + m_colored_Label->SetForegroundColour(*color); + m_colored_Label->Refresh(true); + } + continue; + } + + Field* field = get_field(opt.first); + if (field == nullptr) continue; + field->set_label_colour_force(color); + } + Thaw(); + + auto cur_item = m_treectrl->GetFirstVisibleItem(); + while (cur_item){ + auto title = m_treectrl->GetItemText(cur_item); + for (auto page : m_pages) + { + if (page->title() != title) + continue; + + const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr : + page->m_is_modified_values ? &m_modified_label_clr : + &m_default_text_clr; + + m_treectrl->SetItemTextColour(cur_item, *clr); + break; + } + cur_item = m_treectrl->GetNextVisible(cur_item); + } +} + // Update UI according to changes void Tab::update_changed_ui() { diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 1ed5d1b368..62030bce34 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -57,7 +57,7 @@ public: { Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_vsizer = new wxBoxSizer(wxVERTICAL); - m_item_color = &get_default_label_clr(); + m_item_color = &get_label_clr_default(); SetSizer(m_vsizer); } ~Page(){} @@ -232,6 +232,7 @@ public: void toggle_show_hide_incompatible(); void update_show_hide_incompatible_button(); void update_ui_from_settings(); + void update_labels_colour(); void update_changed_ui(); void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page); void update_changed_tree_ui(); From 28effac0f169e7fa9e143b40e4a6762e97253f53 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 30 Apr 2018 17:07:30 +0200 Subject: [PATCH 115/145] Fix bitmap loading in new dialogs --- xs/src/slic3r/GUI/MsgDialog.cpp | 2 +- xs/src/slic3r/GUI/UpdateDialogs.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/MsgDialog.cpp b/xs/src/slic3r/GUI/MsgDialog.cpp index 532960cfc9..6fba47a37f 100644 --- a/xs/src/slic3r/GUI/MsgDialog.cpp +++ b/xs/src/slic3r/GUI/MsgDialog.cpp @@ -61,7 +61,7 @@ MsgDialog::~MsgDialog() {} // ErrorDialog ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) : - MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")))) + MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG)) { auto *panel = new wxScrolledWindow(this); auto *p_sizer = new wxBoxSizer(wxVERTICAL); diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp index 33c9c0c286..70d9c851c0 100644 --- a/xs/src/slic3r/GUI/UpdateDialogs.cpp +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -105,7 +105,7 @@ MsgUpdateConfig::~MsgUpdateConfig() {} // MsgDataIncompatible MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map &incompats) : - MsgDialog(nullptr, _(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png"))), wxID_NONE) + MsgDialog(nullptr, _(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), wxID_NONE) { auto *text = new wxStaticText(this, wxID_ANY, _(L( "This version of Slic3r PE is not compatible with currently installed configuration bundles.\n" From 4758b68e55e072b10dfc77413e11d01ba64af600 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 1 May 2018 10:35:07 +0200 Subject: [PATCH 116/145] Fix: Turn two Preset & PresetUpdater exceptions into error logs --- xs/src/slic3r/GUI/Preset.cpp | 10 +++++++--- xs/src/slic3r/Utils/PresetUpdater.cpp | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index eca809ec1f..bc59ac2f92 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -120,14 +121,15 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem model.id = section.first.substr(printer_model_key.size()); model.name = section.second.get("name", model.id); section.second.get("variants", ""); + const auto variants_field = section.second.get("variants", ""); std::vector variants; - if (Slic3r::unescape_strings_cstyle(section.second.get("variants", ""), variants)) { + if (Slic3r::unescape_strings_cstyle(variants_field, variants)) { for (const std::string &variant_name : variants) { if (model.variant(variant_name) == nullptr) model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); } } else { - // Log error? // XXX + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field; } if (! model.id.empty() && ! model.variants.empty()) res.models.push_back(std::move(model)); @@ -387,7 +389,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri // Remove the .ini suffix. name.erase(name.size() - 4); if (this->find_preset(name, false)) { - errors_cummulative += "The user preset \"" + name + "\" cannot be loaded. A system preset of the same name has already been loaded."; + // This happens when there's is a preset (most likely legacy one) with the same name as a system preset + // that's already been loaded from a bundle. + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; continue; } try { diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 9c5fe07481..3b786e0d67 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -252,7 +252,7 @@ void PresetUpdater::priv::sync_config(const std::set vendors) con // See if a there's a new version to download const auto recommended_it = new_index.recommended(); if (recommended_it == new_index.end()) { - BOOST_LOG_TRIVIAL(error) << "No recommended version for vendor: " << vendor.name << ", invalid index?"; + BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % vendor.name; continue; } const auto recommended = recommended_it->config_version; @@ -326,7 +326,7 @@ Updates PresetUpdater::priv::get_config_updates() const const auto recommended = idx.recommended(); if (recommended == idx.end()) { - throw std::runtime_error((boost::format("Invalid index: `%1%`") % idx.vendor()).str()); + BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor(); } BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%, version cached: %3%") From b4e63d47cbd96c36bc0264f924784c97d0d81bcf Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 26 Apr 2018 11:19:51 +0200 Subject: [PATCH 117/145] Yet another attempt to fix the layer height profile validation --- xs/src/libslic3r/Print.cpp | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index e692b1e9ee..80f68c51b6 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -598,10 +598,10 @@ std::string Print::validate() const return "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."; SlicingParameters slicing_params0 = this->objects.front()->slicing_parameters(); - const PrintObject* most_layered_object = this->objects.front(); // object with highest layer_height_profile.size() encountered so far + const PrintObject* tallest_object = this->objects.front(); // let's find the tallest object for (const auto* object : objects) - if (object->layer_height_profile.size() > most_layered_object->layer_height_profile.size()) - most_layered_object = object; + if (*(object->layer_height_profile.end()-2) > *(tallest_object->layer_height_profile.end()-2) ) + tallest_object = object; for (PrintObject *object : this->objects) { SlicingParameters slicing_params = object->slicing_parameters(); @@ -618,17 +618,26 @@ std::string Print::validate() const object->update_layer_height_profile(); object->layer_height_profile_valid = was_layer_height_profile_valid; - if ( this->config.variable_layer_height ) { - int i = 0; - while ( i < object->layer_height_profile.size() ) { - if (std::abs(most_layered_object->layer_height_profile[i] - object->layer_height_profile[i]) > EPSILON) - return "The Wipe tower is only supported if all objects have the same layer height profile"; - ++i; - if (i == object->layer_height_profile.size()-2) // this element contains the objects max z, if the other object is taller, - // it does not have to match - we will step over it - if (most_layered_object->layer_height_profile[i] > object->layer_height_profile[i]) - ++i; + if ( this->config.variable_layer_height ) { // comparing layer height profiles + bool failed = false; + if (tallest_object->layer_height_profile.size() >= object->layer_height_profile.size() ) { + int i = 0; + while ( i < object->layer_height_profile.size() && i < tallest_object->layer_height_profile.size()) { + if (std::abs(tallest_object->layer_height_profile[i] - object->layer_height_profile[i])) { + failed = true; + break; + } + ++i; + if (i == object->layer_height_profile.size()-2) // this element contains this objects max z + if (tallest_object->layer_height_profile[i] > object->layer_height_profile[i]) // the difference does not matter in this case + ++i; + } } + else + failed = true; + + if (failed) + return "The Wipe tower is only supported if all objects have the same layer height profile"; } /*for (size_t i = 5; i < object->layer_height_profile.size(); i += 2) From de0d1f34f3f13701d38d9dfb83e26feca1ded63f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 2 May 2018 10:52:17 +0200 Subject: [PATCH 118/145] Label in filament settings changed --- xs/src/slic3r/GUI/Tab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index bb1d938a85..96419f6c85 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1311,7 +1311,7 @@ void TabFilament::build() }; optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Toolchange behaviour"))); + optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers"))); optgroup->append_single_option_line("filament_loading_speed"); optgroup->append_single_option_line("filament_unloading_speed"); optgroup->append_single_option_line("filament_toolchange_delay"); From e9b83a20d0733a1f606ed61726d99bae4f8f6f09 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 2 May 2018 12:44:53 +0200 Subject: [PATCH 119/145] ConfigWizard: Add additional logging --- xs/src/slic3r/GUI/ConfigWizard.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 84f401ba11..1217f86831 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include #include @@ -741,9 +743,12 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (install_bundles.size() > 0) { // Install bundles from resources. updater->install_bundles_rsrc(std::move(install_bundles), snapshot); + } 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); } @@ -828,10 +833,13 @@ ConfigWizard::~ConfigWizard() {} bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater) { + BOOST_LOG_TRIVIAL(info) << "Running ConfigWizard, reason: " << p->run_reason; if (ShowModal() == wxID_OK) { p->apply_config(GUI::get_app_config(), preset_bundle, updater); + BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; return true; } else { + BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; return false; } } From 97a948fa522bab8c9ac0a8b4de62c3fe21e1e396 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 2 May 2018 12:27:46 +0200 Subject: [PATCH 120/145] Fix Perl path MSVC props file #857 --- cmake/msvc/{xs.wperl64d.props => xs.wperl64d.props.in} | 10 +++++++--- xs/CMakeLists.txt | 7 ++++++- 2 files changed, 13 insertions(+), 4 deletions(-) rename cmake/msvc/{xs.wperl64d.props => xs.wperl64d.props.in} (61%) diff --git a/cmake/msvc/xs.wperl64d.props b/cmake/msvc/xs.wperl64d.props.in similarity index 61% rename from cmake/msvc/xs.wperl64d.props rename to cmake/msvc/xs.wperl64d.props.in index b604a56e23..5c7461dc71 100644 --- a/cmake/msvc/xs.wperl64d.props +++ b/cmake/msvc/xs.wperl64d.props.in @@ -1,18 +1,22 @@ + - $(VC_ExecutablePath_x64);$(WindowsSDK_ExecutablePath);$(VS_ExecutablePath);$(MSBuild_ExecutablePath);$(FxCopDir);$(PATH);c:\wperl64d\bin\; + $(VC_ExecutablePath_x64);$(WindowsSDK_ExecutablePath);$(VS_ExecutablePath);$(MSBuild_ExecutablePath);$(FxCopDir);$(PATH);${PROPS_PERL_BIN_PATH}\; - C:\wperl64d\bin\perl.exe + ${PROPS_PERL_EXECUTABLE} slic3r.pl WindowsLocalDebugger - ..\.. + ${PROPS_CMAKE_SOURCE_DIR} diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index abd9c3617a..238a0df349 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -647,7 +647,12 @@ endif () if (MSVC) # Here we associate some additional properties with the MSVC project to enable compilation and debugging out of the box. - set_target_properties(XS PROPERTIES VS_USER_PROPS "${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl64d.props") + get_filename_component(PROPS_PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) + string(REPLACE "/" "\\" PROPS_PERL_BIN_PATH "${PROPS_PERL_BIN_PATH}") + string(REPLACE "/" "\\" PROPS_PERL_EXECUTABLE "${PERL_EXECUTABLE}") + string(REPLACE "/" "\\" PROPS_CMAKE_SOURCE_DIR "${CMAKE_SOURCE_DIR}") + configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl64d.props.in" "${CMAKE_BINARY_DIR}/xs.wperl64d.props" NEWLINE_STYLE CRLF) + set_target_properties(XS PROPERTIES VS_USER_PROPS "${CMAKE_BINARY_DIR}/xs.wperl64d.props") endif() # l10n From 9d9bcfe03f641c0d951cbf603df27282b55ca347 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 2 May 2018 13:03:04 +0200 Subject: [PATCH 121/145] MSVC: Rename props file #857 --- cmake/msvc/{xs.wperl64d.props.in => xs.wperl.props.in} | 0 xs/CMakeLists.txt | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename cmake/msvc/{xs.wperl64d.props.in => xs.wperl.props.in} (100%) diff --git a/cmake/msvc/xs.wperl64d.props.in b/cmake/msvc/xs.wperl.props.in similarity index 100% rename from cmake/msvc/xs.wperl64d.props.in rename to cmake/msvc/xs.wperl.props.in diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 238a0df349..b8d786937a 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -651,8 +651,8 @@ if (MSVC) string(REPLACE "/" "\\" PROPS_PERL_BIN_PATH "${PROPS_PERL_BIN_PATH}") string(REPLACE "/" "\\" PROPS_PERL_EXECUTABLE "${PERL_EXECUTABLE}") string(REPLACE "/" "\\" PROPS_CMAKE_SOURCE_DIR "${CMAKE_SOURCE_DIR}") - configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl64d.props.in" "${CMAKE_BINARY_DIR}/xs.wperl64d.props" NEWLINE_STYLE CRLF) - set_target_properties(XS PROPERTIES VS_USER_PROPS "${CMAKE_BINARY_DIR}/xs.wperl64d.props") + configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl.props.in" "${CMAKE_BINARY_DIR}/xs.wperl.props" NEWLINE_STYLE CRLF) + set_target_properties(XS PROPERTIES VS_USER_PROPS "${CMAKE_BINARY_DIR}/xs.wperl.props") endif() # l10n From e2e4310322badc30a7d62ce9d2744f117f5126c9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 2 May 2018 13:55:04 +0200 Subject: [PATCH 122/145] Removed unneeded scene reloads when selecting objects --- lib/Slic3r/GUI/3DScene.pm | 6 +++++- lib/Slic3r/GUI/Plater.pm | 15 ++++++++++----- lib/Slic3r/GUI/Plater/3D.pm | 23 +++++++++++++++++++---- lib/Slic3r/GUI/Plater/ObjectCutDialog.pm | 1 - lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 1 - 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 352a612643..59b5367b58 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1152,7 +1152,11 @@ sub InitGL { $self->volumes->finalize_geometry(1) if ($^O eq 'linux' && $self->UseVBOs); - $self->zoom_to_bed; + if (scalar @{$self->volumes} > 0) { + $self->zoom_to_volumes; + } else { + $self->zoom_to_bed; + } glClearColor(0, 0, 0, 1); glColor3f(1, 0, 0); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4a013bf2d9..79f251b05c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1809,8 +1809,7 @@ sub list_item_deselected { if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); $self->{canvas}->Refresh; - #FIXME VBOs are being refreshed just to change a selection color? - $self->{canvas3D}->reload_scene if $self->{canvas3D}; + $self->{canvas3D}->deselect_volumes if $self->{canvas3D}; } undef $self->{_lecursor}; } @@ -1822,8 +1821,7 @@ sub list_item_selected { my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); $self->{canvas}->Refresh; - #FIXME VBOs are being refreshed just to change a selection color? - $self->{canvas3D}->reload_scene if $self->{canvas3D}; + $self->{canvas3D}->update_volumes_selection if $self->{canvas3D}; undef $self->{_lecursor}; } @@ -2015,8 +2013,15 @@ sub selection_changed { sub select_object { my ($self, $obj_idx) = @_; + + # remove current selection + foreach my $o (0..$#{$self->{objects}}) { + $PreventListEvents = 1; + $self->{objects}->[$o]->selected(0); + $self->{list}->Select($o, 0); + $PreventListEvents = 0; + } - $_->selected(0) for @{ $self->{objects} }; if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); # We use this flag to avoid circular event handling diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index dce60e2c44..ced6fb82ea 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -31,7 +31,9 @@ sub new { $self->{on_select_object} = sub {}; $self->{on_instances_moved} = sub {}; $self->{on_wipe_tower_moved} = sub {}; - + + $self->{objects_volumes_idxs} = (); + $self->on_select(sub { my ($volume_idx) = @_; $self->{on_select_object}->(($volume_idx == -1) ? undef : $self->volumes->[$volume_idx]->object_idx) @@ -181,6 +183,17 @@ sub set_on_enable_action_buttons { $self->on_enable_action_buttons($cb); } +sub update_volumes_selection { + my ($self) = @_; + + foreach my $obj_idx (0..$#{$self->{model}->objects}) { + if ($self->{objects}[$obj_idx]->selected) { + my @volume_idxs = @{$self->{objects_volumes_idxs}[$obj_idx]}; + $self->select_volume($_) for @volume_idxs; + } + } +} + sub reload_scene { my ($self, $force) = @_; @@ -194,12 +207,14 @@ sub reload_scene { $self->{reload_delayed} = 0; + $self->{objects_volumes_idxs} = (); foreach my $obj_idx (0..$#{$self->{model}->objects}) { my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx); - if ($self->{objects}[$obj_idx]->selected) { - $self->select_volume($_) for @volume_idxs; - } + push(@{$self->{objects_volumes_idxs}}, \@volume_idxs); } + + $self->update_volumes_selection; + if (defined $self->{config}->nozzle_diameter) { # Should the wipe tower be visualized? my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 0cc8b2decc..4d55e313a6 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -116,7 +116,6 @@ sub new { $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->SetMinSize($canvas->GetSize); - $canvas->zoom_to_volumes; } $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index f7e38ed873..28e3bf92b5 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -163,7 +163,6 @@ sub new { $canvas->set_auto_bed_shape; $canvas->SetSize([500,700]); $canvas->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config}); - $canvas->zoom_to_volumes; } $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); From cb486522a80737e63175e79e841ad31380aa88c3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 2 May 2018 14:55:17 +0200 Subject: [PATCH 123/145] Fixed crash when pressing 'A' with empty print bed --- xs/src/libslic3r/Model.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index aaea863e83..a43bf02213 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -269,6 +269,10 @@ TriangleMesh Model::mesh() const static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) { + if (sizes.empty()) + // return if the list is empty or the following call to BoundingBoxf constructor will lead to a crash + return true; + // we supply unscaled data to arrange() bool result = Slic3r::Geometry::arrange( sizes.size(), // number of parts From 16d5faac2000fc6f210e61e18d225ecf4db84c83 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 2 May 2018 13:20:36 +0200 Subject: [PATCH 124/145] Preparations to the localization update & new Slic3rPE.pot --- lib/Slic3r/GUI/Plater.pm | 4 +- resources/localization/Slic3rPE.pot | 2715 +++++++++++++------- resources/localization/list.txt | 14 +- xs/src/slic3r/GUI/AboutDialog.cpp | 2 +- xs/src/slic3r/GUI/BonjourDialog.cpp | 2 +- xs/src/slic3r/GUI/ButtonsDescription.cpp | 2 +- xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp | 33 +- xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp | 2 +- xs/src/slic3r/GUI/GUI.cpp | 17 +- xs/src/slic3r/GUI/Preferences.cpp | 6 + xs/src/slic3r/GUI/Preferences.hpp | 3 +- xs/src/slic3r/GUI/Preset.cpp | 17 +- xs/src/slic3r/GUI/PresetBundle.cpp | 9 +- xs/src/slic3r/GUI/RammingChart.cpp | 6 +- xs/src/slic3r/GUI/Tab.cpp | 5 +- xs/src/slic3r/GUI/WipeTowerDialog.cpp | 12 +- 16 files changed, 1864 insertions(+), 985 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4a013bf2d9..ac1a1f0be2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -549,8 +549,8 @@ sub _on_select_preset { return if ($selected_item == $self->{"selected_item_$group"}); my $selected_string = $choice->GetString($selected_item); - if ($selected_string eq "------- System presets -------" || - $selected_string eq "------- User presets -------"){ + if ($selected_string eq ("------- ".L("System presets")." -------") || + $selected_string eq ("------- ".L("User presets")." -------") ){ $choice->SetSelection($self->{"selected_item_$group"}); return; } diff --git a/resources/localization/Slic3rPE.pot b/resources/localization/Slic3rPE.pot index 0282e7aae7..e4b9e25671 100644 --- a/resources/localization/Slic3rPE.pot +++ b/resources/localization/Slic3rPE.pot @@ -3,20 +3,26 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-03-14 15:32+0100\n" +"POT-Creation-Date: 2018-05-02 15:27+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" -"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: xs/src/slic3r/GUI/AboutDialog.cpp:32 +msgid "About Slic3r" +msgstr "" + +#: xs/src/slic3r/GUI/AboutDialog.cpp:60 +msgid "Version" +msgstr "" + #: xs/src/slic3r/GUI/BedShapeDialog.cpp:39 msgid "Shape" msgstr "" @@ -25,8 +31,8 @@ msgstr "" msgid "Rectangular" msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:50 xs/src/slic3r/GUI/Tab.cpp:1270 -#: lib/Slic3r/GUI/Plater.pm:411 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:50 xs/src/slic3r/GUI/Tab.cpp:1738 +#: lib/Slic3r/GUI/Plater.pm:421 msgid "Size" msgstr "" @@ -48,25 +54,34 @@ msgstr "" msgid "Circular" msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:65 xs/src/libslic3r/PrintConfig.cpp:129 -#: xs/src/libslic3r/PrintConfig.cpp:200 xs/src/libslic3r/PrintConfig.cpp:211 -#: xs/src/libslic3r/PrintConfig.cpp:325 xs/src/libslic3r/PrintConfig.cpp:336 -#: xs/src/libslic3r/PrintConfig.cpp:355 xs/src/libslic3r/PrintConfig.cpp:434 -#: xs/src/libslic3r/PrintConfig.cpp:781 xs/src/libslic3r/PrintConfig.cpp:801 -#: xs/src/libslic3r/PrintConfig.cpp:860 xs/src/libslic3r/PrintConfig.cpp:878 -#: xs/src/libslic3r/PrintConfig.cpp:896 xs/src/libslic3r/PrintConfig.cpp:1051 -#: xs/src/libslic3r/PrintConfig.cpp:1059 xs/src/libslic3r/PrintConfig.cpp:1101 -#: xs/src/libslic3r/PrintConfig.cpp:1110 xs/src/libslic3r/PrintConfig.cpp:1120 -#: xs/src/libslic3r/PrintConfig.cpp:1128 xs/src/libslic3r/PrintConfig.cpp:1136 -#: xs/src/libslic3r/PrintConfig.cpp:1222 xs/src/libslic3r/PrintConfig.cpp:1428 -#: xs/src/libslic3r/PrintConfig.cpp:1498 xs/src/libslic3r/PrintConfig.cpp:1534 -#: xs/src/libslic3r/PrintConfig.cpp:1711 xs/src/libslic3r/PrintConfig.cpp:1718 -#: xs/src/libslic3r/PrintConfig.cpp:1725 xs/src/libslic3r/PrintConfig.cpp:1734 -#: xs/src/libslic3r/PrintConfig.cpp:1744 xs/src/libslic3r/PrintConfig.cpp:1754 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:65 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:85 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:432 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:446 +#: xs/src/slic3r/GUI/RammingChart.cpp:86 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:84 +#: xs/src/libslic3r/PrintConfig.cpp:129 xs/src/libslic3r/PrintConfig.cpp:172 +#: xs/src/libslic3r/PrintConfig.cpp:180 xs/src/libslic3r/PrintConfig.cpp:228 +#: xs/src/libslic3r/PrintConfig.cpp:239 xs/src/libslic3r/PrintConfig.cpp:353 +#: xs/src/libslic3r/PrintConfig.cpp:364 xs/src/libslic3r/PrintConfig.cpp:383 +#: xs/src/libslic3r/PrintConfig.cpp:496 xs/src/libslic3r/PrintConfig.cpp:850 +#: xs/src/libslic3r/PrintConfig.cpp:870 xs/src/libslic3r/PrintConfig.cpp:878 +#: xs/src/libslic3r/PrintConfig.cpp:936 xs/src/libslic3r/PrintConfig.cpp:954 +#: xs/src/libslic3r/PrintConfig.cpp:972 xs/src/libslic3r/PrintConfig.cpp:1034 +#: xs/src/libslic3r/PrintConfig.cpp:1151 xs/src/libslic3r/PrintConfig.cpp:1159 +#: xs/src/libslic3r/PrintConfig.cpp:1201 xs/src/libslic3r/PrintConfig.cpp:1210 +#: xs/src/libslic3r/PrintConfig.cpp:1220 xs/src/libslic3r/PrintConfig.cpp:1228 +#: xs/src/libslic3r/PrintConfig.cpp:1236 xs/src/libslic3r/PrintConfig.cpp:1322 +#: xs/src/libslic3r/PrintConfig.cpp:1528 xs/src/libslic3r/PrintConfig.cpp:1598 +#: xs/src/libslic3r/PrintConfig.cpp:1632 xs/src/libslic3r/PrintConfig.cpp:1828 +#: xs/src/libslic3r/PrintConfig.cpp:1835 xs/src/libslic3r/PrintConfig.cpp:1842 +#: xs/src/libslic3r/PrintConfig.cpp:1856 xs/src/libslic3r/PrintConfig.cpp:1866 +#: xs/src/libslic3r/PrintConfig.cpp:1876 msgid "mm" msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:66 xs/src/libslic3r/PrintConfig.cpp:431 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:66 +#: xs/src/libslic3r/PrintConfig.cpp:493 msgid "Diameter" msgstr "" @@ -77,8 +92,8 @@ msgid "" msgstr "" #: xs/src/slic3r/GUI/BedShapeDialog.cpp:71 -#: xs/src/libslic3r/GCode/PreviewData.cpp:150 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:92 +#: xs/src/libslic3r/GCode/PreviewData.cpp:170 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:95 msgid "Custom" msgstr "" @@ -90,300 +105,730 @@ msgstr "" msgid "Settings" msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:298 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:299 msgid "Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:315 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:316 msgid "Error! " msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:324 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:325 msgid "The selected file contains no geometry." msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:328 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:329 msgid "" "The selected file contains several disjoint areas. This is not supported." msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.hpp:42 +#: xs/src/slic3r/GUI/BedShapeDialog.hpp:44 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:395 msgid "Bed Shape" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:234 +#: xs/src/slic3r/GUI/BonjourDialog.cpp:53 +msgid "Network lookup" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:66 +msgid "Address" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:67 +msgid "Hostname" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:68 +msgid "Service name" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:69 +msgid "OctoPrint version" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:187 +msgid "Searching for devices" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:194 +msgid "Finished" +msgstr "" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:13 +msgid "Buttons And Text Colors Description" +msgstr "" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:38 +msgid "Value is the same as the system value" +msgstr "" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:55 +msgid "" +"Value was changed and is not equal to the system value or the last saved " +"preset" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:15 +msgid "Upgrade" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:17 +msgid "Downgrade" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:19 +msgid "Before roll back" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:21 +msgid "User" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:24 +msgid "Unknown" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:36 +msgid "Active: " +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:42 +msgid "slic3r version" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:43 +msgid "print" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:44 +msgid "filaments" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:45 +msgid "printer" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 xs/src/slic3r/GUI/Tab.cpp:755 +msgid "vendor" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 +msgid "version" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:50 +msgid "min slic3r version" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:52 +msgid "max slic3r version" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:55 +msgid "model" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:55 +msgid "variants" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:67 +msgid "Incompatible with this Slic3r" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:70 +msgid "Activate" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 xs/src/slic3r/GUI/GUI.cpp:402 +msgid "Configuration Snapshots" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:85 +msgid "nozzle" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:103 +msgid "Select all" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:104 +msgid "Select none" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:205 +#, possible-c-format +msgid "Welcome to the Slic3r %s" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:205 +msgid "Welcome" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:211 xs/src/slic3r/GUI/GUI.cpp:399 +#, possible-c-format +msgid "Run %s" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:213 +#, possible-c-format +msgid "" +"Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial " +"configuration; just a few settings and you will be ready to print." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:217 +msgid "" +"Remove user profiles - install from scratch (a snapshot will be taken " +"beforehand)" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:238 +msgid "Other vendors" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:240 +msgid "Custom setup" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:264 +msgid "Automatic updates" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:264 +msgid "Updates" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:272 xs/src/slic3r/GUI/Preferences.cpp:59 +msgid "Check for application updates" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:275 xs/src/slic3r/GUI/Preferences.cpp:61 +msgid "" +"If enabled, Slic3r checks for new versions of Slic3r PE 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." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:279 xs/src/slic3r/GUI/Preferences.cpp:67 +msgid "Update built-in Presets automatically" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:282 xs/src/slic3r/GUI/Preferences.cpp:69 +msgid "" +"If enabled, Slic3r 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." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:283 +msgid "" +"Updates are never applied without user's consent and never overwrite user's " +"customized settings." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:288 +msgid "" +"Additionally a backup snapshot of the whole configuration is created before " +"an update is applied." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:295 +msgid "Other Vendors" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:297 +msgid "Pick another vendor supported by Slic3r PE:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:356 +msgid "Firmware Type" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:356 xs/src/slic3r/GUI/Tab.cpp:1625 +msgid "Firmware" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:360 +msgid "Choose the type of firmware used by your printer." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:395 +msgid "Bed Shape and Size" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:398 +msgid "Set the shape of your printer's bed." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:412 +msgid "Filament and Nozzle Diameters" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:412 +msgid "Print Diameters" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:428 +msgid "Enter the diameter of your printer's hot end nozzle." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:431 +msgid "Nozzle Diameter:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:441 +msgid "Enter the diameter of your filament." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:442 +msgid "" +"Good precision is required, so use a caliper and do multiple measurements " +"along the filament, then compute the average." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:445 +msgid "Filament Diameter:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:463 +msgid "Extruder and Bed Temperatures" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:463 +msgid "Temperatures" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:479 +msgid "Enter the temperature needed for extruding your filament." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:480 +msgid "A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:483 +msgid "Extrusion Temperature:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:484 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:498 +msgid "°C" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:493 +msgid "" +"Enter the bed temperature needed for getting your filament to stick to your " +"heated bed." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:494 +msgid "" +"A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have " +"no heated bed." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:497 +msgid "Bed Temperature:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:791 +msgid "&Finish" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:844 +msgid "Configuration Wizard" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:846 +msgid "Configuration Assistant" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:285 msgid "Array of language names and identifiers should have the same size." msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:245 +#: xs/src/slic3r/GUI/GUI.cpp:296 msgid "Select the language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:245 +#: xs/src/slic3r/GUI/GUI.cpp:296 msgid "Language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:312 xs/src/libslic3r/PrintConfig.cpp:170 +#: xs/src/slic3r/GUI/GUI.cpp:363 xs/src/libslic3r/PrintConfig.cpp:186 msgid "Default" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:337 +#: xs/src/slic3r/GUI/GUI.cpp:402 +msgid "Inspect / activate configuration snapshots" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:403 +msgid "Take Configuration Snapshot" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:403 +msgid "Capture a configuration snapshot" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:404 +msgid "Check for updates" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:404 +msgid "Check for configuration updates" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:406 xs/src/slic3r/GUI/Preferences.cpp:9 +msgid "Preferences" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:406 +msgid "Application preferences" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:408 msgid "Change Application Language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:344 +#: xs/src/slic3r/GUI/GUI.cpp:417 +msgid "Taking configuration snapshot" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:417 +msgid "Snapshot name" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:456 msgid "Application will be restarted" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:344 +#: xs/src/slic3r/GUI/GUI.cpp:456 msgid "Attention!" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:351 -msgid "&Localization" +#: xs/src/slic3r/GUI/GUI.cpp:466 +msgid "&Configuration" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:494 lib/Slic3r/GUI/MainFrame.pm:465 -#: lib/Slic3r/GUI/Plater.pm:1304 -msgid "Error" +#: xs/src/slic3r/GUI/GUI.cpp:485 +msgid "You have unsaved changes " msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:499 +#: xs/src/slic3r/GUI/GUI.cpp:485 +msgid ". Discard changes and continue anyway?" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:486 +msgid "Unsaved Presets" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:683 msgid "Notice" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:504 -msgid "GLUquadricObjPtr | Attempt to free unreferenced scalar" +#: xs/src/slic3r/GUI/GUI.cpp:688 +msgid "Attempt to free unreferenced scalar" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:506 +#: xs/src/slic3r/GUI/GUI.cpp:690 xs/src/slic3r/GUI/WipeTowerDialog.cpp:44 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:326 msgid "Warning" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:655 +#: xs/src/slic3r/GUI/GUI.cpp:879 msgid "Support" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:658 +#: xs/src/slic3r/GUI/GUI.cpp:882 msgid "Select what kind of support do you need" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:659 xs/src/libslic3r/GCode/PreviewData.cpp:137 +#: xs/src/slic3r/GUI/GUI.cpp:883 xs/src/libslic3r/GCode/PreviewData.cpp:157 msgid "None" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:660 xs/src/libslic3r/PrintConfig.cpp:1415 +#: xs/src/slic3r/GUI/GUI.cpp:884 xs/src/libslic3r/PrintConfig.cpp:1515 msgid "Support on build plate only" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:661 +#: xs/src/slic3r/GUI/GUI.cpp:885 msgid "Everywhere" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:673 xs/src/slic3r/GUI/Tab.cpp:441 +#: xs/src/slic3r/GUI/GUI.cpp:897 xs/src/slic3r/GUI/Tab.cpp:869 msgid "Brim" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:675 +#: xs/src/slic3r/GUI/GUI.cpp:899 msgid "" "This flag enables the brim that will be printed around each object on the " "first layer." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:57 +#: xs/src/slic3r/GUI/GUI.cpp:908 +msgid "Purging volumes" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:950 +msgid "Export print config" +msgstr "" + +#: xs/src/slic3r/GUI/MsgDialog.cpp:64 +msgid "Slic3r error" +msgstr "" + +#: xs/src/slic3r/GUI/MsgDialog.cpp:64 +msgid "Slic3r has encountered an error" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:100 msgid "Save current " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:58 +#: xs/src/slic3r/GUI/Tab.cpp:101 msgid "Delete this preset" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:383 +#: xs/src/slic3r/GUI/Tab.cpp:113 +msgid "" +"Hover the cursor over buttons to find more information \n" +"or click this button." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:741 +msgid "It's a default preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:742 +msgid "It's a system preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:743 +msgid "Current preset is inherited from " +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:748 +msgid "It can't be deleted or modified. " +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:749 +msgid "" +"Any modifications should be saved as a new preset inherited from this one. " +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:750 +msgid "To do that please specify a new name for the preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:754 +msgid "Additional information:" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:762 +msgid "printer model" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:764 +msgid "default print profile" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:767 +msgid "default filament profile" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:811 msgid "Layers and perimeters" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:384 xs/src/libslic3r/PrintConfig.cpp:777 +#: xs/src/slic3r/GUI/Tab.cpp:812 xs/src/libslic3r/PrintConfig.cpp:846 msgid "Layer height" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:388 +#: xs/src/slic3r/GUI/Tab.cpp:816 msgid "Vertical shells" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:399 +#: xs/src/slic3r/GUI/Tab.cpp:827 msgid "Horizontal shells" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:400 xs/src/libslic3r/PrintConfig.cpp:1321 +#: xs/src/slic3r/GUI/Tab.cpp:828 xs/src/libslic3r/PrintConfig.cpp:1421 msgid "Solid layers" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:405 +#: xs/src/slic3r/GUI/Tab.cpp:833 msgid "Quality (slower slicing)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:412 xs/src/slic3r/GUI/Tab.cpp:426 -#: xs/src/slic3r/GUI/Tab.cpp:519 xs/src/slic3r/GUI/Tab.cpp:522 -#: xs/src/slic3r/GUI/Tab.cpp:905 xs/src/slic3r/GUI/Tab.cpp:1191 -#: xs/src/libslic3r/PrintConfig.cpp:107 xs/src/libslic3r/PrintConfig.cpp:208 -#: xs/src/libslic3r/PrintConfig.cpp:736 xs/src/libslic3r/PrintConfig.cpp:1740 +#: xs/src/slic3r/GUI/Tab.cpp:840 xs/src/slic3r/GUI/Tab.cpp:854 +#: xs/src/slic3r/GUI/Tab.cpp:948 xs/src/slic3r/GUI/Tab.cpp:951 +#: xs/src/slic3r/GUI/Tab.cpp:1300 xs/src/slic3r/GUI/Tab.cpp:1628 +#: xs/src/libslic3r/PrintConfig.cpp:107 xs/src/libslic3r/PrintConfig.cpp:236 +#: xs/src/libslic3r/PrintConfig.cpp:798 xs/src/libslic3r/PrintConfig.cpp:1862 msgid "Advanced" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:416 xs/src/slic3r/GUI/Tab.cpp:417 -#: xs/src/slic3r/GUI/Tab.cpp:735 xs/src/libslic3r/PrintConfig.cpp:87 -#: xs/src/libslic3r/PrintConfig.cpp:247 xs/src/libslic3r/PrintConfig.cpp:488 -#: xs/src/libslic3r/PrintConfig.cpp:502 xs/src/libslic3r/PrintConfig.cpp:540 -#: xs/src/libslic3r/PrintConfig.cpp:681 xs/src/libslic3r/PrintConfig.cpp:691 -#: xs/src/libslic3r/PrintConfig.cpp:709 xs/src/libslic3r/PrintConfig.cpp:727 -#: xs/src/libslic3r/PrintConfig.cpp:746 xs/src/libslic3r/PrintConfig.cpp:1270 -#: xs/src/libslic3r/PrintConfig.cpp:1287 +#: xs/src/slic3r/GUI/Tab.cpp:844 xs/src/slic3r/GUI/Tab.cpp:845 +#: xs/src/slic3r/GUI/Tab.cpp:1152 xs/src/libslic3r/PrintConfig.cpp:87 +#: xs/src/libslic3r/PrintConfig.cpp:275 xs/src/libslic3r/PrintConfig.cpp:550 +#: xs/src/libslic3r/PrintConfig.cpp:564 xs/src/libslic3r/PrintConfig.cpp:602 +#: xs/src/libslic3r/PrintConfig.cpp:743 xs/src/libslic3r/PrintConfig.cpp:753 +#: xs/src/libslic3r/PrintConfig.cpp:771 xs/src/libslic3r/PrintConfig.cpp:789 +#: xs/src/libslic3r/PrintConfig.cpp:808 xs/src/libslic3r/PrintConfig.cpp:1370 +#: xs/src/libslic3r/PrintConfig.cpp:1387 msgid "Infill" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:422 +#: xs/src/slic3r/GUI/Tab.cpp:850 msgid "Reducing printing time" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:434 +#: xs/src/slic3r/GUI/Tab.cpp:862 msgid "Skirt and brim" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:435 xs/src/libslic3r/GCode/PreviewData.cpp:146 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:88 +#: xs/src/slic3r/GUI/Tab.cpp:863 xs/src/libslic3r/GCode/PreviewData.cpp:166 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:91 msgid "Skirt" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:444 xs/src/slic3r/GUI/Tab.cpp:445 -#: xs/src/libslic3r/PrintConfig.cpp:191 xs/src/libslic3r/PrintConfig.cpp:1037 -#: xs/src/libslic3r/PrintConfig.cpp:1387 xs/src/libslic3r/PrintConfig.cpp:1394 -#: xs/src/libslic3r/PrintConfig.cpp:1406 xs/src/libslic3r/PrintConfig.cpp:1416 -#: xs/src/libslic3r/PrintConfig.cpp:1424 xs/src/libslic3r/PrintConfig.cpp:1439 -#: xs/src/libslic3r/PrintConfig.cpp:1460 xs/src/libslic3r/PrintConfig.cpp:1471 -#: xs/src/libslic3r/PrintConfig.cpp:1487 xs/src/libslic3r/PrintConfig.cpp:1496 -#: xs/src/libslic3r/PrintConfig.cpp:1505 xs/src/libslic3r/PrintConfig.cpp:1516 -#: xs/src/libslic3r/PrintConfig.cpp:1532 xs/src/libslic3r/PrintConfig.cpp:1540 -#: xs/src/libslic3r/PrintConfig.cpp:1541 xs/src/libslic3r/PrintConfig.cpp:1550 -#: xs/src/libslic3r/PrintConfig.cpp:1558 xs/src/libslic3r/PrintConfig.cpp:1572 -#: xs/src/libslic3r/GCode/PreviewData.cpp:147 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:89 +#: xs/src/slic3r/GUI/Tab.cpp:872 xs/src/slic3r/GUI/Tab.cpp:873 +#: xs/src/libslic3r/PrintConfig.cpp:219 xs/src/libslic3r/PrintConfig.cpp:1137 +#: xs/src/libslic3r/PrintConfig.cpp:1487 xs/src/libslic3r/PrintConfig.cpp:1494 +#: xs/src/libslic3r/PrintConfig.cpp:1506 xs/src/libslic3r/PrintConfig.cpp:1516 +#: xs/src/libslic3r/PrintConfig.cpp:1524 xs/src/libslic3r/PrintConfig.cpp:1539 +#: xs/src/libslic3r/PrintConfig.cpp:1560 xs/src/libslic3r/PrintConfig.cpp:1571 +#: xs/src/libslic3r/PrintConfig.cpp:1587 xs/src/libslic3r/PrintConfig.cpp:1596 +#: xs/src/libslic3r/PrintConfig.cpp:1605 xs/src/libslic3r/PrintConfig.cpp:1616 +#: xs/src/libslic3r/PrintConfig.cpp:1630 xs/src/libslic3r/PrintConfig.cpp:1638 +#: xs/src/libslic3r/PrintConfig.cpp:1639 xs/src/libslic3r/PrintConfig.cpp:1648 +#: xs/src/libslic3r/PrintConfig.cpp:1656 xs/src/libslic3r/PrintConfig.cpp:1670 +#: xs/src/libslic3r/GCode/PreviewData.cpp:167 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:92 msgid "Support material" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:450 +#: xs/src/slic3r/GUI/Tab.cpp:878 msgid "Raft" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:454 +#: xs/src/slic3r/GUI/Tab.cpp:882 msgid "Options for support material and raft" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:468 xs/src/libslic3r/PrintConfig.cpp:118 -#: xs/src/libslic3r/PrintConfig.cpp:278 xs/src/libslic3r/PrintConfig.cpp:635 -#: xs/src/libslic3r/PrintConfig.cpp:747 xs/src/libslic3r/PrintConfig.cpp:986 -#: xs/src/libslic3r/PrintConfig.cpp:1208 xs/src/libslic3r/PrintConfig.cpp:1258 -#: xs/src/libslic3r/PrintConfig.cpp:1309 xs/src/libslic3r/PrintConfig.cpp:1632 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:71 +#: xs/src/slic3r/GUI/Tab.cpp:896 xs/src/libslic3r/PrintConfig.cpp:118 +#: xs/src/libslic3r/PrintConfig.cpp:306 xs/src/libslic3r/PrintConfig.cpp:697 +#: xs/src/libslic3r/PrintConfig.cpp:809 xs/src/libslic3r/PrintConfig.cpp:1071 +#: xs/src/libslic3r/PrintConfig.cpp:1308 xs/src/libslic3r/PrintConfig.cpp:1358 +#: xs/src/libslic3r/PrintConfig.cpp:1409 xs/src/libslic3r/PrintConfig.cpp:1730 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:70 msgid "Speed" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:469 +#: xs/src/slic3r/GUI/Tab.cpp:897 msgid "Speed for print moves" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:481 +#: xs/src/slic3r/GUI/Tab.cpp:909 msgid "Speed for non-print moves" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:484 +#: xs/src/slic3r/GUI/Tab.cpp:912 msgid "Modifiers" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:487 +#: xs/src/slic3r/GUI/Tab.cpp:915 msgid "Acceleration control (advanced)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:494 +#: xs/src/slic3r/GUI/Tab.cpp:922 msgid "Autospeed (advanced)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:500 +#: xs/src/slic3r/GUI/Tab.cpp:928 msgid "Multiple Extruders" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:501 xs/src/slic3r/GUI/Tab.cpp:1040 -#: xs/src/libslic3r/PrintConfig.cpp:308 xs/src/libslic3r/PrintConfig.cpp:702 -#: xs/src/libslic3r/PrintConfig.cpp:965 xs/src/libslic3r/PrintConfig.cpp:1279 -#: xs/src/libslic3r/PrintConfig.cpp:1452 xs/src/libslic3r/PrintConfig.cpp:1478 +#: xs/src/slic3r/GUI/Tab.cpp:929 xs/src/slic3r/GUI/Tab.cpp:1470 +#: xs/src/libslic3r/PrintConfig.cpp:336 xs/src/libslic3r/PrintConfig.cpp:764 +#: xs/src/libslic3r/PrintConfig.cpp:1050 xs/src/libslic3r/PrintConfig.cpp:1379 +#: xs/src/libslic3r/PrintConfig.cpp:1552 xs/src/libslic3r/PrintConfig.cpp:1578 msgid "Extruders" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:508 +#: xs/src/slic3r/GUI/Tab.cpp:936 msgid "Ooze prevention" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:512 xs/src/libslic3r/GCode/PreviewData.cpp:149 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:91 +#: xs/src/slic3r/GUI/Tab.cpp:940 xs/src/libslic3r/GCode/PreviewData.cpp:169 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:94 msgid "Wipe tower" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:523 +#: xs/src/slic3r/GUI/Tab.cpp:952 msgid "Extrusion width" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:533 +#: xs/src/slic3r/GUI/Tab.cpp:962 msgid "Overlap" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:536 +#: xs/src/slic3r/GUI/Tab.cpp:965 msgid "Flow" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:539 +#: xs/src/slic3r/GUI/Tab.cpp:968 msgid "Other" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:546 +#: xs/src/slic3r/GUI/Tab.cpp:975 msgid "Output options" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:547 +#: xs/src/slic3r/GUI/Tab.cpp:976 msgid "Sequential printing" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:549 +#: xs/src/slic3r/GUI/Tab.cpp:978 msgid "Extruder clearance (mm)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:558 +#: xs/src/slic3r/GUI/Tab.cpp:987 msgid "Output file" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:564 xs/src/libslic3r/PrintConfig.cpp:1008 +#: xs/src/slic3r/GUI/Tab.cpp:993 xs/src/libslic3r/PrintConfig.cpp:1093 msgid "Post-processing scripts" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:570 xs/src/slic3r/GUI/Tab.cpp:571 -#: xs/src/slic3r/GUI/Tab.cpp:933 xs/src/slic3r/GUI/Tab.cpp:934 -#: xs/src/slic3r/GUI/Tab.cpp:1234 xs/src/slic3r/GUI/Tab.cpp:1235 +#: xs/src/slic3r/GUI/Tab.cpp:999 xs/src/slic3r/GUI/Tab.cpp:1000 +#: xs/src/slic3r/GUI/Tab.cpp:1349 xs/src/slic3r/GUI/Tab.cpp:1350 +#: xs/src/slic3r/GUI/Tab.cpp:1671 xs/src/slic3r/GUI/Tab.cpp:1672 msgid "Notes" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:577 xs/src/slic3r/GUI/Tab.cpp:941 +#: xs/src/slic3r/GUI/Tab.cpp:1006 xs/src/slic3r/GUI/Tab.cpp:1357 +#: xs/src/slic3r/GUI/Tab.cpp:1678 msgid "Dependencies" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:578 xs/src/slic3r/GUI/Tab.cpp:942 +#: xs/src/slic3r/GUI/Tab.cpp:1007 xs/src/slic3r/GUI/Tab.cpp:1358 +#: xs/src/slic3r/GUI/Tab.cpp:1679 msgid "Profile dependencies" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:579 xs/src/slic3r/GUI/Tab.cpp:943 -#: xs/src/slic3r/GUI/Tab.cpp:1746 xs/src/libslic3r/PrintConfig.cpp:143 +#: xs/src/slic3r/GUI/Tab.cpp:1008 xs/src/slic3r/GUI/Tab.cpp:1359 +#: xs/src/slic3r/GUI/Tab.cpp:2241 xs/src/libslic3r/PrintConfig.cpp:143 msgid "Compatible printers" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:603 -#, possible-c-format +#: xs/src/slic3r/GUI/Tab.cpp:1041 +#, no-c-format msgid "" "The Spiral Vase mode requires:\n" "- one perimeter\n" @@ -395,25 +840,11 @@ msgid "" "Shall I adjust those settings in order to enable Spiral Vase?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:610 +#: xs/src/slic3r/GUI/Tab.cpp:1048 msgid "Spiral Vase" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:630 -msgid "" -"The Wipe Tower currently supports only:\n" -"- first layer height 0.2mm\n" -"- layer height from 0.15mm to 0.35mm\n" -"\n" -"Shall I adjust those settings in order to enable the Wipe Tower?" -msgstr "" - -#: xs/src/slic3r/GUI/Tab.cpp:634 xs/src/slic3r/GUI/Tab.cpp:656 -#: xs/src/slic3r/GUI/Tab.cpp:673 -msgid "Wipe Tower" -msgstr "" - -#: xs/src/slic3r/GUI/Tab.cpp:652 +#: xs/src/slic3r/GUI/Tab.cpp:1069 msgid "" "The Wipe Tower currently supports the non-soluble supports only\n" "if they are printed with the current extruder without triggering a tool " @@ -424,7 +855,11 @@ msgid "" "Shall I adjust those settings in order to enable the Wipe Tower?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:670 +#: xs/src/slic3r/GUI/Tab.cpp:1073 xs/src/slic3r/GUI/Tab.cpp:1090 +msgid "Wipe Tower" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1087 msgid "" "For the Wipe Tower to work with the soluble supports, the support layers\n" "need to be synchronized with the object layers.\n" @@ -432,7 +867,7 @@ msgid "" "Shall I synchronize support layers in order to enable the Wipe Tower?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:688 +#: xs/src/slic3r/GUI/Tab.cpp:1105 msgid "" "Supports work better, if the following feature is enabled:\n" "- Detect bridging perimeters\n" @@ -440,355 +875,479 @@ msgid "" "Shall I adjust those settings for supports?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:691 +#: xs/src/slic3r/GUI/Tab.cpp:1108 msgid "Support Generator" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:733 +#: xs/src/slic3r/GUI/Tab.cpp:1150 msgid "The " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:733 -#, possible-c-format +#: xs/src/slic3r/GUI/Tab.cpp:1150 +#, no-c-format msgid "" " infill pattern is not supposed to work at 100% density.\n" "\n" "Shall I switch to rectilinear fill pattern?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:860 xs/src/slic3r/GUI/Tab.cpp:861 -#: lib/Slic3r/GUI/Plater.pm:368 +#: xs/src/slic3r/GUI/Tab.cpp:1255 xs/src/slic3r/GUI/Tab.cpp:1256 +#: lib/Slic3r/GUI/Plater.pm:378 msgid "Filament" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:868 +#: xs/src/slic3r/GUI/Tab.cpp:1263 msgid "Temperature " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:869 xs/src/slic3r/GUI/Tab.cpp:1313 -#: xs/src/libslic3r/PrintConfig.cpp:307 +#: xs/src/slic3r/GUI/Tab.cpp:1264 xs/src/libslic3r/PrintConfig.cpp:335 msgid "Extruder" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:874 +#: xs/src/slic3r/GUI/Tab.cpp:1269 msgid "Bed" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:879 +#: xs/src/slic3r/GUI/Tab.cpp:1274 msgid "Cooling" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:880 xs/src/libslic3r/PrintConfig.cpp:929 -#: xs/src/libslic3r/PrintConfig.cpp:1702 +#: xs/src/slic3r/GUI/Tab.cpp:1275 xs/src/libslic3r/PrintConfig.cpp:1005 +#: xs/src/libslic3r/PrintConfig.cpp:1800 msgid "Enable" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:891 +#: xs/src/slic3r/GUI/Tab.cpp:1286 msgid "Fan settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:892 +#: xs/src/slic3r/GUI/Tab.cpp:1287 msgid "Fan speed" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:900 +#: xs/src/slic3r/GUI/Tab.cpp:1295 msgid "Cooling thresholds" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:906 +#: xs/src/slic3r/GUI/Tab.cpp:1301 msgid "Filament properties" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:910 +#: xs/src/slic3r/GUI/Tab.cpp:1305 msgid "Print speed override" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:920 xs/src/slic3r/GUI/Tab.cpp:1197 +#: xs/src/slic3r/GUI/Tab.cpp:1315 +msgid "Toolchange behaviour" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1319 +msgid "Ramming" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1321 +msgid "Ramming settings" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1336 xs/src/slic3r/GUI/Tab.cpp:1634 msgid "Custom G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:921 xs/src/slic3r/GUI/Tab.cpp:1198 -#: xs/src/libslic3r/PrintConfig.cpp:1349 xs/src/libslic3r/PrintConfig.cpp:1364 +#: xs/src/slic3r/GUI/Tab.cpp:1337 xs/src/slic3r/GUI/Tab.cpp:1635 +#: xs/src/libslic3r/PrintConfig.cpp:1449 xs/src/libslic3r/PrintConfig.cpp:1464 msgid "Start G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:927 xs/src/slic3r/GUI/Tab.cpp:1204 -#: xs/src/libslic3r/PrintConfig.cpp:217 xs/src/libslic3r/PrintConfig.cpp:227 +#: xs/src/slic3r/GUI/Tab.cpp:1343 xs/src/slic3r/GUI/Tab.cpp:1641 +#: xs/src/libslic3r/PrintConfig.cpp:245 xs/src/libslic3r/PrintConfig.cpp:255 msgid "End G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1011 xs/src/slic3r/GUI/Preferences.cpp:11 +#: xs/src/slic3r/GUI/Tab.cpp:1438 xs/src/slic3r/GUI/Preferences.cpp:17 msgid "General" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1012 +#: xs/src/slic3r/GUI/Tab.cpp:1439 msgid "Size and coordinates" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1014 xs/src/libslic3r/PrintConfig.cpp:34 +#: xs/src/slic3r/GUI/Tab.cpp:1441 xs/src/libslic3r/PrintConfig.cpp:34 msgid "Bed shape" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1016 xs/src/slic3r/GUI/Tab.cpp:1715 +#: xs/src/slic3r/GUI/Tab.cpp:1443 xs/src/slic3r/GUI/Tab.cpp:2209 msgid " Set " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1036 +#: xs/src/slic3r/GUI/Tab.cpp:1466 msgid "Capabilities" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1041 +#: xs/src/slic3r/GUI/Tab.cpp:1471 msgid "Number of extruders of the printer." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1063 +#: xs/src/slic3r/GUI/Tab.cpp:1496 msgid "USB/Serial connection" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1064 xs/src/libslic3r/PrintConfig.cpp:1200 +#: xs/src/slic3r/GUI/Tab.cpp:1497 xs/src/libslic3r/PrintConfig.cpp:1300 msgid "Serial port" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1069 +#: xs/src/slic3r/GUI/Tab.cpp:1502 msgid "Rescan serial ports" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1078 xs/src/slic3r/GUI/Tab.cpp:1125 +#: xs/src/slic3r/GUI/Tab.cpp:1511 xs/src/slic3r/GUI/Tab.cpp:1558 msgid "Test" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1091 +#: xs/src/slic3r/GUI/Tab.cpp:1524 msgid "Connection to printer works correctly." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1091 xs/src/slic3r/GUI/Tab.cpp:1135 +#: xs/src/slic3r/GUI/Tab.cpp:1524 xs/src/slic3r/GUI/Tab.cpp:1568 msgid "Success!" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1094 +#: xs/src/slic3r/GUI/Tab.cpp:1527 msgid "Connection failed." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1106 +#: xs/src/slic3r/GUI/Tab.cpp:1539 xs/src/slic3r/Utils/OctoPrint.cpp:50 msgid "OctoPrint upload" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1109 xs/src/slic3r/GUI/Tab.cpp:1156 +#: xs/src/slic3r/GUI/Tab.cpp:1542 xs/src/slic3r/GUI/Tab.cpp:1591 msgid " Browse " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1135 lib/Slic3r/GUI/MainFrame.pm:209 +#: xs/src/slic3r/GUI/Tab.cpp:1568 msgid "Connection to OctoPrint works correctly." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1138 +#: xs/src/slic3r/GUI/Tab.cpp:1571 msgid "Could not connect to OctoPrint" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1138 +#: xs/src/slic3r/GUI/Tab.cpp:1571 msgid "Note: OctoPrint version at least 1.1.0 is required." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1162 +#: xs/src/slic3r/GUI/Tab.cpp:1597 msgid "Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1163 +#: xs/src/slic3r/GUI/Tab.cpp:1598 msgid "Open CA certificate file" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1177 +#: xs/src/slic3r/GUI/Tab.cpp:1612 msgid "" "HTTPS CA file is optional. It is only needed if you use HTTPS with a self-" "signed certificate." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1188 -msgid "Firmware" -msgstr "" - -#: xs/src/slic3r/GUI/Tab.cpp:1210 xs/src/libslic3r/PrintConfig.cpp:48 +#: xs/src/slic3r/GUI/Tab.cpp:1647 xs/src/libslic3r/PrintConfig.cpp:48 msgid "Before layer change G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1216 xs/src/libslic3r/PrintConfig.cpp:766 +#: xs/src/slic3r/GUI/Tab.cpp:1653 xs/src/libslic3r/PrintConfig.cpp:835 msgid "After layer change G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1222 xs/src/libslic3r/PrintConfig.cpp:1609 +#: xs/src/slic3r/GUI/Tab.cpp:1659 xs/src/libslic3r/PrintConfig.cpp:1707 msgid "Tool change G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1228 +#: xs/src/slic3r/GUI/Tab.cpp:1665 msgid "Between objects G-code (for sequential printing)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1266 xs/src/libslic3r/GCode/PreviewData.cpp:400 +#: xs/src/slic3r/GUI/Tab.cpp:1716 xs/src/slic3r/GUI/Tab.cpp:1722 +msgid "Single extruder MM setup" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1723 +msgid "Single extruder multimaterial parameters" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1734 xs/src/libslic3r/GCode/PreviewData.cpp:440 #, possible-c-format msgid "Extruder %d" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1273 +#: xs/src/slic3r/GUI/Tab.cpp:1741 msgid "Layer height limits" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1278 +#: xs/src/slic3r/GUI/Tab.cpp:1746 msgid "Position (for multi-extruder printers)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1281 +#: xs/src/slic3r/GUI/Tab.cpp:1749 msgid "Retraction" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1284 +#: xs/src/slic3r/GUI/Tab.cpp:1752 msgid "Only lift Z" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1297 +#: xs/src/slic3r/GUI/Tab.cpp:1765 msgid "" "Retraction when tool is disabled (advanced settings for multi-extruder " "setups)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1301 lib/Slic3r/GUI/Plater.pm:150 -#: lib/Slic3r/GUI/Plater.pm:2095 +#: xs/src/slic3r/GUI/Tab.cpp:1769 lib/Slic3r/GUI/Plater.pm:160 +#: lib/Slic3r/GUI/Plater.pm:2154 msgid "Preview" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1386 +#: xs/src/slic3r/GUI/Tab.cpp:1848 msgid "" "The Wipe option is not available when using the Firmware Retraction mode.\n" "\n" "Shall I disable it in order to enable Firmware Retraction?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1388 +#: xs/src/slic3r/GUI/Tab.cpp:1850 msgid "Firmware Retraction" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1547 +#: xs/src/slic3r/GUI/Tab.cpp:2022 msgid "Default " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1547 +#: xs/src/slic3r/GUI/Tab.cpp:2022 msgid " preset" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1548 +#: xs/src/slic3r/GUI/Tab.cpp:2023 msgid " preset\n" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1566 +#: xs/src/slic3r/GUI/Tab.cpp:2041 msgid "" "\n" "\n" "is not compatible with printer\n" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1566 +#: xs/src/slic3r/GUI/Tab.cpp:2041 msgid "" "\n" "\n" "and it has the following unsaved changes:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1567 +#: xs/src/slic3r/GUI/Tab.cpp:2042 msgid "" "\n" "\n" "has the following unsaved changes:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1569 +#: xs/src/slic3r/GUI/Tab.cpp:2044 msgid "" "\n" "\n" "Discard changes and continue anyway?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1570 +#: xs/src/slic3r/GUI/Tab.cpp:2045 msgid "Unsaved Changes" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1638 +#: xs/src/slic3r/GUI/Tab.cpp:2119 msgid "The supplied name is empty. It can't be saved." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1658 +#: xs/src/slic3r/GUI/Tab.cpp:2124 +msgid "Cannot overwrite a system profile." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2128 +msgid "Cannot overwrite an external." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2152 msgid "remove" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1658 +#: xs/src/slic3r/GUI/Tab.cpp:2152 msgid "delete" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1659 +#: xs/src/slic3r/GUI/Tab.cpp:2153 msgid "Are you sure you want to " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1659 +#: xs/src/slic3r/GUI/Tab.cpp:2153 msgid " the selected preset?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1660 +#: xs/src/slic3r/GUI/Tab.cpp:2154 msgid "Remove" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1660 lib/Slic3r/GUI/Plater.pm:178 -#: lib/Slic3r/GUI/Plater.pm:196 lib/Slic3r/GUI/Plater.pm:1991 +#: xs/src/slic3r/GUI/Tab.cpp:2154 lib/Slic3r/GUI/Plater.pm:188 +#: lib/Slic3r/GUI/Plater.pm:206 lib/Slic3r/GUI/Plater.pm:2050 msgid "Delete" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1661 +#: xs/src/slic3r/GUI/Tab.cpp:2155 msgid " Preset" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1714 +#: xs/src/slic3r/GUI/Tab.cpp:2208 msgid "All" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1745 +#: xs/src/slic3r/GUI/Tab.cpp:2240 msgid "Select the printers this profile is compatible with." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1834 lib/Slic3r/GUI/MainFrame.pm:509 -#: lib/Slic3r/GUI/Plater.pm:1615 +#: xs/src/slic3r/GUI/Tab.cpp:2286 xs/src/slic3r/GUI/Tab.cpp:2372 +#: xs/src/slic3r/GUI/Preset.cpp:605 xs/src/slic3r/GUI/Preset.cpp:645 +#: xs/src/slic3r/GUI/Preset.cpp:670 xs/src/slic3r/GUI/Preset.cpp:702 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1069 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1122 lib/Slic3r/GUI/Plater.pm:552 +msgid "System presets" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2287 xs/src/slic3r/GUI/Tab.cpp:2373 +msgid "Default presets" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2442 +msgid "" +"LOCKED LOCK;indicates that the settings are the same as the system values " +"for the current option group" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2445 +msgid "" +"UNLOCKED LOCK;indicates that some settings were changed and are not equal to " +"the system values for the current option group.\n" +"Click the UNLOCKED LOCK icon to reset all settings for current option group " +"to the system values." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2451 +msgid "" +"WHITE BULLET;for the left button: \tindicates a non-system preset,\n" +"for the right button: \tindicates that the settings hasn't been modified." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2455 +msgid "" +"BACK ARROW;indicates that the settings were changed and are not equal to the " +"last saved preset for the current option group.\n" +"Click the BACK ARROW icon to reset all settings for the current option group " +"to the last saved preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2481 +msgid "" +"LOCKED LOCK icon indicates that the settings are the same as the system " +"values for the current option group" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2483 +msgid "" +"UNLOCKED LOCK icon indicates that some settings were changed and are not " +"equal to the system values for the current option group.\n" +"Click to reset all settings for current option group to the system values." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2486 +msgid "WHITE BULLET icon indicates a non system preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2489 +msgid "" +"WHITE BULLET icon indicates that the settings are the same as in the last " +"saved preset for the current option group." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2491 +msgid "" +"BACK ARROW icon indicates that the settings were changed and are not equal " +"to the last saved preset for the current option group.\n" +"Click to reset all settings for the current option group to the last saved " +"preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2497 +msgid "" +"LOCKED LOCK icon indicates that the value is the same as the system value." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2498 +msgid "" +"UNLOCKED LOCK icon indicates that the value was changed and is not equal to " +"the system value.\n" +"Click to reset current value to the system value." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2504 +msgid "" +"WHITE BULLET icon indicates that the value is the same as in the last saved " +"preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2505 +msgid "" +"BACK ARROW icon indicates that the value was changed and is not equal to the " +"last saved preset.\n" +"Click to reset current value to the last saved preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2575 lib/Slic3r/GUI/MainFrame.pm:445 +#: lib/Slic3r/GUI/Plater.pm:1660 msgid "Save " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1834 +#: xs/src/slic3r/GUI/Tab.cpp:2575 msgid " as:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1868 +#: xs/src/slic3r/GUI/Tab.cpp:2609 msgid "" "The supplied name is not valid; the following characters are not allowed:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1871 +#: xs/src/slic3r/GUI/Tab.cpp:2612 msgid "The supplied name is not available." msgstr "" -#: xs/src/slic3r/GUI/Tab.hpp:185 +#: xs/src/slic3r/GUI/Tab.hpp:283 msgid "Print Settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.hpp:205 +#: xs/src/slic3r/GUI/Tab.hpp:303 msgid "Filament Settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.hpp:226 +#: xs/src/slic3r/GUI/Tab.hpp:325 msgid "Printer Settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.hpp:240 +#: xs/src/slic3r/GUI/Tab.hpp:340 msgid "Save preset" msgstr "" -#: xs/src/slic3r/GUI/Field.cpp:59 +#: xs/src/slic3r/GUI/Field.cpp:72 msgid "default" msgstr "" +#: xs/src/slic3r/GUI/Preset.cpp:649 xs/src/slic3r/GUI/Preset.cpp:706 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1127 lib/Slic3r/GUI/Plater.pm:553 +msgid "User presets" +msgstr "" + #: xs/src/slic3r/GUI/PresetHints.cpp:27 #, possible-c-format msgid "" @@ -915,123 +1474,300 @@ msgstr "" msgid "%d lines: %.2lf mm" msgstr "" -#: xs/src/slic3r/GUI/Preferences.hpp:17 -msgid "Preferences" -msgstr "" - -#: xs/src/slic3r/GUI/Preferences.cpp:27 +#: xs/src/slic3r/GUI/Preferences.cpp:34 msgid "Remember output directory" msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:29 +#: xs/src/slic3r/GUI/Preferences.cpp:36 msgid "" "If this is enabled, Slic3r will prompt the last output directory instead of " "the one containing the input files." msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:35 +#: xs/src/slic3r/GUI/Preferences.cpp:42 msgid "Auto-center parts" msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:37 +#: xs/src/slic3r/GUI/Preferences.cpp:44 msgid "" "If this is enabled, Slic3r will auto-center objects around the print bed " "center." msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:43 +#: xs/src/slic3r/GUI/Preferences.cpp:50 msgid "Background processing" msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:45 +#: xs/src/slic3r/GUI/Preferences.cpp:52 msgid "" "If this is enabled, Slic3r will pre-process objects as soon as they're " "loaded in order to save time when exporting G-code." msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:51 +#: xs/src/slic3r/GUI/Preferences.cpp:74 msgid "Disable USB/serial connection" msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:53 +#: xs/src/slic3r/GUI/Preferences.cpp:76 msgid "" "Disable communication with the printer over a serial / USB cable. This " "simplifies the user interface in case the printer is never attached to the " "computer." msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:59 +#: xs/src/slic3r/GUI/Preferences.cpp:82 msgid "Suppress \" - default - \" presets" msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:61 +#: xs/src/slic3r/GUI/Preferences.cpp:84 msgid "" "Suppress \" - default - \" presets in the Print / Filament / Printer " "selections once there are any other valid presets available." msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:67 +#: xs/src/slic3r/GUI/Preferences.cpp:90 msgid "Show incompatible print and filament presets" msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:69 +#: xs/src/slic3r/GUI/Preferences.cpp:92 msgid "" "When checked, the print and filament presets are shown in the preset editor " "even if they are marked as incompatible with the active printer" msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:75 +#: xs/src/slic3r/GUI/Preferences.cpp:98 msgid "Use legacy OpenGL 1.1 rendering" msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:77 +#: xs/src/slic3r/GUI/Preferences.cpp:100 msgid "" "If you have rendering issues caused by a buggy OpenGL 2.0 driver, you may " "try to check this checkbox. This will disable the layer height editing and " "anti aliasing, so it is likely better to upgrade your graphics driver." msgstr "" -#: xs/src/slic3r/GUI/Preferences.cpp:101 +#: xs/src/slic3r/GUI/Preferences.cpp:124 msgid "You need to restart Slic3r to make the changes effective." msgstr "" -#: xs/src/slic3r/GUI/BonjourDialog.cpp:53 -msgid "Network lookup" +#: xs/src/slic3r/GUI/RammingChart.cpp:28 +msgid "NO RAMMING AT ALL" msgstr "" -#: xs/src/slic3r/GUI/BonjourDialog.cpp:66 -msgid "Address" +#: xs/src/slic3r/GUI/RammingChart.cpp:81 +msgid "Time" msgstr "" -#: xs/src/slic3r/GUI/BonjourDialog.cpp:67 -msgid "Hostname" +#: xs/src/slic3r/GUI/RammingChart.cpp:81 xs/src/slic3r/GUI/RammingChart.cpp:86 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:82 +#: xs/src/libslic3r/PrintConfig.cpp:480 +msgid "s" msgstr "" -#: xs/src/slic3r/GUI/BonjourDialog.cpp:68 -msgid "Service name" +#: xs/src/slic3r/GUI/RammingChart.cpp:86 +msgid "Volumetric speed" msgstr "" -#: xs/src/slic3r/GUI/BonjourDialog.cpp:69 -msgid "OctoPrint version" +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:27 +msgid "Update available" msgstr "" -#: xs/src/slic3r/GUI/BonjourDialog.cpp:187 -msgid "Searching for devices" +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:27 +msgid "New version of Slic3r PE is available" msgstr "" -#: xs/src/slic3r/GUI/BonjourDialog.cpp:194 -msgid "Finished." +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:34 +msgid "To download, follow the link below." msgstr "" -#: xs/src/slic3r/Utils/OctoPrint.cpp:53 -msgid "G-code file successfully uploaded to the OctoPrint server" +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:41 +msgid "Current version:" msgstr "" -#: xs/src/slic3r/Utils/OctoPrint.cpp:67 +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:43 +msgid "New version:" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:51 +msgid "Don't notify about new releases any more" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:69 +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:161 +msgid "Configuration update" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:69 +msgid "Configuration update is available" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:72 +msgid "" +"Would you like to install it?\n" +"\n" +"Note that a full configuration snapshot will be created first. It can then " +"be restored at any time should there be a problem with the new version.\n" +"\n" +"Updated configuration bundles:" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:108 +msgid "Slic3r incompatibility" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:108 +msgid "Slic3r configuration is incompatible" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:111 +msgid "" +"This version of Slic3r PE is not compatible with currently installed " +"configuration bundles.\n" +"This probably happened as a result of running an older Slic3r PE after using " +"a newer one.\n" +"\n" +"You may either exit Slic3r and try again with a newer version, or you may re-" +"run the initial configuration. Doing so will create a backup snapshot of the " +"existing configuration before installing files compatible with this Slic3r.\n" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:120 +#, possible-c-format +msgid "This Slic3r PE version: %s" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:125 +msgid "Incompatible bundles:" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:141 +msgid "Exit Slic3r" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:144 +msgid "Re-configure" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:165 +#, possible-c-format +msgid "" +"Slic3r PE now uses an updated configuration structure.\n" +"\n" +"So called 'System presets' have been introduced, which hold the built-in " +"default settings for various printers. These System presets cannot be " +"modified, instead, users now may create their own presets inheriting " +"settings from one of the System presets.\n" +"An inheriting preset may either inherit a particular value from its parent " +"or override it with a customized value.\n" +"\n" +"Please proceed with the %s that follows to set up the new presets and to " +"choose whether to enable automatic preset updates." +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:181 +msgid "For more information please visit our wiki page:" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:14 +msgid "Ramming customization" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:40 +msgid "" +"Ramming denotes the rapid extrusion just before a tool change in a single-" +"extruder MM printer. Its purpose is to properly shape the end of the " +"unloaded filament so it does not prevent insertion of the new filament and " +"can itself be reinserted later. This phase is important and different " +"materials can require different extrusion speeds to get the good shape. For " +"this reason, the extrusion rates during ramming are adjustable.\n" +"\n" +"This is an expert-level setting, incorrect adjustment will likely lead to " +"jams, extruder wheel grinding into filament etc." +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:82 +msgid "Total ramming time" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:84 +msgid "Total rammed volume" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:88 +msgid "Ramming line width" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:90 +msgid "Ramming line spacing" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:142 +msgid "Wipe tower - Purging volume adjustment" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:223 +#, possible-c-format +msgid "" +"Here you can adjust required purging volume (mm%s) for any given pair of " +"tools." +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:224 +msgid "Extruder changed to" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:232 +msgid "unloaded" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:233 +msgid "loaded" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:238 +msgid "Tool #" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:245 +msgid "" +"Total purging volume is calculated by summing two values below, depending on " +"which tools are loaded/unloaded." +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:246 +#, possible-c-format +msgid "Volume to purge (mm%s) when the filament is being" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:260 +msgid "From" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:325 +msgid "" +"Switching to simple settings will discard changes done in the advanced " +"mode!\n" +"\n" +"Do you want to proceed?" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:337 +msgid "Show simplified settings" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:337 +msgid "Show advanced settings" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:47 msgid "Error while uploading to the OctoPrint server" msgstr "" -#: xs/src/slic3r/Utils/OctoPrint.cpp:102 +#: xs/src/slic3r/Utils/OctoPrint.cpp:51 lib/Slic3r/GUI/Plater.pm:1493 +msgid "Sending G-code file to the OctoPrint server..." +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:120 msgid "Invalid API key" msgstr "" @@ -1046,7 +1782,7 @@ msgid "" "feature slows down both the print and the G-code generation." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:38 xs/src/libslic3r/PrintConfig.cpp:1579 +#: xs/src/libslic3r/PrintConfig.cpp:38 xs/src/libslic3r/PrintConfig.cpp:1677 msgid "Other layers" msgstr "" @@ -1077,20 +1813,20 @@ msgid "" "default extruder and bed temperature are reset using non-wait command; " "however if M104, M109, M140 or M190 are detected in this custom code, Slic3r " "will not add temperature commands. Note that you can use placeholder " -"variables for all Slic3r settings, so you can put a \"M109 " -"S[first_layer_temperature]\" command wherever you want." +"variables for all Slic3r settings, so you can put a \"M109 S" +"[first_layer_temperature]\" command wherever you want." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:68 lib/Slic3r/GUI/MainFrame.pm:364 +#: xs/src/libslic3r/PrintConfig.cpp:68 lib/Slic3r/GUI/MainFrame.pm:307 msgid "Bottom" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:69 xs/src/libslic3r/PrintConfig.cpp:239 -#: xs/src/libslic3r/PrintConfig.cpp:290 xs/src/libslic3r/PrintConfig.cpp:298 -#: xs/src/libslic3r/PrintConfig.cpp:604 xs/src/libslic3r/PrintConfig.cpp:762 -#: xs/src/libslic3r/PrintConfig.cpp:778 xs/src/libslic3r/PrintConfig.cpp:948 -#: xs/src/libslic3r/PrintConfig.cpp:996 xs/src/libslic3r/PrintConfig.cpp:1159 -#: xs/src/libslic3r/PrintConfig.cpp:1590 xs/src/libslic3r/PrintConfig.cpp:1646 +#: xs/src/libslic3r/PrintConfig.cpp:69 xs/src/libslic3r/PrintConfig.cpp:267 +#: xs/src/libslic3r/PrintConfig.cpp:318 xs/src/libslic3r/PrintConfig.cpp:326 +#: xs/src/libslic3r/PrintConfig.cpp:666 xs/src/libslic3r/PrintConfig.cpp:831 +#: xs/src/libslic3r/PrintConfig.cpp:847 xs/src/libslic3r/PrintConfig.cpp:1024 +#: xs/src/libslic3r/PrintConfig.cpp:1081 xs/src/libslic3r/PrintConfig.cpp:1259 +#: xs/src/libslic3r/PrintConfig.cpp:1688 xs/src/libslic3r/PrintConfig.cpp:1744 msgid "Layers and Perimeters" msgstr "" @@ -1112,9 +1848,9 @@ msgid "" "disable acceleration control for bridges." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:80 xs/src/libslic3r/PrintConfig.cpp:174 -#: xs/src/libslic3r/PrintConfig.cpp:576 xs/src/libslic3r/PrintConfig.cpp:684 -#: xs/src/libslic3r/PrintConfig.cpp:959 +#: xs/src/libslic3r/PrintConfig.cpp:80 xs/src/libslic3r/PrintConfig.cpp:190 +#: xs/src/libslic3r/PrintConfig.cpp:638 xs/src/libslic3r/PrintConfig.cpp:746 +#: xs/src/libslic3r/PrintConfig.cpp:1044 msgid "mm/s²" msgstr "" @@ -1129,9 +1865,9 @@ msgid "" "bridges. Use 180° for zero angle." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:91 xs/src/libslic3r/PrintConfig.cpp:492 -#: xs/src/libslic3r/PrintConfig.cpp:1177 xs/src/libslic3r/PrintConfig.cpp:1188 -#: xs/src/libslic3r/PrintConfig.cpp:1408 xs/src/libslic3r/PrintConfig.cpp:1564 +#: xs/src/libslic3r/PrintConfig.cpp:91 xs/src/libslic3r/PrintConfig.cpp:554 +#: xs/src/libslic3r/PrintConfig.cpp:1277 xs/src/libslic3r/PrintConfig.cpp:1288 +#: xs/src/libslic3r/PrintConfig.cpp:1508 xs/src/libslic3r/PrintConfig.cpp:1662 msgid "°" msgstr "" @@ -1143,9 +1879,9 @@ msgstr "" msgid "This fan speed is enforced during all bridges and overhangs." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:99 xs/src/libslic3r/PrintConfig.cpp:504 -#: xs/src/libslic3r/PrintConfig.cpp:789 xs/src/libslic3r/PrintConfig.cpp:850 -#: xs/src/libslic3r/PrintConfig.cpp:1067 +#: xs/src/libslic3r/PrintConfig.cpp:99 xs/src/libslic3r/PrintConfig.cpp:566 +#: xs/src/libslic3r/PrintConfig.cpp:858 xs/src/libslic3r/PrintConfig.cpp:926 +#: xs/src/libslic3r/PrintConfig.cpp:1167 msgid "%" msgstr "" @@ -1169,11 +1905,12 @@ msgstr "" msgid "Speed for printing bridges." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:120 xs/src/libslic3r/PrintConfig.cpp:638 -#: xs/src/libslic3r/PrintConfig.cpp:749 xs/src/libslic3r/PrintConfig.cpp:811 -#: xs/src/libslic3r/PrintConfig.cpp:868 xs/src/libslic3r/PrintConfig.cpp:988 -#: xs/src/libslic3r/PrintConfig.cpp:1144 xs/src/libslic3r/PrintConfig.cpp:1153 -#: xs/src/libslic3r/PrintConfig.cpp:1543 xs/src/libslic3r/PrintConfig.cpp:1656 +#: xs/src/libslic3r/PrintConfig.cpp:120 xs/src/libslic3r/PrintConfig.cpp:461 +#: xs/src/libslic3r/PrintConfig.cpp:470 xs/src/libslic3r/PrintConfig.cpp:700 +#: xs/src/libslic3r/PrintConfig.cpp:811 xs/src/libslic3r/PrintConfig.cpp:887 +#: xs/src/libslic3r/PrintConfig.cpp:944 xs/src/libslic3r/PrintConfig.cpp:1073 +#: xs/src/libslic3r/PrintConfig.cpp:1244 xs/src/libslic3r/PrintConfig.cpp:1253 +#: xs/src/libslic3r/PrintConfig.cpp:1641 xs/src/libslic3r/PrintConfig.cpp:1754 msgid "mm/s" msgstr "" @@ -1231,65 +1968,103 @@ msgid "" "fan speed according to layer printing time." msgstr "" +#: xs/src/libslic3r/PrintConfig.cpp:170 +msgid "Cooling tube position" +msgstr "" + #: xs/src/libslic3r/PrintConfig.cpp:171 +msgid "Distance of the center-point of the cooling tube from the extruder tip " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:178 +msgid "Cooling tube length" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:179 +msgid "Length of the cooling tube to limit space for cooling moves inside it " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:187 msgid "" "This is the acceleration your printer will be reset to after the role-" "specific acceleration values are used (perimeter/infill). Set zero to " "prevent resetting acceleration at all." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:180 +#: xs/src/libslic3r/PrintConfig.cpp:196 +msgid "Default filament profile" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:197 +msgid "" +"Default filament profile associated with the current printer profile. On " +"selection of the current printer profile, this filament profile will be " +"activated." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:202 +msgid "Default print profile" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:203 +msgid "" +"Default print profile associated with the current printer profile. On " +"selection of the current printer profile, this print profile will be " +"activated." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:208 msgid "Disable fan for the first" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:181 +#: xs/src/libslic3r/PrintConfig.cpp:209 msgid "" "You can set this to a positive value to disable fan at all during the first " "layers, so that it does not make adhesion worse." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:183 xs/src/libslic3r/PrintConfig.cpp:694 -#: xs/src/libslic3r/PrintConfig.cpp:1040 xs/src/libslic3r/PrintConfig.cpp:1231 -#: xs/src/libslic3r/PrintConfig.cpp:1292 xs/src/libslic3r/PrintConfig.cpp:1444 -#: xs/src/libslic3r/PrintConfig.cpp:1489 +#: xs/src/libslic3r/PrintConfig.cpp:211 xs/src/libslic3r/PrintConfig.cpp:756 +#: xs/src/libslic3r/PrintConfig.cpp:1140 xs/src/libslic3r/PrintConfig.cpp:1331 +#: xs/src/libslic3r/PrintConfig.cpp:1392 xs/src/libslic3r/PrintConfig.cpp:1544 +#: xs/src/libslic3r/PrintConfig.cpp:1589 msgid "layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:190 +#: xs/src/libslic3r/PrintConfig.cpp:218 msgid "Don't support bridges" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:192 +#: xs/src/libslic3r/PrintConfig.cpp:220 msgid "" "Experimental option for preventing support material from being generated " "under bridged areas." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:198 +#: xs/src/libslic3r/PrintConfig.cpp:226 msgid "Distance between copies" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:199 +#: xs/src/libslic3r/PrintConfig.cpp:227 msgid "Distance used for the auto-arrange feature of the plater." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:207 +#: xs/src/libslic3r/PrintConfig.cpp:235 msgid "Elephant foot compensation" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:209 +#: xs/src/libslic3r/PrintConfig.cpp:237 msgid "" "The first layer will be shrunk in the XY plane by the configured value to " "compensate for the 1st layer squish aka an Elephant Foot effect." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:218 +#: xs/src/libslic3r/PrintConfig.cpp:246 msgid "" "This end procedure is inserted at the end of the output file. Note that you " "can use placeholder variables for all Slic3r settings." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:228 +#: xs/src/libslic3r/PrintConfig.cpp:256 msgid "" "This end procedure is inserted at the end of the output file, before the " "printer end gcode. Note that you can use placeholder variables for all " @@ -1297,38 +2072,38 @@ msgid "" "extruder order." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:238 +#: xs/src/libslic3r/PrintConfig.cpp:266 msgid "Ensure vertical shell thickness" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:240 +#: xs/src/libslic3r/PrintConfig.cpp:268 msgid "" "Add solid infill near sloping surfaces to guarantee the vertical shell " "thickness (top+bottom solid layers)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:246 +#: xs/src/libslic3r/PrintConfig.cpp:274 msgid "Top/bottom fill pattern" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:248 +#: xs/src/libslic3r/PrintConfig.cpp:276 msgid "" "Fill pattern for top/bottom infill. This only affects the external visible " "layer, and not its adjacent solid shells." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:267 xs/src/libslic3r/PrintConfig.cpp:277 +#: xs/src/libslic3r/PrintConfig.cpp:295 xs/src/libslic3r/PrintConfig.cpp:305 msgid "External perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:268 xs/src/libslic3r/PrintConfig.cpp:377 -#: xs/src/libslic3r/PrintConfig.cpp:592 xs/src/libslic3r/PrintConfig.cpp:710 -#: xs/src/libslic3r/PrintConfig.cpp:974 xs/src/libslic3r/PrintConfig.cpp:1299 -#: xs/src/libslic3r/PrintConfig.cpp:1461 xs/src/libslic3r/PrintConfig.cpp:1621 +#: xs/src/libslic3r/PrintConfig.cpp:296 xs/src/libslic3r/PrintConfig.cpp:405 +#: xs/src/libslic3r/PrintConfig.cpp:654 xs/src/libslic3r/PrintConfig.cpp:772 +#: xs/src/libslic3r/PrintConfig.cpp:1059 xs/src/libslic3r/PrintConfig.cpp:1399 +#: xs/src/libslic3r/PrintConfig.cpp:1561 xs/src/libslic3r/PrintConfig.cpp:1719 msgid "Extrusion Width" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:269 +#: xs/src/libslic3r/PrintConfig.cpp:297 msgid "" "Set this to a non-zero value to set a manual extrusion width for external " "perimeters. If left zero, default extrusion width will be used if set, " @@ -1336,60 +2111,60 @@ msgid "" "(for example 200%), it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:272 xs/src/libslic3r/PrintConfig.cpp:597 -#: xs/src/libslic3r/PrintConfig.cpp:715 xs/src/libslic3r/PrintConfig.cpp:979 -#: xs/src/libslic3r/PrintConfig.cpp:1303 xs/src/libslic3r/PrintConfig.cpp:1465 -#: xs/src/libslic3r/PrintConfig.cpp:1626 +#: xs/src/libslic3r/PrintConfig.cpp:300 xs/src/libslic3r/PrintConfig.cpp:659 +#: xs/src/libslic3r/PrintConfig.cpp:777 xs/src/libslic3r/PrintConfig.cpp:1064 +#: xs/src/libslic3r/PrintConfig.cpp:1403 xs/src/libslic3r/PrintConfig.cpp:1565 +#: xs/src/libslic3r/PrintConfig.cpp:1724 msgid "mm or % (leave 0 for default)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:279 +#: xs/src/libslic3r/PrintConfig.cpp:307 msgid "" "This separate setting will affect the speed of external perimeters (the " "visible ones). If expressed as percentage (for example: 80%) it will be " "calculated on the perimeters speed setting above. Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:282 xs/src/libslic3r/PrintConfig.cpp:619 -#: xs/src/libslic3r/PrintConfig.cpp:1262 xs/src/libslic3r/PrintConfig.cpp:1313 -#: xs/src/libslic3r/PrintConfig.cpp:1508 xs/src/libslic3r/PrintConfig.cpp:1638 +#: xs/src/libslic3r/PrintConfig.cpp:310 xs/src/libslic3r/PrintConfig.cpp:681 +#: xs/src/libslic3r/PrintConfig.cpp:1362 xs/src/libslic3r/PrintConfig.cpp:1413 +#: xs/src/libslic3r/PrintConfig.cpp:1608 xs/src/libslic3r/PrintConfig.cpp:1736 msgid "mm/s or %" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:289 +#: xs/src/libslic3r/PrintConfig.cpp:317 msgid "External perimeters first" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:291 +#: xs/src/libslic3r/PrintConfig.cpp:319 msgid "" "Print contour perimeters from the outermost one to the innermost one instead " "of the default inverse order." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:297 +#: xs/src/libslic3r/PrintConfig.cpp:325 msgid "Extra perimeters if needed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:299 -#, possible-c-format +#: xs/src/libslic3r/PrintConfig.cpp:327 +#, no-c-format msgid "" "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r " "keeps adding perimeters, until more than 70% of the loop immediately above " "is supported." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:309 +#: xs/src/libslic3r/PrintConfig.cpp:337 msgid "" "The extruder to use (unless more specific extruder settings are specified). " "This value overrides perimeter and infill extruders, but not the support " "extruders." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:320 lib/Slic3r/GUI/Plater/3DPreview.pm:69 +#: xs/src/libslic3r/PrintConfig.cpp:348 lib/Slic3r/GUI/Plater/3DPreview.pm:68 msgid "Height" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:321 +#: xs/src/libslic3r/PrintConfig.cpp:349 msgid "" "Set this to the vertical distance between your nozzle tip and (usually) the " "X carriage rods. In other words, this is the height of the clearance " @@ -1397,30 +2172,30 @@ msgid "" "extruder can peek before colliding with other printed objects." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:331 +#: xs/src/libslic3r/PrintConfig.cpp:359 msgid "Radius" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:332 +#: xs/src/libslic3r/PrintConfig.cpp:360 msgid "" "Set this to the clearance radius around your extruder. If the extruder is " "not centered, choose the largest value for safety. This setting is used to " "check for collisions and to display the graphical preview in the plater." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:342 +#: xs/src/libslic3r/PrintConfig.cpp:370 msgid "Extruder Color" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:343 xs/src/libslic3r/PrintConfig.cpp:406 +#: xs/src/libslic3r/PrintConfig.cpp:371 xs/src/libslic3r/PrintConfig.cpp:434 msgid "This is only used in the Slic3r interface as a visual help." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:350 +#: xs/src/libslic3r/PrintConfig.cpp:378 msgid "Extruder offset" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:351 +#: xs/src/libslic3r/PrintConfig.cpp:379 msgid "" "If your firmware doesn't handle the extruder displacement you need the G-" "code to take it into account. This option lets you specify the displacement " @@ -1428,21 +2203,21 @@ msgid "" "coordinates (they will be subtracted from the XY coordinate)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:360 +#: xs/src/libslic3r/PrintConfig.cpp:388 msgid "Extrusion axis" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:361 +#: xs/src/libslic3r/PrintConfig.cpp:389 msgid "" "Use this option to set the axis letter associated to your printer's extruder " "(usually E but some printers use A)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:367 +#: xs/src/libslic3r/PrintConfig.cpp:395 msgid "Extrusion multiplier" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:368 +#: xs/src/libslic3r/PrintConfig.cpp:396 msgid "" "This factor changes the amount of flow proportionally. You may need to tweak " "this setting to get nice surface finish and correct single wall widths. " @@ -1450,11 +2225,11 @@ msgid "" "more, check filament diameter and your firmware E steps." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:376 +#: xs/src/libslic3r/PrintConfig.cpp:404 msgid "Default extrusion width" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:378 +#: xs/src/libslic3r/PrintConfig.cpp:406 msgid "" "Set this to a non-zero value to allow a manual extrusion width. If left to " "zero, Slic3r derives extrusion widths from the nozzle diameter (see the " @@ -1463,74 +2238,113 @@ msgid "" "height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:382 +#: xs/src/libslic3r/PrintConfig.cpp:410 msgid "mm or % (leave 0 for auto)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:387 +#: xs/src/libslic3r/PrintConfig.cpp:415 msgid "Keep fan always on" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:388 +#: xs/src/libslic3r/PrintConfig.cpp:416 msgid "" "If this is enabled, fan will never be disabled and will be kept running at " "least at its minimum speed. Useful for PLA, harmful for ABS." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:394 +#: xs/src/libslic3r/PrintConfig.cpp:422 msgid "Enable fan if layer print time is below" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:395 +#: xs/src/libslic3r/PrintConfig.cpp:423 msgid "" "If layer print time is estimated below this number of seconds, fan will be " "enabled and its speed will be calculated by interpolating the minimum and " "maximum speeds." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:397 xs/src/libslic3r/PrintConfig.cpp:1249 +#: xs/src/libslic3r/PrintConfig.cpp:425 xs/src/libslic3r/PrintConfig.cpp:1349 msgid "approximate seconds" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:405 +#: xs/src/libslic3r/PrintConfig.cpp:433 msgid "Color" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:412 +#: xs/src/libslic3r/PrintConfig.cpp:440 msgid "Filament notes" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:413 +#: xs/src/libslic3r/PrintConfig.cpp:441 msgid "You can put your notes regarding the filament here." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:421 xs/src/libslic3r/PrintConfig.cpp:817 +#: xs/src/libslic3r/PrintConfig.cpp:449 xs/src/libslic3r/PrintConfig.cpp:893 msgid "Max volumetric speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:422 +#: xs/src/libslic3r/PrintConfig.cpp:450 msgid "" "Maximum volumetric speed allowed for this filament. Limits the maximum " "volumetric speed of a print to the minimum of print and filament volumetric " "speed. Set to zero for no limit." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:425 xs/src/libslic3r/PrintConfig.cpp:820 +#: xs/src/libslic3r/PrintConfig.cpp:453 xs/src/libslic3r/PrintConfig.cpp:896 msgid "mm³/s" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:432 +#: xs/src/libslic3r/PrintConfig.cpp:459 +msgid "Loading speed" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:460 +msgid "Speed used for loading the filament on the wipe tower. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:467 +msgid "Unloading speed" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:468 +msgid "" +"Speed used for unloading the filament on the wipe tower (does not affect " +"initial part of unloading just after ramming). " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:476 +msgid "Delay after unloading" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:477 +msgid "" +"Time to wait after the filament is unloaded. May help to get reliable " +"toolchanges with flexible materials that may need more time to shrink to " +"original dimensions. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:486 +msgid "Ramming parameters" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:487 +msgid "" +"This string is edited by RammingDialog and contains ramming specific " +"parameters " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:494 msgid "" "Enter your filament diameter here. Good precision is required, so use a " "caliper and do multiple measurements along the filament, then compute the " "average." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:440 +#: xs/src/libslic3r/PrintConfig.cpp:502 msgid "Density" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:441 +#: xs/src/libslic3r/PrintConfig.cpp:503 msgid "" "Enter your filament density here. This is only for statistical information. " "A decent way is to weigh a known length of filament and compute the ratio of " @@ -1538,15 +2352,15 @@ msgid "" "displacement." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:444 +#: xs/src/libslic3r/PrintConfig.cpp:506 msgid "g/cm³" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:450 +#: xs/src/libslic3r/PrintConfig.cpp:512 msgid "Filament type" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:451 xs/src/libslic3r/PrintConfig.cpp:1009 +#: xs/src/libslic3r/PrintConfig.cpp:513 xs/src/libslic3r/PrintConfig.cpp:1094 msgid "" "If you want to process the output G-code through custom scripts, just list " "their absolute paths here. Separate multiple scripts with a semicolon. " @@ -1555,73 +2369,74 @@ msgid "" "environment variables." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:470 +#: xs/src/libslic3r/PrintConfig.cpp:532 msgid "Soluble material" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:471 +#: xs/src/libslic3r/PrintConfig.cpp:533 msgid "Soluble material is most likely used for a soluble support." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:476 lib/Slic3r/GUI/Plater.pm:453 +#: xs/src/libslic3r/PrintConfig.cpp:538 lib/Slic3r/GUI/Plater.pm:463 msgid "Cost" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:477 +#: xs/src/libslic3r/PrintConfig.cpp:539 msgid "" "Enter your filament cost per kg here. This is only for statistical " "information." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:478 +#: xs/src/libslic3r/PrintConfig.cpp:540 msgid "money/kg" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:487 +#: xs/src/libslic3r/PrintConfig.cpp:549 msgid "Fill angle" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:489 +#: xs/src/libslic3r/PrintConfig.cpp:551 msgid "" "Default base angle for infill orientation. Cross-hatching will be applied to " "this. Bridges will be infilled using the best direction Slic3r can detect, " "so this setting does not affect them." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:501 +#: xs/src/libslic3r/PrintConfig.cpp:563 msgid "Fill density" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:503 +#: xs/src/libslic3r/PrintConfig.cpp:565 +#, possible-c-format msgid "Density of internal infill, expressed in the range 0% - 100%." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:539 +#: xs/src/libslic3r/PrintConfig.cpp:601 msgid "Fill pattern" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:541 +#: xs/src/libslic3r/PrintConfig.cpp:603 msgid "Fill pattern for general low-density infill." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:573 xs/src/libslic3r/PrintConfig.cpp:582 -#: xs/src/libslic3r/PrintConfig.cpp:591 xs/src/libslic3r/PrintConfig.cpp:625 +#: xs/src/libslic3r/PrintConfig.cpp:635 xs/src/libslic3r/PrintConfig.cpp:644 +#: xs/src/libslic3r/PrintConfig.cpp:653 xs/src/libslic3r/PrintConfig.cpp:687 msgid "First layer" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:574 +#: xs/src/libslic3r/PrintConfig.cpp:636 msgid "" "This is the acceleration your printer will use for first layer. Set zero to " "disable acceleration control for first layer." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:583 +#: xs/src/libslic3r/PrintConfig.cpp:645 msgid "" "Heated build plate temperature for the first layer. Set this to zero to " "disable bed temperature control commands in the output." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:593 +#: xs/src/libslic3r/PrintConfig.cpp:655 msgid "" "Set this to a non-zero value to set a manual extrusion width for first " "layer. You can use this to force fatter extrudates for better adhesion. If " @@ -1629,11 +2444,11 @@ msgid "" "layer height. If set to zero, it will use the default extrusion width." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:603 +#: xs/src/libslic3r/PrintConfig.cpp:665 msgid "First layer height" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:605 +#: xs/src/libslic3r/PrintConfig.cpp:667 msgid "" "When printing with very low layer heights, you might still want to print a " "thicker bottom layer to improve adhesion and tolerance for non perfect build " @@ -1641,58 +2456,58 @@ msgid "" "example: 150%) over the default layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:609 xs/src/libslic3r/PrintConfig.cpp:740 -#: xs/src/libslic3r/PrintConfig.cpp:1397 +#: xs/src/libslic3r/PrintConfig.cpp:671 xs/src/libslic3r/PrintConfig.cpp:802 +#: xs/src/libslic3r/PrintConfig.cpp:1497 msgid "mm or %" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:615 +#: xs/src/libslic3r/PrintConfig.cpp:677 msgid "First layer speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:616 +#: xs/src/libslic3r/PrintConfig.cpp:678 msgid "" "If expressed as absolute value in mm/s, this speed will be applied to all " "the print moves of the first layer, regardless of their type. If expressed " "as a percentage (for example: 40%) it will scale the default speeds." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:626 +#: xs/src/libslic3r/PrintConfig.cpp:688 msgid "" "Extruder temperature for first layer. If you want to control temperature " "manually during print, set this to zero to disable temperature control " "commands in the output file." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:634 -#: xs/src/libslic3r/GCode/PreviewData.cpp:145 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:87 +#: xs/src/libslic3r/PrintConfig.cpp:696 +#: xs/src/libslic3r/GCode/PreviewData.cpp:165 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:90 msgid "Gap fill" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:636 +#: xs/src/libslic3r/PrintConfig.cpp:698 msgid "" "Speed for filling small gaps using short zigzag moves. Keep this reasonably " "low to avoid too much shaking and resonance issues. Set zero to disable gaps " "filling." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:644 +#: xs/src/libslic3r/PrintConfig.cpp:706 msgid "Verbose G-code" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:645 +#: xs/src/libslic3r/PrintConfig.cpp:707 msgid "" "Enable this to get a commented G-code file, with each line explained by a " "descriptive text. If you print from SD card, the additional weight of the " "file could make your firmware slow down." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:652 +#: xs/src/libslic3r/PrintConfig.cpp:714 msgid "G-code flavor" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:653 +#: xs/src/libslic3r/PrintConfig.cpp:715 msgid "" "Some G/M-code commands, including temperature control and others, are not " "universal. Set this option to your printer's firmware to get a compatible " @@ -1700,35 +2515,35 @@ msgid "" "extrusion value at all." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:682 +#: xs/src/libslic3r/PrintConfig.cpp:744 msgid "" "This is the acceleration your printer will use for infill. Set zero to " "disable acceleration control for infill." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:690 +#: xs/src/libslic3r/PrintConfig.cpp:752 msgid "Combine infill every" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:692 +#: xs/src/libslic3r/PrintConfig.cpp:754 msgid "" "This feature allows to combine infill and speed up your print by extruding " "thicker infill layers while preserving thin perimeters, thus accuracy." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:696 +#: xs/src/libslic3r/PrintConfig.cpp:758 msgid "Combine infill every n layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:701 +#: xs/src/libslic3r/PrintConfig.cpp:763 msgid "Infill extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:703 +#: xs/src/libslic3r/PrintConfig.cpp:765 msgid "The extruder to use when printing infill." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:711 +#: xs/src/libslic3r/PrintConfig.cpp:773 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill. If " "left zero, default extrusion width will be used if set, otherwise 1.125 x " @@ -1737,32 +2552,32 @@ msgid "" "example 90%) it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:720 +#: xs/src/libslic3r/PrintConfig.cpp:782 msgid "Infill before perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:721 +#: xs/src/libslic3r/PrintConfig.cpp:783 msgid "" "This option will switch the print order of perimeters and infill, making the " "latter first." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:726 +#: xs/src/libslic3r/PrintConfig.cpp:788 msgid "Only infill where needed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:728 +#: xs/src/libslic3r/PrintConfig.cpp:790 msgid "" "This option will limit infill to the areas actually needed for supporting " "ceilings (it will act as internal support material). If enabled, slows down " "the G-code generation due to the multiple checks involved." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:735 +#: xs/src/libslic3r/PrintConfig.cpp:797 msgid "Infill/perimeters overlap" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:737 +#: xs/src/libslic3r/PrintConfig.cpp:799 msgid "" "This setting applies an additional overlap between infill and perimeters for " "better bonding. Theoretically this shouldn't be needed, but backlash might " @@ -1770,22 +2585,30 @@ msgid "" "perimeter extrusion width." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:748 +#: xs/src/libslic3r/PrintConfig.cpp:810 msgid "Speed for printing the internal fill. Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:757 +#: xs/src/libslic3r/PrintConfig.cpp:819 +msgid "Inherits profile" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:820 +msgid "Name of the profile, from which this profile inherits." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:826 msgid "Interface shells" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:758 +#: xs/src/libslic3r/PrintConfig.cpp:827 msgid "" "Force the generation of solid shells between adjacent materials/volumes. " "Useful for multi-extruder prints with translucent materials or manual " "soluble support material." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:767 +#: xs/src/libslic3r/PrintConfig.cpp:836 msgid "" "This custom code is inserted at every layer change, right after the Z move " "and before the extruder moves to the first layer point. Note that you can " @@ -1793,22 +2616,22 @@ msgid "" "[layer_z]." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:779 +#: xs/src/libslic3r/PrintConfig.cpp:848 msgid "" "This setting controls the height (and thus the total number) of the slices/" "layers. Thinner layers give better accuracy but take more time to print." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:787 xs/src/libslic3r/PrintConfig.cpp:796 +#: xs/src/libslic3r/PrintConfig.cpp:856 xs/src/libslic3r/PrintConfig.cpp:865 msgid "Max" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:788 +#: xs/src/libslic3r/PrintConfig.cpp:857 msgid "This setting represents the maximum speed of your fan." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:797 -#, possible-c-format +#: xs/src/libslic3r/PrintConfig.cpp:866 +#, no-c-format msgid "" "This is the highest printable layer height for this extruder, used to cap " "the variable layer height and support layer height. Maximum recommended " @@ -1816,28 +2639,38 @@ msgid "" "adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:807 +#: xs/src/libslic3r/PrintConfig.cpp:876 +msgid "Max print height" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:877 +msgid "" +"Set this to the maximum height that can be reached by your extruder while " +"printing." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:883 msgid "Max print speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:808 +#: xs/src/libslic3r/PrintConfig.cpp:884 msgid "" "When setting other speed settings to 0 Slic3r will autocalculate the optimal " "speed in order to keep constant extruder pressure. This experimental setting " "is used to set the highest print speed you want to allow." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:818 +#: xs/src/libslic3r/PrintConfig.cpp:894 msgid "" "This experimental setting is used to set the maximum volumetric speed your " "extruder supports." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:826 +#: xs/src/libslic3r/PrintConfig.cpp:902 msgid "Max volumetric slope positive" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:827 xs/src/libslic3r/PrintConfig.cpp:838 +#: xs/src/libslic3r/PrintConfig.cpp:903 xs/src/libslic3r/PrintConfig.cpp:914 msgid "" "This experimental setting is used to limit the speed of change in extrusion " "rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " @@ -1845,109 +2678,109 @@ msgid "" "s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:831 xs/src/libslic3r/PrintConfig.cpp:842 +#: xs/src/libslic3r/PrintConfig.cpp:907 xs/src/libslic3r/PrintConfig.cpp:918 msgid "mm³/s²" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:837 +#: xs/src/libslic3r/PrintConfig.cpp:913 msgid "Max volumetric slope negative" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:848 xs/src/libslic3r/PrintConfig.cpp:857 +#: xs/src/libslic3r/PrintConfig.cpp:924 xs/src/libslic3r/PrintConfig.cpp:933 msgid "Min" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:849 +#: xs/src/libslic3r/PrintConfig.cpp:925 msgid "This setting represents the minimum PWM your fan needs to work." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:858 +#: xs/src/libslic3r/PrintConfig.cpp:934 msgid "" "This is the lowest printable layer height for this extruder and limits the " "resolution for variable layer height. Typical values are between 0.05 mm and " "0.1 mm." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:866 +#: xs/src/libslic3r/PrintConfig.cpp:942 msgid "Min print speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:867 +#: xs/src/libslic3r/PrintConfig.cpp:943 msgid "Slic3r will not scale speed down below this speed." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:874 +#: xs/src/libslic3r/PrintConfig.cpp:950 msgid "Minimum extrusion length" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:875 +#: xs/src/libslic3r/PrintConfig.cpp:951 msgid "" "Generate no less than the number of skirt loops required to consume the " "specified amount of filament on the bottom layer. For multi-extruder " "machines, this minimum applies to each extruder." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:884 +#: xs/src/libslic3r/PrintConfig.cpp:960 msgid "Configuration notes" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:885 +#: xs/src/libslic3r/PrintConfig.cpp:961 msgid "" "You can put here your personal notes. This text will be added to the G-code " "header comments." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:894 +#: xs/src/libslic3r/PrintConfig.cpp:970 msgid "Nozzle diameter" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:895 +#: xs/src/libslic3r/PrintConfig.cpp:971 msgid "" "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:901 +#: xs/src/libslic3r/PrintConfig.cpp:977 msgid "API Key" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:902 +#: xs/src/libslic3r/PrintConfig.cpp:978 msgid "" "Slic3r can upload G-code files to OctoPrint. This field should contain the " "API Key required for authentication." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:915 +#: xs/src/libslic3r/PrintConfig.cpp:991 msgid "Hostname, IP or URL" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:916 +#: xs/src/libslic3r/PrintConfig.cpp:992 msgid "" "Slic3r can upload G-code files to OctoPrint. This field should contain the " "hostname, IP address or URL of the OctoPrint instance." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:922 +#: xs/src/libslic3r/PrintConfig.cpp:998 msgid "Only retract when crossing perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:923 +#: xs/src/libslic3r/PrintConfig.cpp:999 msgid "" "Disables retraction when the travel path does not exceed the upper layer's " "perimeters (and thus any ooze will be probably invisible)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:930 +#: xs/src/libslic3r/PrintConfig.cpp:1006 msgid "" "This option will drop the temperature of the inactive extruders to prevent " "oozing. It will enable a tall skirt automatically and move extruders outside " "such skirt when changing temperatures." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:937 +#: xs/src/libslic3r/PrintConfig.cpp:1013 msgid "Output filename format" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:938 +#: xs/src/libslic3r/PrintConfig.cpp:1014 msgid "" "You can use all configuration options as variables inside this template. For " "example: [layer_height], [fill_density] etc. You can also use [timestamp], " @@ -1955,38 +2788,48 @@ msgid "" "[input_filename], [input_filename_base]." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:947 +#: xs/src/libslic3r/PrintConfig.cpp:1023 msgid "Detect bridging perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:949 +#: xs/src/libslic3r/PrintConfig.cpp:1025 msgid "" "Experimental option to adjust flow for overhangs (bridge flow will be used), " "to apply bridge speed to them and enable fan." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:955 xs/src/libslic3r/PrintConfig.cpp:973 -#: xs/src/libslic3r/PrintConfig.cpp:985 xs/src/libslic3r/PrintConfig.cpp:995 +#: xs/src/libslic3r/PrintConfig.cpp:1031 +msgid "Filament parking position" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1032 +msgid "" +"Distance of the extruder tip from the position where the filament is parked " +"when unloaded. This should match the value in printer firmware. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1040 xs/src/libslic3r/PrintConfig.cpp:1058 +#: xs/src/libslic3r/PrintConfig.cpp:1070 xs/src/libslic3r/PrintConfig.cpp:1080 msgid "Perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:956 +#: xs/src/libslic3r/PrintConfig.cpp:1041 msgid "" "This is the acceleration your printer will use for perimeters. A high value " "like 9000 usually gives good results if your hardware is up to the job. Set " "zero to disable acceleration control for perimeters." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:964 +#: xs/src/libslic3r/PrintConfig.cpp:1049 msgid "Perimeter extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:966 +#: xs/src/libslic3r/PrintConfig.cpp:1051 msgid "" "The extruder to use when printing perimeters and brim. First extruder is 1." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:975 +#: xs/src/libslic3r/PrintConfig.cpp:1060 msgid "" "Set this to a non-zero value to set a manual extrusion width for perimeters. " "You may want to use thinner extrudates to get more accurate surfaces. If " @@ -1995,12 +2838,12 @@ msgid "" "it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:987 +#: xs/src/libslic3r/PrintConfig.cpp:1072 msgid "" "Speed for perimeters (contours, aka vertical shells). Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:997 +#: xs/src/libslic3r/PrintConfig.cpp:1082 msgid "" "This option sets the number of perimeters to generate for each layer. Note " "that Slic3r may increase this number automatically when it detects sloping " @@ -2008,33 +2851,59 @@ msgid "" "Perimeters option is enabled." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1001 +#: xs/src/libslic3r/PrintConfig.cpp:1086 msgid "(minimum)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1021 +#: xs/src/libslic3r/PrintConfig.cpp:1106 +msgid "Printer type" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1107 +msgid "Type of the printer." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1111 msgid "Printer notes" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1022 +#: xs/src/libslic3r/PrintConfig.cpp:1112 msgid "You can put your notes regarding the printer here." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1036 +#: xs/src/libslic3r/PrintConfig.cpp:1120 +msgid "Printer vendor" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1121 +msgid "Name of the printer vendor." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1125 +msgid "Printer variant" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1126 +msgid "" +"Name of the printer variant. For example, the printer variants may be " +"differentiated by a nozzle diameter." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1136 msgid "Raft layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1038 +#: xs/src/libslic3r/PrintConfig.cpp:1138 msgid "" "The object will be raised by this number of layers, and support material " "will be generated under it." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1046 +#: xs/src/libslic3r/PrintConfig.cpp:1146 msgid "Resolution" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1047 +#: xs/src/libslic3r/PrintConfig.cpp:1147 msgid "" "Minimum detail resolution, used to simplify the input file for speeding up " "the slicing job and reducing memory usage. High-resolution models often " @@ -2042,266 +2911,266 @@ msgid "" "simplification and use full resolution from input." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1057 +#: xs/src/libslic3r/PrintConfig.cpp:1157 msgid "Minimum travel after retraction" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1058 +#: xs/src/libslic3r/PrintConfig.cpp:1158 msgid "" "Retraction is not triggered when travel moves are shorter than this length." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1064 +#: xs/src/libslic3r/PrintConfig.cpp:1164 msgid "Retract amount before wipe" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1065 +#: xs/src/libslic3r/PrintConfig.cpp:1165 msgid "" "With bowden extruders, it may be wise to do some amount of quick retract " "before doing the wipe movement." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1072 +#: xs/src/libslic3r/PrintConfig.cpp:1172 msgid "Retract on layer change" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1073 +#: xs/src/libslic3r/PrintConfig.cpp:1173 msgid "This flag enforces a retraction whenever a Z move is done." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1078 xs/src/libslic3r/PrintConfig.cpp:1087 +#: xs/src/libslic3r/PrintConfig.cpp:1178 xs/src/libslic3r/PrintConfig.cpp:1187 msgid "Length" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1079 +#: xs/src/libslic3r/PrintConfig.cpp:1179 msgid "Retraction Length" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1080 +#: xs/src/libslic3r/PrintConfig.cpp:1180 msgid "" "When retraction is triggered, filament is pulled back by the specified " "amount (the length is measured on raw filament, before it enters the " "extruder)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1082 xs/src/libslic3r/PrintConfig.cpp:1092 +#: xs/src/libslic3r/PrintConfig.cpp:1182 xs/src/libslic3r/PrintConfig.cpp:1192 msgid "mm (zero to disable)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1088 +#: xs/src/libslic3r/PrintConfig.cpp:1188 msgid "Retraction Length (Toolchange)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1089 +#: xs/src/libslic3r/PrintConfig.cpp:1189 msgid "" "When retraction is triggered before changing tool, filament is pulled back " "by the specified amount (the length is measured on raw filament, before it " "enters the extruder)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1097 +#: xs/src/libslic3r/PrintConfig.cpp:1197 msgid "Lift Z" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1098 +#: xs/src/libslic3r/PrintConfig.cpp:1198 msgid "" "If you set this to a positive value, Z is quickly raised every time a " "retraction is triggered. When using multiple extruders, only the setting for " "the first extruder will be considered." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1106 +#: xs/src/libslic3r/PrintConfig.cpp:1206 msgid "Above Z" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1107 +#: xs/src/libslic3r/PrintConfig.cpp:1207 msgid "Only lift Z above" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1108 +#: xs/src/libslic3r/PrintConfig.cpp:1208 msgid "" "If you set this to a positive value, Z lift will only take place above the " "specified absolute Z. You can tune this setting for skipping lift on the " "first layers." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1115 +#: xs/src/libslic3r/PrintConfig.cpp:1215 msgid "Below Z" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1116 +#: xs/src/libslic3r/PrintConfig.cpp:1216 msgid "Only lift Z below" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1117 +#: xs/src/libslic3r/PrintConfig.cpp:1217 msgid "" "If you set this to a positive value, Z lift will only take place below the " "specified absolute Z. You can tune this setting for limiting lift to the " "first layers." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1125 xs/src/libslic3r/PrintConfig.cpp:1133 +#: xs/src/libslic3r/PrintConfig.cpp:1225 xs/src/libslic3r/PrintConfig.cpp:1233 msgid "Extra length on restart" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1126 +#: xs/src/libslic3r/PrintConfig.cpp:1226 msgid "" "When the retraction is compensated after the travel move, the extruder will " "push this additional amount of filament. This setting is rarely needed." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1134 +#: xs/src/libslic3r/PrintConfig.cpp:1234 msgid "" "When the retraction is compensated after changing tool, the extruder will " "push this additional amount of filament." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1141 xs/src/libslic3r/PrintConfig.cpp:1142 +#: xs/src/libslic3r/PrintConfig.cpp:1241 xs/src/libslic3r/PrintConfig.cpp:1242 msgid "Retraction Speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1143 +#: xs/src/libslic3r/PrintConfig.cpp:1243 msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1149 xs/src/libslic3r/PrintConfig.cpp:1150 +#: xs/src/libslic3r/PrintConfig.cpp:1249 xs/src/libslic3r/PrintConfig.cpp:1250 msgid "Deretraction Speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1151 +#: xs/src/libslic3r/PrintConfig.cpp:1251 msgid "" "The speed for loading of a filament into extruder after retraction (it only " "applies to the extruder motor). If left to zero, the retraction speed is " "used." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1158 +#: xs/src/libslic3r/PrintConfig.cpp:1258 msgid "Seam position" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1160 +#: xs/src/libslic3r/PrintConfig.cpp:1260 msgid "Position of perimeters starting points." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1176 +#: xs/src/libslic3r/PrintConfig.cpp:1276 msgid "Direction" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1178 +#: xs/src/libslic3r/PrintConfig.cpp:1278 msgid "Preferred direction of the seam" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1179 +#: xs/src/libslic3r/PrintConfig.cpp:1279 msgid "Seam preferred direction" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1187 +#: xs/src/libslic3r/PrintConfig.cpp:1287 msgid "Jitter" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1189 +#: xs/src/libslic3r/PrintConfig.cpp:1289 msgid "Seam preferred direction jitter" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1190 +#: xs/src/libslic3r/PrintConfig.cpp:1290 msgid "Preferred direction of the seam - jitter" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1201 +#: xs/src/libslic3r/PrintConfig.cpp:1301 msgid "USB/serial port for printer connection." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1209 +#: xs/src/libslic3r/PrintConfig.cpp:1309 msgid "Serial port speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1210 +#: xs/src/libslic3r/PrintConfig.cpp:1310 msgid "Speed (baud) of USB/serial port for printer connection." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1219 +#: xs/src/libslic3r/PrintConfig.cpp:1319 msgid "Distance from object" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1220 +#: xs/src/libslic3r/PrintConfig.cpp:1320 msgid "" "Distance between skirt and object(s). Set this to zero to attach the skirt " "to the object(s) and get a brim for better adhesion." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1228 +#: xs/src/libslic3r/PrintConfig.cpp:1328 msgid "Skirt height" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1229 +#: xs/src/libslic3r/PrintConfig.cpp:1329 msgid "" "Height of skirt expressed in layers. Set this to a tall value to use skirt " "as a shield against drafts." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1236 +#: xs/src/libslic3r/PrintConfig.cpp:1336 msgid "Loops (minimum)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1237 +#: xs/src/libslic3r/PrintConfig.cpp:1337 msgid "Skirt Loops" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1238 +#: xs/src/libslic3r/PrintConfig.cpp:1338 msgid "" "Number of loops for the skirt. If the Minimum Extrusion Length option is " "set, the number of loops might be greater than the one configured here. Set " "this to zero to disable skirt completely." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1246 +#: xs/src/libslic3r/PrintConfig.cpp:1346 msgid "Slow down if layer print time is below" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1247 +#: xs/src/libslic3r/PrintConfig.cpp:1347 msgid "" "If layer print time is estimated below this number of seconds, print moves " "speed will be scaled down to extend duration to this value." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1257 +#: xs/src/libslic3r/PrintConfig.cpp:1357 msgid "Small perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1259 +#: xs/src/libslic3r/PrintConfig.cpp:1359 msgid "" "This separate setting will affect the speed of perimeters having radius <= " "6.5mm (usually holes). If expressed as percentage (for example: 80%) it will " "be calculated on the perimeters speed setting above. Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1269 +#: xs/src/libslic3r/PrintConfig.cpp:1369 msgid "Solid infill threshold area" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1271 +#: xs/src/libslic3r/PrintConfig.cpp:1371 msgid "" "Force solid infill for regions having a smaller area than the specified " "threshold." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1272 +#: xs/src/libslic3r/PrintConfig.cpp:1372 msgid "mm²" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1278 +#: xs/src/libslic3r/PrintConfig.cpp:1378 msgid "Solid infill extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1280 +#: xs/src/libslic3r/PrintConfig.cpp:1380 msgid "The extruder to use when printing solid infill." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1286 +#: xs/src/libslic3r/PrintConfig.cpp:1386 msgid "Solid infill every" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1288 +#: xs/src/libslic3r/PrintConfig.cpp:1388 msgid "" "This feature allows to force a solid layer every given number of layers. " "Zero to disable. You can set this to any value (for example 9999); Slic3r " @@ -2309,13 +3178,13 @@ msgid "" "according to nozzle diameter and layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1298 xs/src/libslic3r/PrintConfig.cpp:1308 -#: xs/src/libslic3r/GCode/PreviewData.cpp:142 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:84 +#: xs/src/libslic3r/PrintConfig.cpp:1398 xs/src/libslic3r/PrintConfig.cpp:1408 +#: xs/src/libslic3r/GCode/PreviewData.cpp:162 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:87 msgid "Solid infill" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1300 +#: xs/src/libslic3r/PrintConfig.cpp:1400 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "solid surfaces. If left zero, default extrusion width will be used if set, " @@ -2323,22 +3192,22 @@ msgid "" "(for example 90%) it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1310 +#: xs/src/libslic3r/PrintConfig.cpp:1410 msgid "" "Speed for printing solid regions (top/bottom/internal horizontal shells). " "This can be expressed as a percentage (for example: 80%) over the default " "infill speed above. Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1322 +#: xs/src/libslic3r/PrintConfig.cpp:1422 msgid "Number of solid layers to generate on top and bottom surfaces." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1329 +#: xs/src/libslic3r/PrintConfig.cpp:1429 msgid "Spiral vase" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1330 +#: xs/src/libslic3r/PrintConfig.cpp:1430 msgid "" "This feature will raise Z gradually while printing a single-walled object in " "order to remove any visible seam. This option requires a single perimeter, " @@ -2347,18 +3216,18 @@ msgid "" "when printing more than an object." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1339 +#: xs/src/libslic3r/PrintConfig.cpp:1439 msgid "Temperature variation" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1340 +#: xs/src/libslic3r/PrintConfig.cpp:1440 msgid "" "Temperature difference to be applied when an extruder is not active. Enables " "a full-height \"sacrificial\" skirt on which the nozzles are periodically " "wiped." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1350 +#: xs/src/libslic3r/PrintConfig.cpp:1450 msgid "" "This start procedure is inserted at the beginning, after bed has reached the " "target temperature and extruder just started heating, and before extruder " @@ -2369,76 +3238,76 @@ msgid "" "\"M109 S[first_layer_temperature]\" command wherever you want." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1365 +#: xs/src/libslic3r/PrintConfig.cpp:1465 msgid "" "This start procedure is inserted at the beginning, after any printer start " "gcode. This is used to override settings for a specific filament. If Slic3r " "detects M104, M109, M140 or M190 in your custom codes, such commands will " "not be prepended automatically so you're free to customize the order of " "heating commands and other custom actions. Note that you can use placeholder " -"variables for all Slic3r settings, so you can put a \"M109 " -"S[first_layer_temperature]\" command wherever you want. If you have multiple " +"variables for all Slic3r settings, so you can put a \"M109 S" +"[first_layer_temperature]\" command wherever you want. If you have multiple " "extruders, the gcode is processed in extruder order." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1380 +#: xs/src/libslic3r/PrintConfig.cpp:1480 msgid "Single Extruder Multi Material" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1381 +#: xs/src/libslic3r/PrintConfig.cpp:1481 msgid "The printer multiplexes filaments into a single hot end." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1386 +#: xs/src/libslic3r/PrintConfig.cpp:1486 msgid "Generate support material" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1388 +#: xs/src/libslic3r/PrintConfig.cpp:1488 msgid "Enable support material generation." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1393 +#: xs/src/libslic3r/PrintConfig.cpp:1493 msgid "XY separation between an object and its support" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1395 +#: xs/src/libslic3r/PrintConfig.cpp:1495 msgid "" "XY separation between an object and its support. If expressed as percentage " "(for example 50%), it will be calculated over external perimeter width." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1405 +#: xs/src/libslic3r/PrintConfig.cpp:1505 msgid "Pattern angle" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1407 +#: xs/src/libslic3r/PrintConfig.cpp:1507 msgid "" "Use this setting to rotate the support material pattern on the horizontal " "plane." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1417 +#: xs/src/libslic3r/PrintConfig.cpp:1517 msgid "" "Only create support if it lies on a build plate. Don't create support on a " "print." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1423 +#: xs/src/libslic3r/PrintConfig.cpp:1523 msgid "Contact Z distance" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1425 +#: xs/src/libslic3r/PrintConfig.cpp:1525 msgid "" "The vertical distance between object and support material interface. Setting " "this to 0 will also prevent Slic3r from using bridge flow and speed for the " "first object layer." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1438 +#: xs/src/libslic3r/PrintConfig.cpp:1538 msgid "Enforce support for the first" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1440 +#: xs/src/libslic3r/PrintConfig.cpp:1540 msgid "" "Generate support material for the specified number of layers counting from " "bottom, regardless of whether normal support material is enabled or not and " @@ -2446,21 +3315,21 @@ msgid "" "of objects having a very thin or poor footprint on the build plate." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1446 +#: xs/src/libslic3r/PrintConfig.cpp:1546 msgid "Enforce support for the first n layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1451 +#: xs/src/libslic3r/PrintConfig.cpp:1551 msgid "Support material/raft/skirt extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1453 +#: xs/src/libslic3r/PrintConfig.cpp:1553 msgid "" "The extruder to use when printing support material, raft and skirt (1+, 0 to " "use the current extruder to minimize tool changes)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1462 +#: xs/src/libslic3r/PrintConfig.cpp:1562 msgid "" "Set this to a non-zero value to set a manual extrusion width for support " "material. If left zero, default extrusion width will be used if set, " @@ -2468,91 +3337,91 @@ msgid "" "example 90%) it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1470 +#: xs/src/libslic3r/PrintConfig.cpp:1570 msgid "Interface loops" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1472 +#: xs/src/libslic3r/PrintConfig.cpp:1572 msgid "" "Cover the top contact layer of the supports with loops. Disabled by default." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1477 +#: xs/src/libslic3r/PrintConfig.cpp:1577 msgid "Support material/raft interface extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1479 +#: xs/src/libslic3r/PrintConfig.cpp:1579 msgid "" "The extruder to use when printing support material interface (1+, 0 to use " "the current extruder to minimize tool changes). This affects raft too." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1486 +#: xs/src/libslic3r/PrintConfig.cpp:1586 msgid "Interface layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1488 +#: xs/src/libslic3r/PrintConfig.cpp:1588 msgid "" "Number of interface layers to insert between the object(s) and support " "material." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1495 +#: xs/src/libslic3r/PrintConfig.cpp:1595 msgid "Interface pattern spacing" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1497 +#: xs/src/libslic3r/PrintConfig.cpp:1597 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1504 -#: xs/src/libslic3r/GCode/PreviewData.cpp:148 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:90 +#: xs/src/libslic3r/PrintConfig.cpp:1604 +#: xs/src/libslic3r/GCode/PreviewData.cpp:168 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:93 msgid "Support material interface" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1506 +#: xs/src/libslic3r/PrintConfig.cpp:1606 msgid "" "Speed for printing support material interface layers. If expressed as " "percentage (for example 50%) it will be calculated over support material " "speed." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1515 +#: xs/src/libslic3r/PrintConfig.cpp:1615 msgid "Pattern" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1517 +#: xs/src/libslic3r/PrintConfig.cpp:1617 msgid "Pattern used to generate support material." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1531 +#: xs/src/libslic3r/PrintConfig.cpp:1629 msgid "Pattern spacing" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1533 +#: xs/src/libslic3r/PrintConfig.cpp:1631 msgid "Spacing between support material lines." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1542 +#: xs/src/libslic3r/PrintConfig.cpp:1640 msgid "Speed for printing support material." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1549 +#: xs/src/libslic3r/PrintConfig.cpp:1647 msgid "Synchronize with object layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1551 +#: xs/src/libslic3r/PrintConfig.cpp:1649 msgid "" "Synchronize support layers with the object print layers. This is useful with " "multi-material printers, where the extruder switch is expensive." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1557 +#: xs/src/libslic3r/PrintConfig.cpp:1655 msgid "Overhang threshold" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1559 +#: xs/src/libslic3r/PrintConfig.cpp:1657 msgid "" "Support material will not be generated for overhangs whose slope angle (90° " "= vertical) is above the given threshold. In other words, this value " @@ -2561,60 +3430,60 @@ msgid "" "detection (recommended)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1571 +#: xs/src/libslic3r/PrintConfig.cpp:1669 msgid "With sheath around the support" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1573 +#: xs/src/libslic3r/PrintConfig.cpp:1671 msgid "" "Add a sheath (a single perimeter line) around the base support. This makes " "the support more reliable, but also more difficult to remove." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1580 +#: xs/src/libslic3r/PrintConfig.cpp:1678 msgid "" "Extruder temperature for layers after the first one. Set this to zero to " "disable temperature control commands in the output." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1583 +#: xs/src/libslic3r/PrintConfig.cpp:1681 msgid "Temperature" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1589 +#: xs/src/libslic3r/PrintConfig.cpp:1687 msgid "Detect thin walls" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1591 +#: xs/src/libslic3r/PrintConfig.cpp:1689 msgid "" "Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1597 +#: xs/src/libslic3r/PrintConfig.cpp:1695 msgid "Threads" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1598 +#: xs/src/libslic3r/PrintConfig.cpp:1696 msgid "" "Threads are used to parallelize long-running tasks. Optimal threads number " "is slightly above the number of available cores/processors." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1610 +#: xs/src/libslic3r/PrintConfig.cpp:1708 msgid "" "This custom code is inserted right before every extruder change. Note that " "you can use placeholder variables for all Slic3r settings as well as " "[previous_extruder] and [next_extruder]." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1620 xs/src/libslic3r/PrintConfig.cpp:1631 -#: xs/src/libslic3r/GCode/PreviewData.cpp:143 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:85 +#: xs/src/libslic3r/PrintConfig.cpp:1718 xs/src/libslic3r/PrintConfig.cpp:1729 +#: xs/src/libslic3r/GCode/PreviewData.cpp:163 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:88 msgid "Top solid infill" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1622 +#: xs/src/libslic3r/PrintConfig.cpp:1720 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "top surfaces. You may want to use thinner extrudates to fill all narrow " @@ -2623,7 +3492,7 @@ msgid "" "percentage (for example 90%) it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1633 +#: xs/src/libslic3r/PrintConfig.cpp:1731 msgid "" "Speed for printing top solid layers (it only applies to the uppermost " "external layers and not to their internal solid layers). You may want to " @@ -2632,51 +3501,51 @@ msgid "" "for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1645 lib/Slic3r/GUI/MainFrame.pm:363 +#: xs/src/libslic3r/PrintConfig.cpp:1743 lib/Slic3r/GUI/MainFrame.pm:306 msgid "Top" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1647 +#: xs/src/libslic3r/PrintConfig.cpp:1745 msgid "Number of solid layers to generate on top surfaces." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1649 +#: xs/src/libslic3r/PrintConfig.cpp:1747 msgid "Top solid layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1654 lib/Slic3r/GUI/Plater/3DPreview.pm:95 +#: xs/src/libslic3r/PrintConfig.cpp:1752 lib/Slic3r/GUI/Plater/3DPreview.pm:98 msgid "Travel" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1655 +#: xs/src/libslic3r/PrintConfig.cpp:1753 msgid "Speed for travel moves (jumps between distant extrusion points)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1663 +#: xs/src/libslic3r/PrintConfig.cpp:1761 msgid "Use firmware retraction" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1664 +#: xs/src/libslic3r/PrintConfig.cpp:1762 msgid "" "This experimental setting uses G10 and G11 commands to have the firmware " "handle the retraction. This is only supported in recent Marlin." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1670 +#: xs/src/libslic3r/PrintConfig.cpp:1768 msgid "Use relative E distances" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1671 +#: xs/src/libslic3r/PrintConfig.cpp:1769 msgid "" "If your firmware requires relative E values, check this, otherwise leave it " "unchecked. Most firmwares use absolute values." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1677 +#: xs/src/libslic3r/PrintConfig.cpp:1775 msgid "Use volumetric E" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1678 +#: xs/src/libslic3r/PrintConfig.cpp:1776 msgid "" "This experimental setting uses outputs the E values in cubic millimeters " "instead of linear millimeters. If your firmware doesn't already know " @@ -2686,83 +3555,113 @@ msgid "" "only supported in recent Marlin." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1688 +#: xs/src/libslic3r/PrintConfig.cpp:1786 msgid "Enable variable layer height feature" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1689 +#: xs/src/libslic3r/PrintConfig.cpp:1787 msgid "" "Some printers or printer setups may have difficulties printing with a " "variable layer height. Enabled by default." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1695 +#: xs/src/libslic3r/PrintConfig.cpp:1793 msgid "Wipe while retracting" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1696 +#: xs/src/libslic3r/PrintConfig.cpp:1794 msgid "" "This flag will move the nozzle while retracting to minimize the possible " "blob on leaky extruders." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1703 +#: xs/src/libslic3r/PrintConfig.cpp:1801 msgid "" "Multi material printers may need to prime or purge extruders on tool " "changes. Extrude the excess material into the wipe tower." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1709 +#: xs/src/libslic3r/PrintConfig.cpp:1807 +msgid "Purging volumes - load/unload volumes" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1808 +msgid "" +"This vector saves required volumes to change from/to each tool used on the " +"wipe tower. These values are used to simplify creation of the full purging " +"volumes below. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1815 +msgid "Purging volumes - matrix" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1816 +msgid "" +"This matrix describes volumes (in cubic milimetres) required to purge the " +"new filament on the wipe tower for any given pair of tools. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1826 msgid "Position X" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1710 +#: xs/src/libslic3r/PrintConfig.cpp:1827 msgid "X coordinate of the left front corner of a wipe tower" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1716 +#: xs/src/libslic3r/PrintConfig.cpp:1833 msgid "Position Y" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1717 +#: xs/src/libslic3r/PrintConfig.cpp:1834 msgid "Y coordinate of the left front corner of a wipe tower" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1723 lib/Slic3r/GUI/Plater/3DPreview.pm:70 +#: xs/src/libslic3r/PrintConfig.cpp:1840 lib/Slic3r/GUI/Plater/3DPreview.pm:69 msgid "Width" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1724 +#: xs/src/libslic3r/PrintConfig.cpp:1841 msgid "Width of a wipe tower" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1730 -msgid "Per color change depth" +#: xs/src/libslic3r/PrintConfig.cpp:1847 +msgid "Wipe tower rotation angle" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1731 -msgid "" -"Depth of a wipe color per color change. For N colors, there will be maximum " -"(N-1) tool switches performed, therefore the total depth of the wipe tower " -"will be (N-1) times this value." +#: xs/src/libslic3r/PrintConfig.cpp:1848 +msgid "Wipe tower rotation angle with respect to x-axis " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1739 +#: xs/src/libslic3r/PrintConfig.cpp:1849 +msgid "degrees" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1854 +msgid "Maximal bridging distance" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1855 +msgid "Maximal distance between supports on sparse infill sections. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1861 msgid "XY Size Compensation" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1741 +#: xs/src/libslic3r/PrintConfig.cpp:1863 msgid "" "The object will be grown/shrunk in the XY plane by the configured value " "(negative = inwards, positive = outwards). This might be useful for fine-" "tuning hole sizes." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1749 +#: xs/src/libslic3r/PrintConfig.cpp:1871 msgid "Z offset" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1750 +#: xs/src/libslic3r/PrintConfig.cpp:1872 msgid "" "This value will be added (or subtracted) from all the Z coordinates in the " "output G-code. It is used to compensate for bad Z endstop position: for " @@ -2770,980 +3669,946 @@ msgid "" "print bed, set this to -0.3 (or fix your endstop)." msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:138 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:80 +#: xs/src/libslic3r/GCode/PreviewData.cpp:158 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:83 msgid "Perimeter" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:139 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:81 +#: xs/src/libslic3r/GCode/PreviewData.cpp:159 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:84 msgid "External perimeter" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:140 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:82 +#: xs/src/libslic3r/GCode/PreviewData.cpp:160 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:85 msgid "Overhang perimeter" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:141 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:83 +#: xs/src/libslic3r/GCode/PreviewData.cpp:161 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:86 msgid "Internal infill" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:144 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:86 +#: xs/src/libslic3r/GCode/PreviewData.cpp:164 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:89 msgid "Bridge infill" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:151 +#: xs/src/libslic3r/GCode/PreviewData.cpp:171 msgid "Mixed" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:330 -#: lib/Slic3r/GUI/Plater/3DPreview.pm:68 +#: xs/src/libslic3r/GCode/PreviewData.cpp:362 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:67 msgid "Feature type" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:332 +#: xs/src/libslic3r/GCode/PreviewData.cpp:364 msgid "Height (mm)" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:334 +#: xs/src/libslic3r/GCode/PreviewData.cpp:366 msgid "Width (mm)" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:336 +#: xs/src/libslic3r/GCode/PreviewData.cpp:368 msgid "Speed (mm/s)" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:338 +#: xs/src/libslic3r/GCode/PreviewData.cpp:370 +msgid "Volumetric flow rate (mm3/s)" +msgstr "" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:372 #: lib/Slic3r/GUI/Plater/3DPreview.pm:72 msgid "Tool" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:66 +#: lib/Slic3r/GUI.pm:303 +msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:62 msgid "Version " msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:66 +#: lib/Slic3r/GUI/MainFrame.pm:62 msgid "" " - Remember to check for updates at http://github.com/prusa3d/slic3r/releases" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:118 +#: lib/Slic3r/GUI/MainFrame.pm:114 msgid "Plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:120 +#: lib/Slic3r/GUI/MainFrame.pm:116 msgid "Controller" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:191 -msgid "No Bonjour device found" -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:191 -msgid "Device Browser" -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:212 -msgid "I wasn't able to connect to OctoPrint (" -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:213 -msgid "). Check hostname and OctoPrint version (at least 1.1.0 is required)." -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:245 +#: lib/Slic3r/GUI/MainFrame.pm:194 msgid "Open STL/OBJ/AMF…\tCtrl+O" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:245 +#: lib/Slic3r/GUI/MainFrame.pm:194 msgid "Open a model" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:248 +#: lib/Slic3r/GUI/MainFrame.pm:197 msgid "&Load Config…\tCtrl+L" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:248 +#: lib/Slic3r/GUI/MainFrame.pm:197 msgid "Load exported configuration file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:251 +#: lib/Slic3r/GUI/MainFrame.pm:200 msgid "&Export Config…\tCtrl+E" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:251 +#: lib/Slic3r/GUI/MainFrame.pm:200 msgid "Export current configuration to file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:254 +#: lib/Slic3r/GUI/MainFrame.pm:203 msgid "&Load Config Bundle…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:254 +#: lib/Slic3r/GUI/MainFrame.pm:203 msgid "Load presets from a bundle" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:257 +#: lib/Slic3r/GUI/MainFrame.pm:206 msgid "&Export Config Bundle…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:257 +#: lib/Slic3r/GUI/MainFrame.pm:206 msgid "Export all presets to file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:262 +#: lib/Slic3r/GUI/MainFrame.pm:211 msgid "Q&uick Slice…\tCtrl+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:262 +#: lib/Slic3r/GUI/MainFrame.pm:211 msgid "Slice a file into a G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:268 +#: lib/Slic3r/GUI/MainFrame.pm:217 msgid "Quick Slice and Save &As…\tCtrl+Alt+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:268 +#: lib/Slic3r/GUI/MainFrame.pm:217 msgid "Slice a file into a G-code, save as" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:274 +#: lib/Slic3r/GUI/MainFrame.pm:223 msgid "&Repeat Last Quick Slice\tCtrl+Shift+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:274 +#: lib/Slic3r/GUI/MainFrame.pm:223 msgid "Repeat last quick slice" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:281 +#: lib/Slic3r/GUI/MainFrame.pm:230 msgid "Slice to SV&G…\tCtrl+G" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:281 +#: lib/Slic3r/GUI/MainFrame.pm:230 msgid "Slice file to a multi-layer SVG" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:285 +#: lib/Slic3r/GUI/MainFrame.pm:234 msgid "(&Re)Slice Now\tCtrl+S" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:285 +#: lib/Slic3r/GUI/MainFrame.pm:234 msgid "Start new slicing process" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:288 +#: lib/Slic3r/GUI/MainFrame.pm:237 msgid "Repair STL file…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:288 +#: lib/Slic3r/GUI/MainFrame.pm:237 msgid "Automatically repair an STL file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:293 -msgid "Preferences…\tCtrl+," -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:293 -msgid "Application preferences" -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:298 +#: lib/Slic3r/GUI/MainFrame.pm:241 msgid "&Quit" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:298 +#: lib/Slic3r/GUI/MainFrame.pm:241 msgid "Quit Slic3r" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:308 +#: lib/Slic3r/GUI/MainFrame.pm:251 msgid "Export G-code..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:308 +#: lib/Slic3r/GUI/MainFrame.pm:251 msgid "Export current plate as G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:311 +#: lib/Slic3r/GUI/MainFrame.pm:254 msgid "Export plate as STL..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:311 +#: lib/Slic3r/GUI/MainFrame.pm:254 msgid "Export current plate as STL" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:314 +#: lib/Slic3r/GUI/MainFrame.pm:257 msgid "Export plate as AMF..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:314 +#: lib/Slic3r/GUI/MainFrame.pm:257 msgid "Export current plate as AMF" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:317 +#: lib/Slic3r/GUI/MainFrame.pm:260 msgid "Export plate as 3MF..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:317 +#: lib/Slic3r/GUI/MainFrame.pm:260 msgid "Export current plate as 3MF" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:330 +#: lib/Slic3r/GUI/MainFrame.pm:273 msgid "Select &Plater Tab\tCtrl+1" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:330 +#: lib/Slic3r/GUI/MainFrame.pm:273 msgid "Show the plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:336 +#: lib/Slic3r/GUI/MainFrame.pm:279 msgid "Select &Controller Tab\tCtrl+T" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:336 +#: lib/Slic3r/GUI/MainFrame.pm:279 msgid "Show the printer controller" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:344 +#: lib/Slic3r/GUI/MainFrame.pm:287 msgid "Select P&rint Settings Tab\tCtrl+2" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:344 +#: lib/Slic3r/GUI/MainFrame.pm:287 msgid "Show the print settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:347 +#: lib/Slic3r/GUI/MainFrame.pm:290 msgid "Select &Filament Settings Tab\tCtrl+3" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:347 +#: lib/Slic3r/GUI/MainFrame.pm:290 msgid "Show the filament settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:350 +#: lib/Slic3r/GUI/MainFrame.pm:293 msgid "Select Print&er Settings Tab\tCtrl+4" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:350 +#: lib/Slic3r/GUI/MainFrame.pm:293 msgid "Show the printer settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:362 +#: lib/Slic3r/GUI/MainFrame.pm:305 msgid "Iso" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:362 +#: lib/Slic3r/GUI/MainFrame.pm:305 msgid "Iso View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:363 +#: lib/Slic3r/GUI/MainFrame.pm:306 msgid "Top View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:364 +#: lib/Slic3r/GUI/MainFrame.pm:307 msgid "Bottom View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:365 +#: lib/Slic3r/GUI/MainFrame.pm:308 msgid "Front" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:365 +#: lib/Slic3r/GUI/MainFrame.pm:308 msgid "Front View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:366 +#: lib/Slic3r/GUI/MainFrame.pm:309 msgid "Rear" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:366 +#: lib/Slic3r/GUI/MainFrame.pm:309 msgid "Rear View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:367 +#: lib/Slic3r/GUI/MainFrame.pm:310 msgid "Left" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:367 +#: lib/Slic3r/GUI/MainFrame.pm:310 msgid "Left View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:368 +#: lib/Slic3r/GUI/MainFrame.pm:311 msgid "Right" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:368 +#: lib/Slic3r/GUI/MainFrame.pm:311 msgid "Right View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:374 -msgid "&Configuration " -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:374 -msgid "Run Configuration " -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:379 +#: lib/Slic3r/GUI/MainFrame.pm:317 msgid "Prusa 3D Drivers" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:379 +#: lib/Slic3r/GUI/MainFrame.pm:317 msgid "Open the Prusa3D drivers download page in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:382 +#: lib/Slic3r/GUI/MainFrame.pm:320 msgid "Prusa Edition Releases" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:382 +#: lib/Slic3r/GUI/MainFrame.pm:320 msgid "Open the Prusa Edition releases page in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:389 +#: lib/Slic3r/GUI/MainFrame.pm:327 msgid "Slic3r &Website" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:389 +#: lib/Slic3r/GUI/MainFrame.pm:327 msgid "Open the Slic3r website in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:392 +#: lib/Slic3r/GUI/MainFrame.pm:330 msgid "Slic3r &Manual" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:392 +#: lib/Slic3r/GUI/MainFrame.pm:330 msgid "Open the Slic3r manual in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:396 +#: lib/Slic3r/GUI/MainFrame.pm:334 msgid "System Info" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:396 +#: lib/Slic3r/GUI/MainFrame.pm:334 msgid "Show system information" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:399 +#: lib/Slic3r/GUI/MainFrame.pm:337 msgid "Report an Issue" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:399 +#: lib/Slic3r/GUI/MainFrame.pm:337 msgid "Report an issue on the Slic3r Prusa Edition" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:402 +#: lib/Slic3r/GUI/MainFrame.pm:340 msgid "&About Slic3r" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:402 +#: lib/Slic3r/GUI/MainFrame.pm:340 msgid "Show about dialog" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:412 +#: lib/Slic3r/GUI/MainFrame.pm:350 msgid "&File" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:413 +#: lib/Slic3r/GUI/MainFrame.pm:351 msgid "&Plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:414 +#: lib/Slic3r/GUI/MainFrame.pm:352 msgid "&Object" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:415 +#: lib/Slic3r/GUI/MainFrame.pm:353 msgid "&Window" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:416 +#: lib/Slic3r/GUI/MainFrame.pm:354 msgid "&View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:420 +#: lib/Slic3r/GUI/MainFrame.pm:357 msgid "&Help" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:452 +#: lib/Slic3r/GUI/MainFrame.pm:388 msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:464 +#: lib/Slic3r/GUI/MainFrame.pm:400 msgid "No previously sliced file." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:469 +#: lib/Slic3r/GUI/MainFrame.pm:401 lib/Slic3r/GUI/Plater.pm:1342 +msgid "Error" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:405 msgid "Previously sliced file (" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:469 +#: lib/Slic3r/GUI/MainFrame.pm:405 msgid ") not found." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:470 +#: lib/Slic3r/GUI/MainFrame.pm:406 msgid "File Not Found" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:509 +#: lib/Slic3r/GUI/MainFrame.pm:445 msgid "SVG" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:509 +#: lib/Slic3r/GUI/MainFrame.pm:445 msgid "G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:509 lib/Slic3r/GUI/Plater.pm:1615 +#: lib/Slic3r/GUI/MainFrame.pm:445 lib/Slic3r/GUI/Plater.pm:1660 msgid " file as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:523 +#: lib/Slic3r/GUI/MainFrame.pm:459 msgid "Slicing…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:523 +#: lib/Slic3r/GUI/MainFrame.pm:459 msgid "Processing " msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:543 +#: lib/Slic3r/GUI/MainFrame.pm:479 msgid " was successfully sliced." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:545 +#: lib/Slic3r/GUI/MainFrame.pm:481 msgid "Slicing Done!" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:561 +#: lib/Slic3r/GUI/MainFrame.pm:497 msgid "Select the STL file to repair:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:575 +#: lib/Slic3r/GUI/MainFrame.pm:511 msgid "Save OBJ file (less prone to coordinate errors than STL) as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:589 +#: lib/Slic3r/GUI/MainFrame.pm:525 msgid "Your file was repaired." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:589 +#: lib/Slic3r/GUI/MainFrame.pm:525 msgid "Repair" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:600 +#: lib/Slic3r/GUI/MainFrame.pm:536 msgid "Save configuration as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:618 lib/Slic3r/GUI/MainFrame.pm:662 +#: lib/Slic3r/GUI/MainFrame.pm:554 lib/Slic3r/GUI/MainFrame.pm:598 msgid "Select configuration to load:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:641 +#: lib/Slic3r/GUI/MainFrame.pm:577 msgid "Save presets bundle as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:682 +#: lib/Slic3r/GUI/MainFrame.pm:618 #, possible-perl-format msgid "%d presets successfully imported." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:744 -msgid "You have unsaved changes " -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:744 -msgid ". Discard changes and continue anyway?" -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:745 -msgid "Unsaved Presets" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:104 lib/Slic3r/GUI/Plater.pm:2094 +#: lib/Slic3r/GUI/Plater.pm:112 lib/Slic3r/GUI/Plater.pm:2153 msgid "3D" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:138 +#: lib/Slic3r/GUI/Plater.pm:148 msgid "2D" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:157 +#: lib/Slic3r/GUI/Plater.pm:167 msgid "Layers" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:177 lib/Slic3r/GUI/Plater.pm:195 +#: lib/Slic3r/GUI/Plater.pm:187 lib/Slic3r/GUI/Plater.pm:205 msgid "Add…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:179 lib/Slic3r/GUI/Plater.pm:197 +#: lib/Slic3r/GUI/Plater.pm:189 lib/Slic3r/GUI/Plater.pm:207 msgid "Delete All" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:180 lib/Slic3r/GUI/Plater.pm:198 +#: lib/Slic3r/GUI/Plater.pm:190 lib/Slic3r/GUI/Plater.pm:208 msgid "Arrange" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:182 +#: lib/Slic3r/GUI/Plater.pm:192 msgid "More" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:183 +#: lib/Slic3r/GUI/Plater.pm:193 msgid "Fewer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:185 +#: lib/Slic3r/GUI/Plater.pm:195 msgid "45° ccw" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:186 +#: lib/Slic3r/GUI/Plater.pm:196 msgid "45° cw" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:187 lib/Slic3r/GUI/Plater.pm:203 +#: lib/Slic3r/GUI/Plater.pm:197 lib/Slic3r/GUI/Plater.pm:213 msgid "Scale…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:188 lib/Slic3r/GUI/Plater.pm:204 -#: lib/Slic3r/GUI/Plater.pm:2069 +#: lib/Slic3r/GUI/Plater.pm:198 lib/Slic3r/GUI/Plater.pm:214 +#: lib/Slic3r/GUI/Plater.pm:2128 msgid "Split" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:189 lib/Slic3r/GUI/Plater.pm:205 -#: lib/Slic3r/GUI/Plater.pm:2072 +#: lib/Slic3r/GUI/Plater.pm:199 lib/Slic3r/GUI/Plater.pm:215 +#: lib/Slic3r/GUI/Plater.pm:2131 msgid "Cut…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:191 lib/Slic3r/GUI/Plater.pm:206 -#: lib/Slic3r/GUI/Plater.pm:2076 +#: lib/Slic3r/GUI/Plater.pm:201 lib/Slic3r/GUI/Plater.pm:216 +#: lib/Slic3r/GUI/Plater.pm:2135 msgid "Settings…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:192 +#: lib/Slic3r/GUI/Plater.pm:202 msgid "Layer Editing" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:207 +#: lib/Slic3r/GUI/Plater.pm:217 msgid "Layer editing" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:220 +#: lib/Slic3r/GUI/Plater.pm:230 msgid "Name" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:221 lib/Slic3r/GUI/Plater.pm:904 +#: lib/Slic3r/GUI/Plater.pm:231 lib/Slic3r/GUI/Plater.pm:942 msgid "Copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:222 lib/Slic3r/GUI/Plater.pm:1060 -#: lib/Slic3r/GUI/Plater.pm:1065 lib/Slic3r/GUI/Plater.pm:2038 +#: lib/Slic3r/GUI/Plater.pm:232 lib/Slic3r/GUI/Plater.pm:1098 +#: lib/Slic3r/GUI/Plater.pm:1103 lib/Slic3r/GUI/Plater.pm:2097 msgid "Scale" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:236 +#: lib/Slic3r/GUI/Plater.pm:246 msgid "Export G-code…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:237 +#: lib/Slic3r/GUI/Plater.pm:247 msgid "Slice now" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:238 +#: lib/Slic3r/GUI/Plater.pm:248 msgid "Print…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:239 +#: lib/Slic3r/GUI/Plater.pm:249 msgid "Send to printer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:240 +#: lib/Slic3r/GUI/Plater.pm:250 msgid "Export STL…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:367 +#: lib/Slic3r/GUI/Plater.pm:377 msgid "Print settings" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:369 +#: lib/Slic3r/GUI/Plater.pm:379 msgid "Printer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:401 +#: lib/Slic3r/GUI/Plater.pm:411 msgid "Info" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:412 +#: lib/Slic3r/GUI/Plater.pm:422 msgid "Volume" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:413 +#: lib/Slic3r/GUI/Plater.pm:423 msgid "Facets" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:414 +#: lib/Slic3r/GUI/Plater.pm:424 msgid "Materials" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:415 +#: lib/Slic3r/GUI/Plater.pm:425 msgid "Manifold" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:441 +#: lib/Slic3r/GUI/Plater.pm:451 msgid "Sliced Info" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:450 +#: lib/Slic3r/GUI/Plater.pm:460 msgid "Used Filament (m)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:451 +#: lib/Slic3r/GUI/Plater.pm:461 msgid "Used Filament (mm³)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:452 +#: lib/Slic3r/GUI/Plater.pm:462 msgid "Used Filament (g)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:454 +#: lib/Slic3r/GUI/Plater.pm:464 msgid "Estimated printing time" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:633 +#: lib/Slic3r/GUI/Plater.pm:662 msgid "Loading…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:633 lib/Slic3r/GUI/Plater.pm:647 +#: lib/Slic3r/GUI/Plater.pm:662 lib/Slic3r/GUI/Plater.pm:676 msgid "Processing input file\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:667 +#: lib/Slic3r/GUI/Plater.pm:699 msgid "" "This file contains several objects positioned at multiple heights. Instead " "of considering them as multiple objects, should I consider\n" "this file as a single object having multiple parts?\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:670 lib/Slic3r/GUI/Plater.pm:687 +#: lib/Slic3r/GUI/Plater.pm:702 lib/Slic3r/GUI/Plater.pm:719 msgid "Multi-part object detected" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:684 +#: lib/Slic3r/GUI/Plater.pm:716 msgid "" "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" "these files to represent a single object having multiple parts?\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:696 +#: lib/Slic3r/GUI/Plater.pm:728 msgid "Loaded " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:748 +#: lib/Slic3r/GUI/Plater.pm:786 msgid "" "Your object appears to be too large, so it was automatically scaled down to " "fit your print bed." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:749 +#: lib/Slic3r/GUI/Plater.pm:787 msgid "Object too large?" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:904 +#: lib/Slic3r/GUI/Plater.pm:942 msgid "Enter the number of copies of the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:931 +#: lib/Slic3r/GUI/Plater.pm:969 msgid "" "\n" "Non-positive value." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:932 +#: lib/Slic3r/GUI/Plater.pm:970 msgid "" "\n" "Not a numeric value." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:933 +#: lib/Slic3r/GUI/Plater.pm:971 msgid "Slic3r Error" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1034 +#: lib/Slic3r/GUI/Plater.pm:992 +msgid "Enter the rotation angle:" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:992 +msgid "Rotate around " +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:992 +msgid "Invalid rotation angle entered" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1072 #, possible-perl-format msgid "Enter the new size for the selected object (print bed: %smm):" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1035 lib/Slic3r/GUI/Plater.pm:1039 +#: lib/Slic3r/GUI/Plater.pm:1073 lib/Slic3r/GUI/Plater.pm:1077 msgid "Scale along " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1035 lib/Slic3r/GUI/Plater.pm:1039 -#: lib/Slic3r/GUI/Plater.pm:1060 lib/Slic3r/GUI/Plater.pm:1065 +#: lib/Slic3r/GUI/Plater.pm:1073 lib/Slic3r/GUI/Plater.pm:1077 +#: lib/Slic3r/GUI/Plater.pm:1098 lib/Slic3r/GUI/Plater.pm:1103 msgid "Invalid scaling value entered" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1039 lib/Slic3r/GUI/Plater.pm:1065 -#, possible-perl-format +#: lib/Slic3r/GUI/Plater.pm:1077 lib/Slic3r/GUI/Plater.pm:1103 +#, no-perl-format msgid "Enter the scale % for the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1060 +#: lib/Slic3r/GUI/Plater.pm:1098 msgid "Enter the new max size for the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1116 +#: lib/Slic3r/GUI/Plater.pm:1154 msgid "" "The selected object can't be split because it contains more than one volume/" "material." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1125 +#: lib/Slic3r/GUI/Plater.pm:1163 msgid "" "The selected object couldn't be split because it contains only one part." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1290 +#: lib/Slic3r/GUI/Plater.pm:1328 msgid "Slicing cancelled" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1304 +#: lib/Slic3r/GUI/Plater.pm:1342 msgid "Another export job is currently running." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1335 -msgid "Save G-code file as:" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:1352 -msgid "Export cancelled" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:1449 +#: lib/Slic3r/GUI/Plater.pm:1490 msgid "File added to print queue" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1452 -msgid "Sending G-code file to the OctoPrint server..." -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:1455 +#: lib/Slic3r/GUI/Plater.pm:1496 msgid "G-code file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1458 +#: lib/Slic3r/GUI/Plater.pm:1499 msgid "Export failed" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1509 lib/Slic3r/GUI/Plater.pm:1551 +#: lib/Slic3r/GUI/Plater.pm:1511 +msgid "OctoPrint upload finished." +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1554 lib/Slic3r/GUI/Plater.pm:1596 msgid "STL file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1562 +#: lib/Slic3r/GUI/Plater.pm:1607 msgid "AMF file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1566 +#: lib/Slic3r/GUI/Plater.pm:1611 msgid "Error exporting AMF file " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1578 +#: lib/Slic3r/GUI/Plater.pm:1623 msgid "3MF file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1582 +#: lib/Slic3r/GUI/Plater.pm:1627 msgid "Error exporting 3MF file " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1818 -msgid "" -"Please install the OpenGL modules to use this feature (see build " -"instructions)." -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:1928 +#: lib/Slic3r/GUI/Plater.pm:1985 #, possible-perl-format msgid "%d (%d shells)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1930 +#: lib/Slic3r/GUI/Plater.pm:1987 #, possible-perl-format msgid "Auto-repaired (%d errors)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1935 +#: lib/Slic3r/GUI/Plater.pm:1992 #, possible-perl-format msgid "" "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d " "facets reversed, %d backwards edges" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1940 +#: lib/Slic3r/GUI/Plater.pm:1997 msgid "Yes" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1991 +#: lib/Slic3r/GUI/Plater.pm:2050 msgid "Remove the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1994 +#: lib/Slic3r/GUI/Plater.pm:2053 msgid "Increase copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1994 +#: lib/Slic3r/GUI/Plater.pm:2053 msgid "Place one more copy of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1997 +#: lib/Slic3r/GUI/Plater.pm:2056 msgid "Decrease copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1997 +#: lib/Slic3r/GUI/Plater.pm:2056 msgid "Remove one copy of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2000 +#: lib/Slic3r/GUI/Plater.pm:2059 msgid "Set number of copies…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2000 +#: lib/Slic3r/GUI/Plater.pm:2059 msgid "Change the number of copies of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2004 +#: lib/Slic3r/GUI/Plater.pm:2063 msgid "Rotate 45° clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2004 +#: lib/Slic3r/GUI/Plater.pm:2063 msgid "Rotate the selected object by 45° clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2007 +#: lib/Slic3r/GUI/Plater.pm:2066 msgid "Rotate 45° counter-clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2007 +#: lib/Slic3r/GUI/Plater.pm:2066 msgid "Rotate the selected object by 45° counter-clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2012 +#: lib/Slic3r/GUI/Plater.pm:2071 msgid "Rotate" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2012 +#: lib/Slic3r/GUI/Plater.pm:2071 msgid "Rotate the selected object by an arbitrary angle" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2014 +#: lib/Slic3r/GUI/Plater.pm:2073 msgid "Around X axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2014 +#: lib/Slic3r/GUI/Plater.pm:2073 msgid "Rotate the selected object by an arbitrary angle around X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2017 +#: lib/Slic3r/GUI/Plater.pm:2076 msgid "Around Y axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2017 +#: lib/Slic3r/GUI/Plater.pm:2076 msgid "Rotate the selected object by an arbitrary angle around Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2020 +#: lib/Slic3r/GUI/Plater.pm:2079 msgid "Around Z axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2020 +#: lib/Slic3r/GUI/Plater.pm:2079 msgid "Rotate the selected object by an arbitrary angle around Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2025 +#: lib/Slic3r/GUI/Plater.pm:2084 msgid "Mirror" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2025 +#: lib/Slic3r/GUI/Plater.pm:2084 msgid "Mirror the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2027 lib/Slic3r/GUI/Plater.pm:2043 -#: lib/Slic3r/GUI/Plater.pm:2059 +#: lib/Slic3r/GUI/Plater.pm:2086 lib/Slic3r/GUI/Plater.pm:2102 +#: lib/Slic3r/GUI/Plater.pm:2118 msgid "Along X axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2027 +#: lib/Slic3r/GUI/Plater.pm:2086 msgid "Mirror the selected object along the X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2030 lib/Slic3r/GUI/Plater.pm:2046 -#: lib/Slic3r/GUI/Plater.pm:2062 +#: lib/Slic3r/GUI/Plater.pm:2089 lib/Slic3r/GUI/Plater.pm:2105 +#: lib/Slic3r/GUI/Plater.pm:2121 msgid "Along Y axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2030 +#: lib/Slic3r/GUI/Plater.pm:2089 msgid "Mirror the selected object along the Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2033 lib/Slic3r/GUI/Plater.pm:2049 -#: lib/Slic3r/GUI/Plater.pm:2065 +#: lib/Slic3r/GUI/Plater.pm:2092 lib/Slic3r/GUI/Plater.pm:2108 +#: lib/Slic3r/GUI/Plater.pm:2124 msgid "Along Z axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2033 +#: lib/Slic3r/GUI/Plater.pm:2092 msgid "Mirror the selected object along the Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2038 lib/Slic3r/GUI/Plater.pm:2054 +#: lib/Slic3r/GUI/Plater.pm:2097 lib/Slic3r/GUI/Plater.pm:2113 msgid "Scale the selected object along a single axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2040 lib/Slic3r/GUI/Plater.pm:2056 +#: lib/Slic3r/GUI/Plater.pm:2099 lib/Slic3r/GUI/Plater.pm:2115 msgid "Uniformly…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2040 lib/Slic3r/GUI/Plater.pm:2056 +#: lib/Slic3r/GUI/Plater.pm:2099 lib/Slic3r/GUI/Plater.pm:2115 msgid "Scale the selected object along the XYZ axes" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2043 lib/Slic3r/GUI/Plater.pm:2059 +#: lib/Slic3r/GUI/Plater.pm:2102 lib/Slic3r/GUI/Plater.pm:2118 msgid "Scale the selected object along the X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2046 lib/Slic3r/GUI/Plater.pm:2062 +#: lib/Slic3r/GUI/Plater.pm:2105 lib/Slic3r/GUI/Plater.pm:2121 msgid "Scale the selected object along the Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2049 lib/Slic3r/GUI/Plater.pm:2065 +#: lib/Slic3r/GUI/Plater.pm:2108 lib/Slic3r/GUI/Plater.pm:2124 msgid "Scale the selected object along the Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2054 +#: lib/Slic3r/GUI/Plater.pm:2113 msgid "Scale to size" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2069 +#: lib/Slic3r/GUI/Plater.pm:2128 msgid "Split the selected object into individual parts" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2072 +#: lib/Slic3r/GUI/Plater.pm:2131 msgid "Open the 3D cutting tool" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2076 +#: lib/Slic3r/GUI/Plater.pm:2135 msgid "Open the object editor dialog" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2080 +#: lib/Slic3r/GUI/Plater.pm:2139 msgid "Reload from Disk" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2080 +#: lib/Slic3r/GUI/Plater.pm:2139 msgid "Reload the selected file from Disk" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2083 +#: lib/Slic3r/GUI/Plater.pm:2142 msgid "Export object as STL…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2083 +#: lib/Slic3r/GUI/Plater.pm:2142 msgid "Export this single object as STL file" msgstr "" @@ -3755,30 +4620,34 @@ msgstr "" msgid "Drag your objects here" msgstr "" -#: lib/Slic3r/GUI/Plater/3DPreview.pm:63 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:62 msgid "1 Layer" msgstr "" -#: lib/Slic3r/GUI/Plater/3DPreview.pm:65 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:64 msgid "View" msgstr "" -#: lib/Slic3r/GUI/Plater/3DPreview.pm:75 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:71 +msgid "Volumetric flow rate" +msgstr "" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:78 msgid "Show" msgstr "" -#: lib/Slic3r/GUI/Plater/3DPreview.pm:78 lib/Slic3r/GUI/Plater/3DPreview.pm:79 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:81 lib/Slic3r/GUI/Plater/3DPreview.pm:82 msgid "Feature types" msgstr "" -#: lib/Slic3r/GUI/Plater/3DPreview.pm:96 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:99 msgid "Retractions" msgstr "" -#: lib/Slic3r/GUI/Plater/3DPreview.pm:97 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:100 msgid "Unretractions" msgstr "" -#: lib/Slic3r/GUI/Plater/3DPreview.pm:98 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:101 msgid "Shells" msgstr "" diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 2925495450..2112d67c36 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -1,15 +1,23 @@ +xs/src/slic3r/GUI/AboutDialog.cpp xs/src/slic3r/GUI/BedShapeDialog.cpp xs/src/slic3r/GUI/BedShapeDialog.hpp +xs/src/slic3r/GUI/BonjourDialog.cpp +xs/src/slic3r/GUI/ButtonsDescription.cpp +xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp +xs/src/slic3r/GUI/ConfigWizard.cpp xs/src/slic3r/GUI/GUI.cpp +xs/src/slic3r/GUI/MsgDialog.cpp xs/src/slic3r/GUI/Tab.cpp xs/src/slic3r/GUI/Tab.hpp xs/src/slic3r/GUI/Field.cpp xs/src/slic3r/GUI/OptionsGroup.cpp -xs/src/slic3r/GUI/2DBed.cpp +xs/src/slic3r/GUI/Preset.cpp +xs/src/slic3r/GUI/PresetBundle.cpp xs/src/slic3r/GUI/PresetHints.cpp -xs/src/slic3r/GUI/Preferences.hpp xs/src/slic3r/GUI/Preferences.cpp -xs/src/slic3r/GUI/BonjourDialog.cpp +xs/src/slic3r/GUI/RammingChart.cpp +xs/src/slic3r/GUI/UpdateDialogs.cpp +xs/src/slic3r/GUI/WipeTowerDialog.cpp xs/src/slic3r/Utils/OctoPrint.cpp xs/src/libslic3r/PrintConfig.cpp xs/src/libslic3r/GCode/PreviewData.cpp diff --git a/xs/src/slic3r/GUI/AboutDialog.cpp b/xs/src/slic3r/GUI/AboutDialog.cpp index 3c359fd66a..e3c4528725 100644 --- a/xs/src/slic3r/GUI/AboutDialog.cpp +++ b/xs/src/slic3r/GUI/AboutDialog.cpp @@ -57,7 +57,7 @@ AboutDialog::AboutDialog() // version { - auto version_string = _(L("Version ")) + std::string(SLIC3R_VERSION); + auto version_string = _(L("Version"))+ " " + std::string(SLIC3R_VERSION); wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize); wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); #ifdef __WXMSW__ diff --git a/xs/src/slic3r/GUI/BonjourDialog.cpp b/xs/src/slic3r/GUI/BonjourDialog.cpp index 34fac9a911..11cfea642d 100644 --- a/xs/src/slic3r/GUI/BonjourDialog.cpp +++ b/xs/src/slic3r/GUI/BonjourDialog.cpp @@ -191,7 +191,7 @@ void BonjourDialog::on_timer(wxTimerEvent &) label->SetLabel(wxString::Format("%s %s", search_str, dots)); timer_state = (timer_state) % 3 + 1; } else { - label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished.")))); + label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished"))+".")); timer->Stop(); } } diff --git a/xs/src/slic3r/GUI/ButtonsDescription.cpp b/xs/src/slic3r/GUI/ButtonsDescription.cpp index 7ea16d9420..5739fc90e3 100644 --- a/xs/src/slic3r/GUI/ButtonsDescription.cpp +++ b/xs/src/slic3r/GUI/ButtonsDescription.cpp @@ -10,7 +10,7 @@ namespace Slic3r { namespace GUI { ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions) : - wxDialog(parent, wxID_ANY, "Buttons And Text Colors Description", wxDefaultPosition, wxDefaultSize), + wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize), m_icon_descriptions(icon_descriptions) { auto grid_sizer = new wxFlexGridSizer(3, 20, 20); diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp index c0affcab7c..efcbf05bdb 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -8,32 +8,32 @@ namespace Slic3r { namespace GUI { -static std::string format_reason(const Config::Snapshot::Reason reason) +static wxString format_reason(const Config::Snapshot::Reason reason) { switch (reason) { case Config::Snapshot::SNAPSHOT_UPGRADE: - return std::string(_(L("Upgrade"))); + return wxString(_(L("Upgrade"))); case Config::Snapshot::SNAPSHOT_DOWNGRADE: - return std::string(_(L("Downgrade"))); + return wxString(_(L("Downgrade"))); case Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK: - return std::string(_(L("Before roll back"))); + return wxString(_(L("Before roll back"))); case Config::Snapshot::SNAPSHOT_USER: - return std::string(_(L("User"))); + return wxString(_(L("User"))); case Config::Snapshot::SNAPSHOT_UNKNOWN: default: - return std::string(_(L("Unknown"))); + return wxString(_(L("Unknown"))); } } -static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active) +static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active) { // Start by declaring a row with an alternating background color. - std::string text = ""; text += ""; // Format the row header. - text += std::string("") + (snapshot_active ? _(L("Active: ")) : "") + + text += wxString("") + (snapshot_active ? _(L("Active: ")) : "") + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason); if (! snapshot.comment.empty()) text += " (" + snapshot.comment + ")"; @@ -46,9 +46,10 @@ static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_ bool compatible = true; for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) { - text += _(L("vendor")) + ": " + vc.name + ", ver: " + vc.version.config_version.to_string() + ", min slic3r ver: " + vc.version.min_slic3r_version.to_string(); + text += _(L("vendor")) + ": " + vc.name +", " + _(L("version")) + ": " + vc.version.config_version.to_string() + + ", " + _(L("min slic3r version")) + ": " + vc.version.min_slic3r_version.to_string(); if (vc.version.max_slic3r_version != Semver::inf()) - text += ", max slic3r ver: " + vc.version.max_slic3r_version.to_string(); + text += ", " + _(L("max slic3r version")) + ": " + vc.version.max_slic3r_version.to_string(); text += "
"; for (const std::pair> &model : vc.models_variants_installed) { text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": "; @@ -72,9 +73,9 @@ static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_ return text; } -static std::string generate_html_page(const Config::SnapshotDB &snapshot_db, const std::string &on_snapshot) +static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot) { - std::string text = + wxString text = "" "" ""; @@ -91,7 +92,7 @@ static std::string generate_html_page(const Config::SnapshotDB &snapshot_db, con return text; } -ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const std::string &on_snapshot) +ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot) : wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) { this->SetBackgroundColour(*wxWHITE); @@ -110,8 +111,8 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db #endif html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); html->SetBorders(2); - std::string text = generate_html_page(snapshot_db, on_snapshot); - html->SetPage(text.c_str()); + wxString text = generate_html_page(snapshot_db, on_snapshot); + html->SetPage(text); vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0); html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this); } diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp index 943601e731..f43fb8ed16 100644 --- a/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp @@ -17,7 +17,7 @@ namespace Config { class ConfigSnapshotDialog : public wxDialog { public: - ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const std::string &id); + ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &id); const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; } private: diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 8d8818126d..e6c07b470e 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -203,7 +203,7 @@ static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); if (luma >= 128) { - g_color_label_modified = wxColour(253, 88, 0); + g_color_label_modified = wxColour(252, 77, 1); g_color_label_sys = wxColour(26, 132, 57); } else { g_color_label_modified = wxColour(253, 111, 40); @@ -339,6 +339,7 @@ bool load_language() void save_language() { + //! TO DO !! use GetCanonicalName; long language = wxLANGUAGE_UNKNOWN; if (g_wxLocale) { language = g_wxLocale->GetLanguage(); @@ -398,12 +399,12 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), ConfigWizard::name()); // Cmd+, is standard on OS X - what about other operating systems? - local_menu->Append(config_id_base + ConfigMenuWizard, ConfigWizard::name() + "\u2026", config_wizard_tooltip); - local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots\u2026")), _(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 + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); + local_menu->Append(config_id_base + ConfigMenuWizard, ConfigWizard::name() + "\u2026", config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots"))+"\u2026", _(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 + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences\u2026\tCtrl+,")), _(L("Application preferences"))); + local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences"))+"\u2026\tCtrl+,", _(L("Application preferences"))); local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ @@ -685,7 +686,7 @@ void show_info(wxWindow* parent, const wxString& message, const wxString& title) } void warning_catcher(wxWindow* parent, const wxString& message){ - if (message == _(L("GLUquadricObjPtr | Attempt to free unreferenced scalar")) ) + if (message == "GLUquadricObjPtr | " + _(L("Attempt to free unreferenced scalar")) ) return; wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); msg.ShowModal(); @@ -903,7 +904,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl m_optgroup->append_single_option_line(option); - Line line = { _(L("")), "" }; + Line line = { "", "" }; line.widget = [config](wxWindow* parent){ g_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + "\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); auto sizer = new wxBoxSizer(wxHORIZONTAL); diff --git a/xs/src/slic3r/GUI/Preferences.cpp b/xs/src/slic3r/GUI/Preferences.cpp index 4c60577d56..cb7ac92a69 100644 --- a/xs/src/slic3r/GUI/Preferences.cpp +++ b/xs/src/slic3r/GUI/Preferences.cpp @@ -5,6 +5,12 @@ namespace Slic3r { namespace GUI { +PreferencesDialog::PreferencesDialog(wxWindow* parent, int event_preferences) : + wxDialog(parent, wxID_ANY, _(L("Preferences")), wxDefaultPosition, wxDefaultSize), + m_event_preferences(event_preferences) { + build(); + } + void PreferencesDialog::build() { auto app_config = get_app_config(); diff --git a/xs/src/slic3r/GUI/Preferences.hpp b/xs/src/slic3r/GUI/Preferences.hpp index f94a1db69e..d01d78b70b 100644 --- a/xs/src/slic3r/GUI/Preferences.hpp +++ b/xs/src/slic3r/GUI/Preferences.hpp @@ -17,8 +17,7 @@ class PreferencesDialog : public wxDialog std::shared_ptr m_optgroup; int m_event_preferences; public: - PreferencesDialog(wxWindow* parent, int event_preferences) : wxDialog(parent, wxID_ANY, _(L("Preferences")), - wxDefaultPosition, wxDefaultSize), m_event_preferences(event_preferences) { build(); } + PreferencesDialog(wxWindow* parent, int event_preferences); ~PreferencesDialog(){ } void build(); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index bc59ac2f92..1388c35103 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -602,7 +602,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) std::map nonsys_presets; wxString selected = ""; if (!this->m_presets.front().is_visible) - ui->Append("------- System presets -------", wxNullBitmap); + ui->Append("------- " +_(L("System presets")) + " -------", wxNullBitmap); for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) @@ -642,11 +642,11 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } if (preset.is_default) - ui->Append("------- System presets -------", wxNullBitmap); + ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------- User presets -------", wxNullBitmap); + ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected) @@ -667,7 +667,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati std::map nonsys_presets; wxString selected = ""; if (!this->m_presets.front().is_visible) - ui->Append("------- System presets -------", wxNullBitmap); + ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) @@ -699,11 +699,11 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } if (preset.is_default) - ui->Append("------- System presets -------", wxNullBitmap); + ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------- User presets -------", wxNullBitmap); + ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected) @@ -808,11 +808,6 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b // If the first visible preset was not found, return the 0th element, which is the default preset. } - // Temporary decision - if (name_w_suffix == "------- System presets -------" || - name_w_suffix == "------- User presets -------") - return true; - // 2) Select the new preset. if (m_idx_selected != idx || force) { this->select_preset(idx); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 9123e144b4..63b6a4e0ce 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -1011,8 +1011,7 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) { - if (name == "------- System presets -------" || - name == "------- User presets -------") + if (name.find_first_of("-------") == 0) return; if (idx >= filament_presets.size()) @@ -1066,7 +1065,7 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma std::map nonsys_presets; wxString selected_str = ""; if (!this->filaments().front().is_visible) - ui->Append("------- System presets -------", wxNullBitmap); + ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++i) { const Preset &preset = this->filaments.preset(i); bool selected = this->filament_presets[idx_extruder] == preset.name; @@ -1119,12 +1118,12 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma selected_str = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } if (preset.is_default) - ui->Append("------- System presets -------", wxNullBitmap); + ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------- User presets -------", wxNullBitmap); + ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected_str) diff --git a/xs/src/slic3r/GUI/RammingChart.cpp b/xs/src/slic3r/GUI/RammingChart.cpp index 1ef43be026..97a6b7712c 100644 --- a/xs/src/slic3r/GUI/RammingChart.cpp +++ b/xs/src/slic3r/GUI/RammingChart.cpp @@ -25,7 +25,7 @@ void Chart::draw() { dc.DrawRectangle(m_rect); if (visible_area.m_width < 0.499) { - dc.DrawText("NO RAMMING AT ALL",wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,m_rect.GetBottom()-m_rect.GetHeight()/2)); + dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,m_rect.GetBottom()-m_rect.GetHeight()/2)); return; } @@ -78,12 +78,12 @@ void Chart::draw() { } // axis labels: - wxString label = L("Time (s)"); + wxString label = _(L("Time")) + " ("+_(L("s"))+")"; int text_width = 0; int text_height = 0; dc.GetTextExtent(label,&text_width,&text_height); dc.DrawText(label,wxPoint(0.5*(m_rect.GetRight()+m_rect.GetLeft())-text_width/2.f, m_rect.GetBottom()+25)); - label = L("Volumetric speed (mm\u00B3/s)"); + label = _(L("Volumetric speed")) + " (" + _(L("mm")) + "\u00B3/" + _(L("s")) + ")"; dc.GetTextExtent(label,&text_width,&text_height); dc.DrawRotatedText(label,wxPoint(0,0.5*(m_rect.GetBottom()+m_rect.GetTop())+text_width/2.f),90); } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index bb1d938a85..ee75f1920d 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -230,8 +230,9 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) return; if (selected_item >= 0){ std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); - if (selected_string == "------- System presets -------" || - selected_string == "------- User presets -------"){ + if (selected_string.find_first_of("-------") == 0 + /*selected_string == "------- System presets -------" || + selected_string == "------- User presets -------"*/){ m_presets_choice->SetSelection(m_selected_preset_item); return; } diff --git a/xs/src/slic3r/GUI/WipeTowerDialog.cpp b/xs/src/slic3r/GUI/WipeTowerDialog.cpp index b34de07458..4b25ccd3ad 100644 --- a/xs/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/xs/src/slic3r/GUI/WipeTowerDialog.cpp @@ -79,15 +79,15 @@ RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100); auto gsizer_param = new wxFlexGridSizer(2, 5, 15); - gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time (s):")))), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time")) + " (" + _(L("s")) + "):")), 0, wxALIGN_CENTER_VERTICAL); gsizer_param->Add(m_widget_time); - gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total rammed volume (mm"))+"\u00B3):")), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total rammed volume")) + " (" + _(L("mm")) + "\u00B3):")), 0, wxALIGN_CENTER_VERTICAL); gsizer_param->Add(m_widget_volume); gsizer_param->AddSpacer(20); gsizer_param->AddSpacer(20); - gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line width (%):")))), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line width")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL); gsizer_param->Add(m_widget_ramming_line_width_multiplicator); - gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing (%):")))), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL); gsizer_param->Add(m_widget_ramming_step_multiplicator); sizer_param->Add(gsizer_param, 0, wxTOP, 100); @@ -220,7 +220,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con // collect and format sizer format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced, - _(L("Here you can adjust required purging volume (mm\u00B3) for any given pair of tools.")), + wxString::Format(_(L("Here you can adjust required purging volume (mm%s) for any given pair of tools.")), "\u00B3"), _(L("Extruder changed to"))); // Hide preview page before new page creating @@ -243,7 +243,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con // collect and format sizer format_sizer(m_sizer_simple, m_page_simple, gridsizer_simple, _(L("Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded.")), - _(L("Volume to purge (mm\u00B3) when the filament is being")), 50); + wxString::Format(_(L("Volume to purge (mm%s) when the filament is being")), "\u00B3"), 50); m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(m_page_simple, 0, wxEXPAND | wxALL, 25); From 8ad605354460407d3462644ac811bc1bc3776c67 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 2 May 2018 17:13:39 +0200 Subject: [PATCH 125/145] PresetUpdater: More logging --- xs/src/slic3r/Utils/PresetUpdater.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 3b786e0d67..3b74b0c6b4 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -359,6 +359,11 @@ Updates PresetUpdater::priv::get_config_updates() const const auto cached_vp = VendorProfile::from_ini(path_in_cache, false); if (cached_vp.config_version == recommended->config_version) { updates.updates.emplace_back(std::move(path_in_cache), std::move(bundle_path), *recommended); + } else { + BOOST_LOG_TRIVIAL(warning) << boost::format("Vendor: %1%: Index indicates update (%2%) but cached bundle has a different version: %3%") + % idx.vendor() + % recommended->config_version.to_string() + % cached_vp.config_version.to_string(); } } } From a02bfdd2bc5f6cd942bf6c124a1e19ea7d066be9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 3 May 2018 09:10:12 +0200 Subject: [PATCH 126/145] Initial zoom set to bed extent --- lib/Slic3r/GUI/3DScene.pm | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 59b5367b58..352a612643 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1152,11 +1152,7 @@ sub InitGL { $self->volumes->finalize_geometry(1) if ($^O eq 'linux' && $self->UseVBOs); - if (scalar @{$self->volumes} > 0) { - $self->zoom_to_volumes; - } else { - $self->zoom_to_bed; - } + $self->zoom_to_bed; glClearColor(0, 0, 0, 1); glColor3f(1, 0, 0); From 20b83c5b534c6767b04338578df2d2c6ff111bd1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 May 2018 09:21:01 +0200 Subject: [PATCH 127/145] Save the Canonical Language Name instead of the Enumerator to the Slic3r.ini --- xs/src/slic3r/GUI/GUI.cpp | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index e6c07b470e..4789f4590b 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -309,22 +309,18 @@ bool select_language(wxArrayString & names, bool load_language() { - long language; - if (!g_AppConfig->has("translation_language")) - language = wxLANGUAGE_UNKNOWN; - else { - auto str_language = g_AppConfig->get("translation_language"); - language = str_language != "" ? stol(str_language) : wxLANGUAGE_UNKNOWN; - } + wxString language = wxEmptyString; + if (g_AppConfig->has("translation_language")) + language = g_AppConfig->get("translation_language"); - if (language == wxLANGUAGE_UNKNOWN) + if (language.IsEmpty()) return false; wxArrayString names; wxArrayLong identifiers; get_installed_languages(names, identifiers); for (size_t i = 0; i < identifiers.Count(); i++) { - if (identifiers[i] == language) + if (wxLocale::GetLanguageCanonicalName(identifiers[i]) == language) { g_wxLocale = new wxLocale; g_wxLocale->Init(identifiers[i]); @@ -339,13 +335,11 @@ bool load_language() void save_language() { - //! TO DO !! use GetCanonicalName; - long language = wxLANGUAGE_UNKNOWN; - if (g_wxLocale) { - language = g_wxLocale->GetLanguage(); - } - std::string str_language = std::to_string(language); - g_AppConfig->set("translation_language", str_language); + wxString language = wxEmptyString; + if (g_wxLocale) + language = g_wxLocale->GetCanonicalName(); + + g_AppConfig->set("translation_language", language.ToStdString()); g_AppConfig->save(); } From d19b1162b3689de25af260bb958272df6bf77a3a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 3 May 2018 11:09:13 +0200 Subject: [PATCH 128/145] Fixed normals on wipe tower box --- xs/src/libslic3r/TriangleMesh.cpp | 10 +++++++--- xs/src/slic3r/GUI/3DScene.cpp | 11 +++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index aecb39662e..45e4b6f5dc 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -51,9 +51,6 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& fa for (int i = 0; i < stl.stats.number_of_facets; i++) { stl_facet facet; - facet.normal.x = 0; - facet.normal.y = 0; - facet.normal.z = 0; const Pointf3& ref_f1 = points[facets[i].x]; facet.vertex[0].x = ref_f1.x; @@ -73,6 +70,13 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& fa facet.extra[0] = 0; facet.extra[1] = 0; + float normal[3]; + stl_calculate_normal(normal, &facet); + stl_normalize_vector(normal); + facet.normal.x = normal[0]; + facet.normal.y = normal[1]; + facet.normal.z = normal[2]; + stl.facet_start[i] = facet; } stl_get_size(&stl); diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index a016ab5aa0..6b2f5c8309 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -443,13 +443,16 @@ std::vector GLVolumeCollection::load_object( int GLVolumeCollection::load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs) { - float color[4] = { 1.0f, 1.0f, 0.0f, 0.5f }; + float color[4] = { 0.5f, 0.5f, 0.0f, 0.5f }; this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); - auto mesh = make_cube(width, depth, height); - mesh.translate(-width/2.f,-depth/2.f,0.f); - Point origin_of_rotation(0.f,0.f); + if (height == 0.0f) + height = 0.1f; + + auto mesh = make_cube(width, depth, height); + mesh.translate(-width / 2.f, -depth / 2.f, 0.f); + Point origin_of_rotation(0.f, 0.f); mesh.rotate(rotation_angle,&origin_of_rotation); if (use_VBOs) From af4e0308ae63149a5bc7bcf69b67d0967c7900b2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 May 2018 13:49:37 +0200 Subject: [PATCH 129/145] Fixed visibility of the contents of the GCode fields --- xs/src/slic3r/GUI/OptionsGroup.cpp | 19 ++++++++++++++++--- xs/src/slic3r/GUI/OptionsGroup.hpp | 2 -- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 09a2fe50f6..50e6a688e0 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -118,21 +118,34 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width && option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { + wxBoxSizer* tmp_sizer; +#ifdef __WXGTK__ + tmp_sizer = new wxBoxSizer(wxVERTICAL); + m_panel->SetSizer(tmp_sizer); + m_panel->Layout(); +#else + tmp_sizer = sizer; +#endif /* __WXGTK__ */ + const auto& option = option_set.front(); const auto& field = build_field(option); auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); btn_sizer->Add(field->m_Undo_to_sys_btn); btn_sizer->Add(field->m_Undo_btn); - sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0); + tmp_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0); if (is_window_field(field)) - sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); + tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); if (is_sizer_field(field)) - sizer->Add(field->getSizer(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); + tmp_sizer->Add(field->getSizer(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); return; } auto grid_sizer = m_grid_sizer; +#ifdef __WXGTK__ + m_panel->SetSizer(m_grid_sizer); + m_panel->Layout(); +#endif /* __WXGTK__ */ // Build a label if we have it wxStaticText* label=nullptr; diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 01d9184fd1..83b5b1233f 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -138,8 +138,6 @@ public: static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0); #ifdef __WXGTK__ m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - m_panel->SetSizer(m_grid_sizer); - m_panel->Layout(); sizer->Fit(m_panel); sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); #else From dd4669d1a0c86e80a3aac55f4633872aba051553 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 3 May 2018 16:08:41 +0200 Subject: [PATCH 130/145] Fixed crash when reducing objects from 2D/preview/layers tabs --- lib/Slic3r/GUI/3DScene.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 352a612643..1b6adf800e 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -908,6 +908,9 @@ sub deselect_volumes { sub select_volume { my ($self, $volume_idx) = @_; + + return if ($volume_idx >= scalar(@{$self->volumes})); + $self->volumes->[$volume_idx]->set_selected(1) if $volume_idx != -1; } From 471f90659fcfb4c765637850de19e959a6b2110f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 3 May 2018 16:28:41 +0200 Subject: [PATCH 131/145] Fixed typo preventing to build on Win --- xs/src/slic3r/GUI/OptionsGroup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 50e6a688e0..657ad03c03 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -118,7 +118,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width && option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { - wxBoxSizer* tmp_sizer; + wxSizer* tmp_sizer; #ifdef __WXGTK__ tmp_sizer = new wxBoxSizer(wxVERTICAL); m_panel->SetSizer(tmp_sizer); From 19977edae21aba12c90a52d3d51f8d3f6d3d439a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 3 May 2018 21:45:43 +0200 Subject: [PATCH 132/145] Removed the "Broken croak" support, which was useful on broken 64bit Strawberry perl only. We don't use Strawberry perl anymore, so this has been removed for clarity. Added a PerlCallback wrapper to call a Perl subroutine from a C++ code. --- CMakeLists.txt | 1 - xs/CMakeLists.txt | 4 - xs/src/libslic3r/GCode/CoolingBuffer.cpp | 2 +- xs/src/libslic3r/Utils.hpp | 15 +++ xs/src/libslic3r/utils.cpp | 123 ++++++++++++++++------- xs/src/xsinit.h | 9 -- xs/t/22_exception.t | 15 +-- 7 files changed, 104 insertions(+), 65 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb3d6dfd6c..e8b2a6faaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,6 @@ option(SLIC3R_STATIC "Compile Slic3r with static libraries (Boost, TBB, glew) option(SLIC3R_GUI "Compile Slic3r with GUI components (OpenGL, wxWidgets)" 1) option(SLIC3R_PRUSACONTROL "Compile Slic3r with the PrusaControl prject file format (requires wxWidgets base library)" 1) option(SLIC3R_PROFILE "Compile Slic3r with an invasive Shiny profiler" 0) -option(SLIC3R_HAS_BROKEN_CROAK "Compile Slic3r for a broken Strawberry Perl 64bit" 0) option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1) if (MSVC AND SLIC3R_MSVC_COMPILE_PARALLEL) diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 4f97c0acfe..4f44fc7bf7 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -475,10 +475,6 @@ if (SLIC3R_PROFILE) add_definitions(-DSLIC3R_PROFILE) endif () -if (SLIC3R_HAS_BROKEN_CROAK) - target_compile_definitions(XS PRIVATE -DSLIC3R_HAS_BROKEN_CROAK) -endif () - if (CMAKE_BUILD_TYPE MATCHES DEBUG) target_compile_definitions(XS PRIVATE -DSLIC3R_DEBUG -DDEBUG -D_DEBUG) else () diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp index b786f9bce5..a15247693c 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -536,7 +536,7 @@ float CoolingBuffer::calculate_layer_slowdown(std::vector 0) { by_slowdown_time.emplace_back(&adj); if (! m_cooling_logic_proportional) // sorts the lines, also sets adj.time_non_adjustable diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 0066aa5e69..e5157741ec 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -84,6 +84,21 @@ inline T next_highest_power_of_2(T v) return ++ v; } +class PerlCallback { +public: + PerlCallback(void *sv) : m_callback(nullptr) { this->register_callback(sv); } + PerlCallback() : m_callback(nullptr) {} + ~PerlCallback() { this->deregister_callback(); } + void register_callback(void *sv); + void deregister_callback(); + void call(); + void call(int i); + void call(int i, int j); +// void call(const std::vector &ints); +private: + void *m_callback; +}; + } // namespace Slic3r #endif // slic3r_Utils_hpp_ diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index f2415ac072..2978783a63 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -1,3 +1,5 @@ +#include "Utils.hpp" + #include #include @@ -135,44 +137,6 @@ const std::string& data_dir() } // namespace Slic3r -#ifdef SLIC3R_HAS_BROKEN_CROAK - -// Some Strawberry Perl builds (mainly the latest 64bit builds) have a broken mechanism -// for emiting Perl exception after handling a C++ exception. Perl interpreter -// simply hangs. Better to show a message box in that case and stop the application. - -#include -#include -#include -#include - -#ifdef WIN32 -#include -#endif - -void confess_at(const char *file, int line, const char *func, const char *format, ...) -{ - char dest[1024*8]; - va_list argptr; - va_start(argptr, format); - vsprintf(dest, format, argptr); - va_end(argptr); - - char filelinefunc[1024*8]; - sprintf(filelinefunc, "\r\nin function: %s\r\nfile: %s\r\nline: %d\r\n", func, file, line); - strcat(dest, filelinefunc); - strcat(dest, "\r\n Closing the application.\r\n"); - #ifdef WIN32 - ::MessageBoxA(NULL, dest, "Slic3r Prusa Edition", MB_OK | MB_ICONERROR); - #endif - - // Give up. - printf(dest); - exit(-1); -} - -#else - #include void @@ -202,7 +166,88 @@ confess_at(const char *file, int line, const char *func, #endif } -#endif +void PerlCallback::register_callback(void *sv) +{ + if (! SvROK((SV*)sv) || SvTYPE(SvRV((SV*)sv)) != SVt_PVCV) + croak("Not a Callback %_ for PerlFunction", (SV*)sv); + if (m_callback) + SvSetSV((SV*)m_callback, (SV*)sv); + else + m_callback = newSVsv((SV*)sv); +} + +void PerlCallback::deregister_callback() +{ + if (m_callback) { + sv_2mortal((SV*)m_callback); + m_callback = nullptr; + } +} + +void PerlCallback::call() +{ + if (! m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(int i) +{ + if (! m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSViv(i))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(int i, int j) +{ + if (! m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSViv(i))); + XPUSHs(sv_2mortal(newSViv(j))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +/* +void PerlCallback::call(const std::vector &ints) +{ + if (! m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + AV* av = newAV(); + for (int i : ints) + av_push(av, newSViv(i)); + XPUSHs(av); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} +*/ #ifdef WIN32 #ifndef NOMINMAX diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 9cb60384f6..01c1293ac6 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -195,15 +195,6 @@ SV* to_SV(TriangleMesh* THIS); } -#ifdef SLIC3R_HAS_BROKEN_CROAK -#undef croak -#ifdef _MSC_VER - #define croak(...) confess_at(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) -#else - #define croak(...) confess_at(__FILE__, __LINE__, __func__, __VA_ARGS__) -#endif -#endif - // Defined in wxPerlIface.cpp // Return a pointer to the associated wxWidgets object instance given by classname. extern void* wxPli_sv_2_object( pTHX_ SV* scalar, const char* classname ); diff --git a/xs/t/22_exception.t b/xs/t/22_exception.t index c7df42670a..fead8ddee7 100644 --- a/xs/t/22_exception.t +++ b/xs/t/22_exception.t @@ -6,16 +6,9 @@ use warnings; use Slic3r::XS; use Test::More tests => 1; -if ($ENV{SLIC3R_HAS_BROKEN_CROAK}) -{ - ok 1, 'SLIC3R_HAS_BROKEN_CROAK set, croaks and confesses from a C++ code will lead to an application exit!'; -} -else -{ - eval { - Slic3r::xspp_test_croak_hangs_on_strawberry(); - }; - is $@, "xspp_test_croak_hangs_on_strawberry: exception catched\n", 'croak from inside a C++ exception delivered'; -} +eval { + Slic3r::xspp_test_croak_hangs_on_strawberry(); +}; +is $@, "xspp_test_croak_hangs_on_strawberry: exception catched\n", 'croak from inside a C++ exception delivered'; __END__ From c4885506363893518f8225904ad1968f114896ac Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 4 May 2018 11:57:37 +0200 Subject: [PATCH 133/145] Fixed update of 3D view when selecting objects on Linux --- lib/Slic3r/GUI/Plater.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a0fbf15470..a3ad064cd0 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1810,6 +1810,7 @@ sub list_item_deselected { $self->select_object(undef); $self->{canvas}->Refresh; $self->{canvas3D}->deselect_volumes if $self->{canvas3D}; + $self->{canvas3D}->Render if $self->{canvas3D}; } undef $self->{_lecursor}; } @@ -1822,6 +1823,7 @@ sub list_item_selected { $self->select_object($obj_idx); $self->{canvas}->Refresh; $self->{canvas3D}->update_volumes_selection if $self->{canvas3D}; + $self->{canvas3D}->Render if $self->{canvas3D}; undef $self->{_lecursor}; } From c579ec7f5fe92e8cce773750a73086cbe8ef074a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 7 May 2018 14:23:07 +0200 Subject: [PATCH 134/145] Fixed wrong extrusion paths detection with multimaterial objects --- xs/src/libslic3r/GCode.cpp | 21 ++++++++++++------ xs/src/libslic3r/GCode.hpp | 2 ++ xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 24 ++++++--------------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 8ad479532b..dc8e5d6918 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -1287,6 +1287,10 @@ void GCode::process_layer( m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) : this->set_extruder(extruder_id); + // let analyzer tag generator aware of a role type change + if (m_enable_analyzer && layer_tools.has_wipe_tower && m_wipe_tower) + m_last_analyzer_extrusion_role = erWipeTower; + if (extrude_skirt) { auto loops_it = skirt_loops_per_extruder.find(extruder_id); if (loops_it != skirt_loops_per_extruder.end()) { @@ -2170,7 +2174,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line - if (m_enable_extrusion_role_markers || m_enable_analyzer) + if (m_enable_extrusion_role_markers) { if (path.role() != m_last_extrusion_role) { @@ -2181,18 +2185,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(m_last_extrusion_role)); gcode += buf; } - if (m_enable_analyzer) - { - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_extrusion_role)); - gcode += buf; - } } } // adds analyzer tags and updates analyzer's tracking data if (m_enable_analyzer) { + if (path.role() != m_last_analyzer_extrusion_role) + { + m_last_analyzer_extrusion_role = path.role(); + char buf[32]; + sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role)); + gcode += buf; + } + if (m_last_mm3_per_mm != path.mm3_per_mm) { m_last_mm3_per_mm = path.mm3_per_mm; @@ -2230,6 +2236,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (path.role() == erExternalPerimeter) comment += ";_EXTERNAL_PERIMETER"; } + // F is mm per minute. gcode += m_writer.set_speed(F, "", comment); double path_length = 0.; diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 968cd14dec..d028e90aac 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -121,6 +121,7 @@ public: m_enable_cooling_markers(false), m_enable_extrusion_role_markers(false), m_enable_analyzer(false), + m_last_analyzer_extrusion_role(erNone), m_layer_count(0), m_layer_index(-1), m_layer(nullptr), @@ -253,6 +254,7 @@ protected: // Extended markers will be added during G-code generation. // The G-code Analyzer will remove these comments from the final G-code. bool m_enable_analyzer; + ExtrusionRole m_last_analyzer_extrusion_role; // How many times will change_layer() be called? // change_layer() will update the progress bar. unsigned int m_layer_count; diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index ad7d91c50d..b414186bed 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -126,6 +126,11 @@ public: m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool)); } + // adds tag for analyzer + char buf[64]; + sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); + m_gcode += buf; + m_gcode += "G1"; if (rot.x != rotated_current_pos.x) { m_gcode += set_format_X(rot.x); // Transform current position back to wipe tower coordinates (was updated by set_format_X) @@ -488,11 +493,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( .travel(cleaning_box.ld, 7200) .set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. - // adds tag for analyzer - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); - writer.append(buf); - for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { unsigned int tool = tools[idx_tool]; m_left_to_right = true; @@ -585,12 +585,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); writer.set_initial_position(initial_position); - - // adds tag for analyzer - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); - writer.append(buf); - // Increase the extruder driver current to allow fast ramming. writer.set_extruder_trimpot(750); @@ -658,12 +652,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); writer.set_initial_position(initial_position); - // adds tag for analyzer - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); - writer.append(buf) - .extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. - 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); + writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. + 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. // Extrude 4 rounds of a brim around the future wipe tower. From 8eb9ddc2eb6c3f98cf28125ddbee4d233fa58616 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 7 May 2018 16:13:58 +0200 Subject: [PATCH 135/145] Max count of auto assigned extruders when splitting object set as dependent of current printer --- lib/Slic3r/GUI/Plater.pm | 4 ++-- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 3 ++- xs/src/libslic3r/Model.cpp | 16 ++++++++-------- xs/src/libslic3r/Model.hpp | 10 +++++----- xs/xsp/Model.xsp | 4 ++-- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a3ad064cd0..0a5d02b944 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -700,7 +700,7 @@ sub load_files { . "Instead of considering them as multiple objects, should I consider\n" . "this file as a single object having multiple parts?\n"), L('Multi-part object detected'), wxICON_WARNING | wxYES | wxNO); - $model->convert_multipart_object if $dialog->ShowModal() == wxID_YES; + $model->convert_multipart_object if $dialog->ShowModal(scalar(@$nozzle_dmrs)) == wxID_YES; } if ($one_by_one) { @@ -717,7 +717,7 @@ sub load_files { . "Instead of considering them as multiple objects, should I consider\n" . "these files to represent a single object having multiple parts?\n"), L('Multi-part object detected'), wxICON_WARNING | wxYES | wxNO); - $new_model->convert_multipart_object if $dialog->ShowModal() == wxID_YES; + $new_model->convert_multipart_object if $dialog->ShowModal(scalar(@$nozzle_dmrs)) == wxID_YES; push @obj_idx, $self->load_model_objects(@{$new_model->objects}); } diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 28e3bf92b5..a632edeeae 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -475,7 +475,8 @@ sub on_btn_split { my $itemData = $self->get_selection; if ($itemData && $itemData->{type} eq 'volume') { my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}]; - $self->{parts_changed} = 1 if $volume->split > 1; + my $nozzle_dmrs = $self->GetParent->GetParent->GetParent->{config}->get('nozzle_diameter'); + $self->{parts_changed} = 1 if $volume->split(scalar(@$nozzle_dmrs)) > 1; } $self->_parts_changed; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index a43bf02213..7559411448 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -404,7 +404,7 @@ bool Model::looks_like_multipart_object() const return false; } -void Model::convert_multipart_object() +void Model::convert_multipart_object(unsigned int max_extruders) { if (this->objects.empty()) return; @@ -421,7 +421,7 @@ void Model::convert_multipart_object() if (new_v != nullptr) { new_v->name = o->name; - new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string()); + new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders)); } } @@ -481,20 +481,20 @@ bool Model::fits_print_volume(const FullPrintConfig &config) const return print_volume.contains(transformed_bounding_box()); } -unsigned int Model::get_auto_extruder_id() +unsigned int Model::get_auto_extruder_id(unsigned int max_extruders) { unsigned int id = s_auto_extruder_id; - if (++s_auto_extruder_id > 4) + if (++s_auto_extruder_id > max_extruders) reset_auto_extruder_id(); return id; } -std::string Model::get_auto_extruder_id_as_string() +std::string Model::get_auto_extruder_id_as_string(unsigned int max_extruders) { char str_extruder[64]; - sprintf(str_extruder, "%ud", get_auto_extruder_id()); + sprintf(str_extruder, "%ud", get_auto_extruder_id(max_extruders)); return str_extruder; } @@ -996,7 +996,7 @@ ModelMaterial* ModelVolume::assign_unique_material() // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. -size_t ModelVolume::split() +size_t ModelVolume::split(unsigned int max_extruders) { TriangleMeshPtrs meshptrs = this->mesh.split(); if (meshptrs.size() <= 1) { @@ -1019,7 +1019,7 @@ size_t ModelVolume::split() char str_idx[64]; sprintf(str_idx, "_%d", idx + 1); this->object->volumes[ivolume]->name = name + str_idx; - this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string()); + this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string(max_extruders)); delete mesh; ++ idx; } diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 0c0ffe7769..4835028b98 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -173,8 +173,8 @@ public: // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. - size_t split(); - + size_t split(unsigned int max_extruders); + ModelMaterial* assign_unique_material(); private: @@ -280,7 +280,7 @@ public: void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); bool looks_like_multipart_object() const; - void convert_multipart_object(); + void convert_multipart_object(unsigned int max_extruders); // Ensures that the min z of the model is not negative void adjust_min_z(); @@ -291,8 +291,8 @@ public: void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } - static unsigned int get_auto_extruder_id(); - static std::string get_auto_extruder_id_as_string(); + static unsigned int get_auto_extruder_id(unsigned int max_extruders); + static std::string get_auto_extruder_id_as_string(unsigned int max_extruders); static void reset_auto_extruder_id(); }; diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 7028395373..5b83183539 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -95,7 +95,7 @@ void duplicate_objects_grid(unsigned int x, unsigned int y, double dist); bool looks_like_multipart_object() const; - void convert_multipart_object(); + void convert_multipart_object(unsigned int max_extruders); void print_info() const; @@ -346,7 +346,7 @@ ModelMaterial::attributes() void set_modifier(bool modifier) %code%{ THIS->modifier = modifier; %}; - size_t split(); + size_t split(unsigned int max_extruders); ModelMaterial* assign_unique_material(); }; From 754bfd926b46924df2e1c56adb945c12ba900f02 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 May 2018 11:10:44 +0200 Subject: [PATCH 136/145] Auto-correction of the input values according to the admissible range --- xs/src/libslic3r/PrintConfig.cpp | 3 ++- xs/src/slic3r/GUI/Field.cpp | 25 ++++++++++++++++++------- xs/src/slic3r/GUI/Field.hpp | 1 - 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index f70a42182e..697a6f0d4a 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -111,7 +111,8 @@ PrintConfigDef::PrintConfigDef() "with cooling (use a fan) before tweaking this."); def->cli = "bridge-flow-ratio=f"; def->min = 0; - def->default_value = new ConfigOptionFloat(1); + def->max = 2; + def->default_value = new ConfigOptionFloat(1); def = this->add("bridge_speed", coFloat); def->label = L("Bridges"); diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 5cae9172ef..a5a14d368a 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -82,10 +82,8 @@ namespace Slic3r { namespace GUI { return std::regex_match(string, regex_pattern); } -// boost::any Field::get_value_by_opt_type(wxString& str) void Field::get_value_by_opt_type(wxString& str) { -// boost::any m_value; switch (m_opt.type){ case coInt: m_value = wxAtoi(str); @@ -96,9 +94,24 @@ namespace Slic3r { namespace GUI { case coFloat:{ if (m_opt.type == coPercent && str.Last() == '%') str.RemoveLast(); + else if (str.Last() == '%') { + show_error(m_parent, _(L("Current option doesn't support percentage"))); + set_value(double_to_string(m_opt.min), true); + m_value = double(m_opt.min); + break; + } double val; str.ToCDouble(&val); - m_value = val; + if (m_opt.min <= val && val <= m_opt.max) + m_value = val; + else + { + show_error(m_parent, _(L("Input value is out of range"))); + if (m_opt.min > val) val = m_opt.min; + if (val > m_opt.max) val = m_opt.max; + set_value(double_to_string(val), true); + m_value = val; + } break; } case coString: case coStrings: @@ -108,8 +121,6 @@ namespace Slic3r { namespace GUI { default: break; } - -// return m_value; } void TextCtrl::BUILD() { @@ -188,9 +199,9 @@ namespace Slic3r { namespace GUI { boost::any& TextCtrl::get_value() { wxString ret_str = static_cast(window)->GetValue(); - /*boost::any ret_val*/get_value_by_opt_type(ret_str); + get_value_by_opt_type(ret_str); - return m_value;//ret_val; + return m_value; } void TextCtrl::enable() { dynamic_cast(window)->Enable(); dynamic_cast(window)->SetEditable(true); } diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 729f73d160..7e421244bb 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -124,7 +124,6 @@ public: virtual wxWindow* getWindow() { return nullptr; } bool is_matched(const std::string& string, const std::string& pattern); -// boost::any get_value_by_opt_type(wxString& str); void get_value_by_opt_type(wxString& str); /// Factory method for generating new derived classes. From 98d9ce31de65f70111c490a883b33408d0f30124 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 May 2018 12:54:02 +0200 Subject: [PATCH 137/145] Corrected error message to last commit --- xs/src/slic3r/GUI/Field.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index a5a14d368a..623e20b45c 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -95,23 +95,25 @@ namespace Slic3r { namespace GUI { if (m_opt.type == coPercent && str.Last() == '%') str.RemoveLast(); else if (str.Last() == '%') { - show_error(m_parent, _(L("Current option doesn't support percentage"))); + wxString label = m_Label->GetLabel(); + if (label.Last() == '\n') label.RemoveLast(); + while (label.Last() == ' ') label.RemoveLast(); + if (label.Last() == ':') label.RemoveLast(); + show_error(m_parent, wxString::Format(_(L("%s doesn't support percentage")), label)); set_value(double_to_string(m_opt.min), true); m_value = double(m_opt.min); break; } double val; str.ToCDouble(&val); - if (m_opt.min <= val && val <= m_opt.max) - m_value = val; - else + if (m_opt.min > val && val > m_opt.max) { show_error(m_parent, _(L("Input value is out of range"))); if (m_opt.min > val) val = m_opt.min; if (val > m_opt.max) val = m_opt.max; set_value(double_to_string(val), true); - m_value = val; } + m_value = val; break; } case coString: case coStrings: From 5e231bf8749c0eb54cd3ffc71f5d172d7c8a9c65 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 10 May 2018 17:44:04 +0200 Subject: [PATCH 138/145] New wipe tower parameters added to INI file with profiles --- resources/profiles/PrusaResearch.ini | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index d82921fd8d..87950bae79 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -121,7 +121,8 @@ top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 travel_speed = 180 wipe_tower = 1 -wipe_tower_per_color_wipe = 20 +wipe_tower_bridging = 10 +wipe_tower_rotation_angle = 0 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 @@ -538,6 +539,10 @@ compatible_printers = compatible_printers_condition = end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 +filament_loading_speed = 28 +filament_unloading_speed = 90 +filament_toolchange_delay = 0 +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" filament_cost = 0 filament_density = 0 filament_diameter = 1.75 @@ -591,6 +596,7 @@ fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_max_volumetric_speed = 11 +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 @@ -763,6 +769,7 @@ fan_always_on = 0 filament_colour = #FFFFD7 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" +filament_ramming_parameters = "120 100 8.3871 8.6129 8.93548 9.22581 9.48387 9.70968 9.87097 10.0323 10.2258 10.4194 10.6452 10.8065| 0.05 8.34193 0.45 8.73548 0.95 9.34836 1.45 9.78385 1.95 10.0871 2.45 10.5161 2.95 10.8903 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" filament_soluble = 1 filament_type = PVA first_layer_temperature = 195 @@ -933,6 +940,9 @@ retract_restart_extra_toolchange = 0 retract_speed = 80 single_extruder_multi_material = 1 printer_model = MK2SMM +parking_pos_retraction = 92 +cooling_tube_length = 5 +cooling_tube_retraction = 91.5 [printer:*mm-single*] inherits = *multimaterial* From 9af6a89f20785feab4d068977957bee9ee239cad Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 10 May 2018 18:07:22 +0200 Subject: [PATCH 139/145] Fixed a crash when loading multipart objects --- lib/Slic3r/GUI/Plater.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 0a5d02b944..08c1065df7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -700,7 +700,7 @@ sub load_files { . "Instead of considering them as multiple objects, should I consider\n" . "this file as a single object having multiple parts?\n"), L('Multi-part object detected'), wxICON_WARNING | wxYES | wxNO); - $model->convert_multipart_object if $dialog->ShowModal(scalar(@$nozzle_dmrs)) == wxID_YES; + $model->convert_multipart_object(scalar(@$nozzle_dmrs)) if $dialog->ShowModal() == wxID_YES; } if ($one_by_one) { @@ -717,7 +717,7 @@ sub load_files { . "Instead of considering them as multiple objects, should I consider\n" . "these files to represent a single object having multiple parts?\n"), L('Multi-part object detected'), wxICON_WARNING | wxYES | wxNO); - $new_model->convert_multipart_object if $dialog->ShowModal(scalar(@$nozzle_dmrs)) == wxID_YES; + $new_model->convert_multipart_object(scalar(@$nozzle_dmrs)) if $dialog->ShowModal() == wxID_YES; push @obj_idx, $self->load_model_objects(@{$new_model->objects}); } From d620961c34ceb8ca747ab675042abb3abf8c341a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 10 May 2018 18:37:57 +0200 Subject: [PATCH 140/145] Fixed the version number. We have an alpha state as of today. --- xs/src/libslic3r/libslic3r.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 4aef4d5c16..e7b858e8b8 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.40.0" +#define SLIC3R_VERSION "1.40.0-alpha" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; From e91ebddeea4da1f6062546ee354573119c22f89b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 11 May 2018 09:53:35 +0200 Subject: [PATCH 141/145] Added possibility of select all TextCtrl context using Ctrl+A --- xs/src/slic3r/GUI/Field.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 623e20b45c..0fcf480e31 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -194,6 +194,14 @@ namespace Slic3r { namespace GUI { temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent) { on_change_field(); }), temp->GetId()); + // select all text using Ctrl+A + temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) + { + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + temp->SetSelection(-1, -1); //select all + event.Skip(); + })); + // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); } From d89f8128cde85f45beac212847ee0f578eab26ed Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 11 May 2018 12:28:31 +0200 Subject: [PATCH 142/145] Fixed a GCode preview crash on Linux due to returning a reference to temporary (issue #872) --- xs/src/libslic3r/GCode/PreviewData.cpp | 2 +- xs/src/libslic3r/GCode/PreviewData.hpp | 2 +- xs/src/slic3r/GUI/3DScene.cpp | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/xs/src/libslic3r/GCode/PreviewData.cpp b/xs/src/libslic3r/GCode/PreviewData.cpp index d431708c1b..40f0747b28 100644 --- a/xs/src/libslic3r/GCode/PreviewData.cpp +++ b/xs/src/libslic3r/GCode/PreviewData.cpp @@ -275,7 +275,7 @@ bool GCodePreviewData::empty() const return extrusion.layers.empty() && travel.polylines.empty() && retraction.positions.empty() && unretraction.positions.empty(); } -const GCodePreviewData::Color& GCodePreviewData::get_extrusion_role_color(ExtrusionRole role) const +GCodePreviewData::Color GCodePreviewData::get_extrusion_role_color(ExtrusionRole role) const { return extrusion.role_colors[role]; } diff --git a/xs/src/libslic3r/GCode/PreviewData.hpp b/xs/src/libslic3r/GCode/PreviewData.hpp index a7d77e0b93..ea8ca6d58a 100644 --- a/xs/src/libslic3r/GCode/PreviewData.hpp +++ b/xs/src/libslic3r/GCode/PreviewData.hpp @@ -187,7 +187,7 @@ public: void reset(); bool empty() const; - const Color& get_extrusion_role_color(ExtrusionRole role) const; + Color get_extrusion_role_color(ExtrusionRole role) const; Color get_height_color(float height) const; Color get_width_color(float width) const; Color get_feedrate_color(float feedrate) const; diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 6b2f5c8309..85018404c9 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -2204,7 +2204,7 @@ void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, return 0.0f; } - static const GCodePreviewData::Color& path_color(const GCodePreviewData& data, const std::vector& tool_colors, float value) + static const GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector& tool_colors, float value) { switch (data.extrusion.view_type) { @@ -2257,7 +2257,6 @@ void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, }; typedef std::vector FiltersList; - size_t initial_volumes_count = volumes.volumes.size(); // detects filters @@ -2281,7 +2280,6 @@ void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, for (Filter& filter : filters) { s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)volumes.volumes.size()); - GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba); if (volume != nullptr) { From 8c4c392cb8a47031402c215c31b3d67eafa5ad49 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 11 May 2018 14:41:21 +0200 Subject: [PATCH 143/145] Fixed enable/disable of the tooltips on GTK --- xs/src/slic3r/GUI/Field.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 0fcf480e31..4febae3621 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -183,14 +183,21 @@ namespace Slic3r { namespace GUI { //! to allow the default handling event.Skip(); //! eliminating the g-code pop up text description - temp->GetToolTip()->Enable(false); + bool flag = false; +#ifdef __WXGTK__ + // I have no idea why, but on GTK flag works in other way + flag = true; +#endif // __WXGTK__ + temp->GetToolTip()->Enable(flag); }), temp->GetId()); +#if !defined(__WXGTK__) temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e) { e.Skip();// on_kill_focus(e); temp->GetToolTip()->Enable(true); }), temp->GetId()); +#endif // __WXGTK__ temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent) { on_change_field(); }), temp->GetId()); From 613bcf80555ba5982797e31bed7bad9407ea8ae4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 11 May 2018 15:41:26 +0200 Subject: [PATCH 144/145] Updated PrusaResearch.ini with the latest version from Slic3r-settings repository --- resources/profiles/PrusaResearch.idx | 2 ++ resources/profiles/PrusaResearch.ini | 23 +++++++++-------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index f07d8b2806..e3b1e6ef54 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1 +1,3 @@ +0.1.2 Wipe tower changes +0.1.1 Minor print speed adjustments 0.1.0 Initial diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 87950bae79..ba8e55ad41 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.1.0 +config_version = 0.1.2 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -90,7 +90,7 @@ seam_position = nearest skirts = 1 skirt_distance = 2 skirt_height = 3 -small_perimeter_speed = 20 +small_perimeter_speed = 25 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 @@ -180,7 +180,7 @@ gap_fill_speed = 20 infill_acceleration = 800 infill_speed = 30 max_print_speed = 80 -small_perimeter_speed = 15 +small_perimeter_speed = 20 solid_infill_speed = 30 support_material_extrusion_width = 0.3 support_material_spacing = 1.5 @@ -215,7 +215,7 @@ infill_speed = 20 max_print_speed = 100 perimeter_extrusion_width = 0 perimeter_speed = 20 -small_perimeter_speed = 10 +small_perimeter_speed = 15 solid_infill_extrusion_width = 0 solid_infill_speed = 20 support_material_speed = 20 @@ -268,7 +268,7 @@ infill_acceleration = 1600 infill_speed = 40 perimeter_acceleration = 600 perimeter_speed = 25 -small_perimeter_speed = 10 +small_perimeter_speed = 15 solid_infill_speed = 40 top_solid_infill_speed = 30 @@ -341,7 +341,7 @@ infill_acceleration = 1600 infill_speed = 40 perimeter_acceleration = 600 perimeter_speed = 25 -small_perimeter_speed = 10 +small_perimeter_speed = 15 solid_infill_speed = 40 support_material_extrusion_width = 0.2 top_solid_infill_speed = 30 @@ -549,7 +549,7 @@ filament_diameter = 1.75 filament_notes = "" filament_settings_id = filament_soluble = 0 -min_print_speed = 5 +min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" @@ -643,7 +643,6 @@ first_layer_bed_temperature = 105 first_layer_temperature = 270 max_fan_speed = 20 min_fan_speed = 10 -min_print_speed = 5 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 @@ -657,7 +656,6 @@ extrusion_multiplier = 1.2 filament_colour = #804040 filament_max_volumetric_speed = 10 first_layer_temperature = 200 -min_print_speed = 5 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 200 @@ -823,7 +821,6 @@ first_layer_bed_temperature = 60 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 -min_print_speed = 5 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 250 @@ -856,7 +853,6 @@ first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 -min_print_speed = 15 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 @@ -877,7 +873,6 @@ first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 100 min_fan_speed = 100 -min_print_speed = 15 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 @@ -938,11 +933,11 @@ retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 80 -single_extruder_multi_material = 1 -printer_model = MK2SMM parking_pos_retraction = 92 cooling_tube_length = 5 cooling_tube_retraction = 91.5 +single_extruder_multi_material = 1 +printer_model = MK2SMM [printer:*mm-single*] inherits = *multimaterial* From ae1a015494b0ac1d50f7931b71f09ba560e3a984 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 11 May 2018 16:03:07 +0200 Subject: [PATCH 145/145] Fixed Perl array addressing issues, leading to crashes when loading additional objects at the 3D path preview window. --- lib/Slic3r/GUI/Plater/3D.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index ced6fb82ea..c9c9542762 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -32,7 +32,7 @@ sub new { $self->{on_instances_moved} = sub {}; $self->{on_wipe_tower_moved} = sub {}; - $self->{objects_volumes_idxs} = (); + $self->{objects_volumes_idxs} = []; $self->on_select(sub { my ($volume_idx) = @_; @@ -188,8 +188,8 @@ sub update_volumes_selection { foreach my $obj_idx (0..$#{$self->{model}->objects}) { if ($self->{objects}[$obj_idx]->selected) { - my @volume_idxs = @{$self->{objects_volumes_idxs}[$obj_idx]}; - $self->select_volume($_) for @volume_idxs; + my $volume_idxs = $self->{objects_volumes_idxs}->[$obj_idx]; + $self->select_volume($_) for @{$volume_idxs}; } } } @@ -207,7 +207,7 @@ sub reload_scene { $self->{reload_delayed} = 0; - $self->{objects_volumes_idxs} = (); + $self->{objects_volumes_idxs} = []; foreach my $obj_idx (0..$#{$self->{model}->objects}) { my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx); push(@{$self->{objects_volumes_idxs}}, \@volume_idxs);