diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 4ec388c14..0c5e87429 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 b2f51b9e1..68fc96339 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 000000000..925447cf2 Binary files /dev/null and b/resources/icons/printers/MK2S.png differ diff --git a/resources/icons/printers/MK2SMM.png b/resources/icons/printers/MK2SMM.png new file mode 100644 index 000000000..d6ff16125 Binary files /dev/null and b/resources/icons/printers/MK2SMM.png differ diff --git a/resources/icons/printers/MK3.png b/resources/icons/printers/MK3.png new file mode 100644 index 000000000..5279ba01e Binary files /dev/null and b/resources/icons/printers/MK3.png differ diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index fa4d48cda..0a1d26eaa 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 f28db8f92..e73c8429a 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 27e7fad6b..7e2fe3180 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 e32b645b4..4bdf88dda 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 000000000..3cad789b6 --- /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 000000000..1b14e29be --- /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 000000000..ba028c0e8 --- /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 3eca4e707..53288067c 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 362b15307..1f93e18e9 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 0009b17ec..6731cd394 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 3634c5dd9..78e15bada 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 7131bf771..241f0433e 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 f481ba374..4949e0e03 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 000000000..0e4c62af9 --- /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 000000000..431134097 --- /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 1376ff164..5115eda64 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 000000000..3596b7c86 --- /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 87a8d8d86..d817af052 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