From 3fcf194e39e93d36bc164acffbf7f4ad2c67fb6f Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Tue, 13 Mar 2018 12:39:57 +0100
Subject: [PATCH 01/97] 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 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 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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

literal 0
HcmV?d00001

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 <locale>
 
+#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 <iostream>   // XXX
+#include <algorithm>
+#include <utility>
+#include <boost/filesystem.hpp>
+
+#include <wx/settings.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/dcclient.h>
+#include <wx/statbmp.h>
+#include <wx/checkbox.h>
+#include <wx/statline.h>
+
+#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 <memory>
+
+#include <wx/dialog.h>
+
+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<priv> 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 <vector>
+
+#include <wx/sizer.h>
+#include <wx/panel.h>
+#include <wx/button.h>
+
+
+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<wxString> items;
+	std::vector<wxString>::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<wxWindow*>(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<bool>(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<PrinterVariant> variants;
@@ -51,11 +51,10 @@ public:
             return nullptr;
         }
         const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(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<PrinterModel>          models;
+    std::vector<PrinterModel>          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 <algorithm>
 #include <fstream>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/clamp.hpp>
@@ -104,6 +105,7 @@ void PresetBundle::setup_directories()
     std::initializer_list<boost::filesystem::path> 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 &section : tree)
+    for (auto &section : 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<std::string>("name", model.id);
             section.second.get<std::string>("variants", "");
             std::vector<std::string> variants;
             if (Slic3r::unescape_strings_cstyle(section.second.get<std::string>("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<std::string> 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 <iostream>  // XXX
+#include <thread>
+#include <boost/filesystem/path.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+#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<PresetUpdater>(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 <memory>
+
+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<priv> 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 <xsinit.h>
+#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<PresetHints>    		O_OBJECT_SLIC3R_T
 TabIface*	   				O_OBJECT_SLIC3R
 Ref<TabIface> 				O_OBJECT_SLIC3R_T
+# TODO: remove:
+# ConfigWizard*               O_OBJECT_SLIC3R
+# Ref<ConfigWizard>           O_OBJECT_SLIC3R_T
 
 OctoPrint*                  O_OBJECT_SLIC3R
 Ref<OctoPrint>              O_OBJECT_SLIC3R_T

From 57e47a3296c94e9ed879e5bf281aef69f1942409 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Wed, 28 Mar 2018 11:36:36 +0200
Subject: [PATCH 02/97] 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 0c5e87429..495dd9ecc 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 e73c8429a..3ce499d2b 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 d7c9a590a..9706ced2c 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 4bdf88dda..e951beb44 100644
--- a/xs/src/slic3r/GUI/AppConfig.cpp
+++ b/xs/src/slic3r/GUI/AppConfig.cpp
@@ -1,5 +1,3 @@
-#include <GL/glew.h>
-
 #include "../../libslic3r/libslic3r.h"
 #include "../../libslic3r/Utils.hpp"
 #include "AppConfig.hpp"
@@ -9,15 +7,20 @@
 #include <string.h>
 #include <utility>
 #include <assert.h>
+#include <vector>
 
 #include <boost/filesystem.hpp>
 #include <boost/nowide/cenv.hpp>
 #include <boost/nowide/fstream.hpp>
 #include <boost/property_tree/ini_parser.hpp>
 #include <boost/property_tree/ptree.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
 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<std::string> 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<std::string, std::string> &storage = m_storage[section.first];
@@ -96,10 +114,53 @@ void AppConfig::save()
     	for (const std::pair<std::string, std::string> &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<std::string> 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 9b1d5a712..4ccd51304 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 <set>
 #include <map>
 #include <string>
 
+#include "libslic3r/Config.hpp"
+
 namespace Slic3r {
 
 class AppConfig
@@ -65,6 +68,13 @@ public:
 	void 				clear_section(const std::string &section)
 		{ m_storage[section].clear(); }
 
+	// TODO: remove / upgrade
+	// ConfigOptionStrings get_strings(const std::string &section, const std::string &key) const;
+	// void set_strings(const std::string &section, 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<std::string, std::map<std::string, std::string>> 	m_storage;
+	// Map of enabled vendors / models / variants
+	std::map<std::string, std::map<std::string, std::set<std::string>>> 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 0e4c62af9..000000000
--- a/xs/src/slic3r/Utils/PresetUpdate.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-#include "PresetUpdate.hpp"
-
-#include <iostream>  // XXX
-#include <thread>
-#include <boost/filesystem/path.hpp>
-#include <boost/filesystem/fstream.hpp>
-
-#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<PresetUpdater>(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 000000000..11064d2f0
--- /dev/null
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -0,0 +1,137 @@
+#include "PresetUpdater.hpp"
+
+#include <iostream>  // XXX
+#include <thread>
+#include <boost/algorithm/string.hpp>
+#include <boost/filesystem/path.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+#include <wx/app.h>
+#include <wx/event.h>
+
+#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<VendorProfile> 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<VendorProfile> 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<VendorProfile> 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 431134097..aafe9569b 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<priv> 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 08a88883d..de0e5a22b 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 3596b7c86..000000000
--- a/xs/xsp/Utils_PresetUpdate.xsp
+++ /dev/null
@@ -1,18 +0,0 @@
-%module{Slic3r::XS};
-
-%{
-#include <xsinit.h>
-#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 000000000..666379f02
--- /dev/null
+++ b/xs/xsp/Utils_PresetUpdater.xsp
@@ -0,0 +1,11 @@
+%module{Slic3r::XS};
+
+%{
+#include <xsinit.h>
+#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 d817af052..9c0f60c1d 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -239,6 +239,10 @@ Ref<TabIface> 				O_OBJECT_SLIC3R_T
 # ConfigWizard*               O_OBJECT_SLIC3R
 # Ref<ConfigWizard>           O_OBJECT_SLIC3R_T
 
+PresetUpdater*              O_OBJECT_SLIC3R
+Ref<PresetUpdater>          O_OBJECT_SLIC3R_T
+Clone<PresetUpdater>        O_OBJECT_SLIC3R_T
+
 OctoPrint*                  O_OBJECT_SLIC3R
 Ref<OctoPrint>              O_OBJECT_SLIC3R_T
 Clone<OctoPrint>            O_OBJECT_SLIC3R_T

From e53949f2c85e06adaf7be9a96873844a8862eab6 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Thu, 29 Mar 2018 17:54:43 +0200
Subject: [PATCH 03/97] 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 495dd9ecc..ed2f6dfc7 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 68fc96339..4307375c8 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 0a1d26eaa..6dc29a96e 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 e951beb44..10a586e27 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 4ccd51304..e43ff51bf 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 &section)
 		{ m_storage[section].clear(); }
 
-	// TODO: remove / upgrade
-	// ConfigOptionStrings get_strings(const std::string &section, const std::string &key) const;
-	// void set_strings(const std::string &section, 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 3cad789b6..556b91a46 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 1b14e29be..a06388396 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<priv> p;
diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp
index ba028c0e8..ab291d40f 100644
--- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp
+++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp
@@ -9,6 +9,9 @@
 #include <wx/panel.h>
 #include <wx/button.h>
 
+#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<class T>
+	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 53288067c..80e232287 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<wxWindow*>(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 1f93e18e9..98a124091 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 c437f8b41..40afca144 100644
--- a/xs/src/slic3r/GUI/Preset.cpp
+++ b/xs/src/slic3r/GUI/Preset.cpp
@@ -2,6 +2,7 @@
 #include <cassert>
 
 #include "Preset.hpp"
+#include "AppConfig.hpp"
 
 #include <fstream>
 #include <boost/filesystem.hpp>
@@ -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<std::string>& Preset::print_options()
 {    
     static std::vector<std::string> s_opts {
diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp
index 78e15bada..075eed9af 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<PrinterVariant> 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<std::string> &keys);
     ~PresetCollection();
 
+    typedef std::deque<Preset>::iterator Iterator;
+    typedef std::deque<Preset>::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 241f0433e..8645a4f2d 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 <iostream>    // XXX
 #include <algorithm>
 #include <fstream>
 #include <boost/filesystem.hpp>
@@ -105,7 +106,7 @@ void PresetBundle::setup_directories()
     std::initializer_list<boost::filesystem::path> 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 4949e0e03..4189e6c46 100644
--- a/xs/src/slic3r/GUI/PresetBundle.hpp
+++ b/xs/src/slic3r/GUI/PresetBundle.hpp
@@ -5,6 +5,7 @@
 #include "Preset.hpp"
 
 #include <set>
+#include <boost/filesystem/path.hpp>
 
 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 11064d2f0..040d326b5 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 5115eda64..ad7f69a2d 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 84efdde53..1187a1cf5 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 <vojtech@kral.hk>
Date: Thu, 5 Apr 2018 16:10:44 +0200
Subject: [PATCH 04/97] 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 6eb307c5c..06db9efef 100644
--- a/xs/src/libslic3r/Config.hpp
+++ b/xs/src/libslic3r/Config.hpp
@@ -659,6 +659,7 @@ public:
     ConfigOptionPoints() : ConfigOptionVector<Pointf>() {}
     explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector<Pointf>(n, value) {}
     explicit ConfigOptionPoints(std::initializer_list<Pointf> il) : ConfigOptionVector<Pointf>(std::move(il)) {}
+    explicit ConfigOptionPoints(const std::vector<Pointf> &values) : ConfigOptionVector<Pointf>(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 657e5a452..5795f044b 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 556b91a46..edf958bd8 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<GCodeFlavor> 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<ConfigOptionPoints>("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<const ConfigOptionFloats*>(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<const ConfigOptionFloats*>(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<const ConfigOptionInts*>(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<const ConfigOptionInts*>(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<DynamicPrintConfig> 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 ab291d40f..1d9519bc3 100644
--- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp
+++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp
@@ -8,9 +8,13 @@
 #include <wx/sizer.h>
 #include <wx/panel.h>
 #include <wx/button.h>
+#include <wx/choice.h>
+#include <wx/spinctrl.h>
 
+#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<class T>
-	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<wxString> items;
 	std::vector<wxString>::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 <vojtech@kral.hk>
Date: Thu, 5 Apr 2018 18:30:03 +0200
Subject: [PATCH 05/97] 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 edf958bd8..914ebb9a1 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 1d9519bc3..9f9395975 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<PrinterPicker*> 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 <vojtech@kral.hk>
Date: Fri, 6 Apr 2018 12:15:28 +0200
Subject: [PATCH 06/97] 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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
Pqd{aPl_aXfOoIOhw-Mb-

literal 0
HcmV?d00001

diff --git a/resources/icons/printers/BarBaz_M2.png b/resources/icons/printers/BarBaz_M2.png
new file mode 100644
index 0000000000000000000000000000000000000000..5924cc88b9ac373d5dc802ac06b82d1ad9d5015c
GIT binary patch
literal 10369
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
Pqd{aPl_aXfOoIOhw-Mb-

literal 0
HcmV?d00001

diff --git a/resources/icons/printers/BarBaz_M3.png b/resources/icons/printers/BarBaz_M3.png
new file mode 100644
index 0000000000000000000000000000000000000000..5924cc88b9ac373d5dc802ac06b82d1ad9d5015c
GIT binary patch
literal 10369
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
Pqd{aPl_aXfOoIOhw-Mb-

literal 0
HcmV?d00001

diff --git a/resources/icons/printers/Foobar_M1.png b/resources/icons/printers/Foobar_M1.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_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 000000000..83bb15684
--- /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 000000000..21681398a
--- /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 10a586e27..9e5ce5f1b 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 e43ff51bf..7aac95fd6 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 &section)
 		{ 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 914ebb9a1..ce51d7641 100644
--- a/xs/src/slic3r/GUI/ConfigWizard.cpp
+++ b/xs/src/slic3r/GUI/ConfigWizard.cpp
@@ -1,6 +1,5 @@
 #include "ConfigWizard_private.hpp"
 
-#include <iostream>   // XXX
 #include <algorithm>
 #include <utility>
 #include <boost/filesystem.hpp>
@@ -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<ConfigOptionPoints>("bed_shape"));
+	shape_panel->build_panel(wizard_p()->custom_config->option<ConfigOptionPoints>("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<DynamicPrintConfig> 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 9f9395975..d32a609be 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<DynamicPrintConfig> 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 9c0f60c1d..c1ca58827 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -235,9 +235,6 @@ PresetHints*	    		O_OBJECT_SLIC3R
 Ref<PresetHints>    		O_OBJECT_SLIC3R_T
 TabIface*	   				O_OBJECT_SLIC3R
 Ref<TabIface> 				O_OBJECT_SLIC3R_T
-# TODO: remove:
-# ConfigWizard*               O_OBJECT_SLIC3R
-# Ref<ConfigWizard>           O_OBJECT_SLIC3R_T
 
 PresetUpdater*              O_OBJECT_SLIC3R
 Ref<PresetUpdater>          O_OBJECT_SLIC3R_T

From 90a8ef8e9f208dce144cea4252173079fd0f830c Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Fri, 6 Apr 2018 13:18:12 +0200
Subject: [PATCH 07/97] 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 4307375c8..402cb8051 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 6dc29a96e..b654b4039 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 ce51d7641..032b5e903 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<const ConfigOptionInts*>(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<const ConfigOptionInts*>(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 a06388396..40ecf09a1 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<priv> p;
diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp
index d32a609be..652328aaa 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 80e232287..310860e53 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 98a124091..321c97d36 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 40afca144..66836074e 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<std::string>& Preset::print_options()
diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp
index 075eed9af..4f734b85e 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<PrinterVariant> 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 8645a4f2d..bd9e35ca8 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 <iostream>    // XXX
 #include <algorithm>
 #include <fstream>
 #include <boost/filesystem.hpp>
@@ -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 ad7f69a2d..fcf465a29 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 <vojtech@kral.hk>
Date: Mon, 9 Apr 2018 10:41:34 +0200
Subject: [PATCH 08/97] 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 032b5e903..f13448c37 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 <vojtech@kral.hk>
Date: Mon, 9 Apr 2018 16:24:34 +0200
Subject: [PATCH 09/97] 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 c5d68400d..6d788cf34 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 <wx/dcbuffer.h>
 #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 <vojtech@kral.hk>
Date: Mon, 9 Apr 2018 16:39:50 +0200
Subject: [PATCH 10/97] 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 34b9eaa9f..14736468a 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 <bubnikv@gmail.com>
Date: Mon, 9 Apr 2018 17:03:37 +0200
Subject: [PATCH 11/97] 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 4ec388c14..88e4745d1 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 0879ea35b..000000000
--- 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 =
-        '<html>' .
-        '<body bgcolor="#ffffff" link="#808080">' .
-        '<font color="#808080">' .
-        'Copyright &copy; 2016 Vojtech Bubnik, Prusa Research. <br />' .
-        'Copyright &copy; 2011-2016 Alessandro Ranellucci. <br />' .
-        '<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
-        '<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
-        '<br /><br /><br />' .
-        '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. <br />' .
-        'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> designed by Mark James. ' .
-        '</font>' .
-        '</body>' .
-        '</html>';
-    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 b2f51b9e1..31124e432 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 84f169e57..b9a6dbfee 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 47a584343..06eb041df 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 82a6b328e..3f560fa4f 100644
--- a/xs/src/libslic3r/FileParserError.hpp
+++ b/xs/src/libslic3r/FileParserError.hpp
@@ -4,6 +4,7 @@
 #include "libslic3r.h"
 
 #include <string>
+#include <boost/filesystem/path.hpp>
 #include <stdexcept>
 
 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 559e4c63c..91f02ab25 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> &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> 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 358797bf7..1d02c8650 100644
--- a/xs/src/slic3r/Config/Snapshot.hpp
+++ b/xs/src/slic3r/Config/Snapshot.hpp
@@ -6,6 +6,7 @@
 
 #include <boost/filesystem.hpp>
 
+#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<Snapshot>::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> &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 1102f3149..cc961829d 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> 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> 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 7af1d4b5b..43512e82f 100644
--- a/xs/src/slic3r/Config/Version.hpp
+++ b/xs/src/slic3r/Config/Version.hpp
@@ -4,6 +4,8 @@
 #include <string>
 #include <vector>
 
+#include <boost/filesystem.hpp>
+
 #include "../../libslic3r/FileParserError.hpp"
 #include "../Utils/Semver.hpp"
 
@@ -54,17 +56,25 @@ public:
 	typedef std::vector<Version>::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<Version>& 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<Index>	load_db();
+
 private:
+	std::string 				m_vendor;
 	std::vector<Version>		m_configs;
 };
 
diff --git a/xs/src/slic3r/GUI/AboutDialog.cpp b/xs/src/slic3r/GUI/AboutDialog.cpp
new file mode 100644
index 000000000..49cfff2bd
--- /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 =
+            "<html>"
+            "<body bgcolor=\"#ffffff\" link=\"#808080\">"
+            "<font color=\"#808080\">"
+            "Copyright &copy; 2016-2018 Prusa Research. <br />"
+            "Copyright &copy; 2011-2017 Alessandro Ranellucci. <br />"
+            "<a href=\"http://slic3r.org/\">Slic3r</a> is licensed under the "
+            "<a href=\"http://www.gnu.org/licenses/agpl-3.0.html\">GNU Affero General Public License, version 3</a>."
+            "<br /><br /><br />"
+            "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. <br />"
+            "Slic3r logo designed by Corey Daniels, <a href=\"http://www.famfamfam.com/lab/icons/silk/\">Silk Icon Set</a> designed by Mark James. "
+            "</font>"
+            "</body>"
+            "</html>";
+        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 000000000..01f7564c5
--- /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 <wx/wx.h>
+#include <wx/intl.h>
+#include <wx/html/htmlwin.h>
+
+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 3eca4e707..48d56ff11 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 362b15307..0cbdf8729 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 1376ff164..c8d76adba 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 <bubnikv@gmail.com>
Date: Tue, 10 Apr 2018 16:27:42 +0200
Subject: [PATCH 12/97] 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 b9a6dbfee..e01f11b26 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 91f02ab25..33c7ef82f 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 1d02c8650..3c7754273 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 000000000..a4e4d846b
--- /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 = "<tr bgcolor=\"";
+    text += row_even ? "#FFFFFF" : "#C0C0C0";
+    text += "\">";
+    text += "<td>";
+//    text += _(L("ID:")) + " " + snapshot.id + "<br>";
+    text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "<br>";
+    text += _(L("slic3r version:")) + " " + snapshot.slic3r_version_captured.to_string() + "<br>";
+    if (! snapshot.comment.empty())
+        text += _(L("user comment:")) + " " + snapshot.comment + "<br>";
+//    text += "reason: " + snapshot.reason + "<br>";
+    text += "print: " + snapshot.print + "<br>";
+    text += "filaments: " + snapshot.filaments.front() + "<br>";
+    text += "printer: " + snapshot.printer + "<br>";
+
+    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() + "<br>";
+    }
+
+    text += "<p align=\"right\"><a href=\"" + snapshot.id + "\">Activate</a></p>";
+    text += "</td>";
+	text += "</tr>";
+    return text;
+}
+
+static std::string generate_html_page(const Config::SnapshotDB &snapshot_db)
+{
+    std::string text = 
+        "<html>"
+        "<body bgcolor=\"#ffffff\" cellspacing=\"2\" cellpadding=\"0\" border=\"0\" link=\"#800000\">"
+        "<font color=\"#000000\">";
+    text += "<table style=\"width:100%\">";
+    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 +=
+        "</table>"
+        "</font>"
+        "</body>"
+        "</html>";
+    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 000000000..70187ee99
--- /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 <wx/wx.h>
+#include <wx/intl.h>
+#include <wx/html/htmlwin.h>
+
+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 48d56ff11..0a163bf28 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 7fc3b8033..8aa8cc53f 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 c4123c7bb..a2b2328af 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 <windows.h>
+	#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 6b2fbf893..7b670bd3e 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 <bubnikv@gmail.com>
Date: Wed, 11 Apr 2018 12:21:15 +0200
Subject: [PATCH 13/97] 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 bb56fa886..b28b84df3 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 6aa7b34cb..f7d90c796 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 e5b27d1dc..886c8f419 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 b654b4039..cf82855cf 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 33c7ef82f..9fabbe013 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 <time.h>
@@ -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<std::string, std::map<std::string, std::set<std::string>>> 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<std::string, std::map<std::string, std::set<std::string>>> &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 3c7754273..a7b8a5aa5 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 <map>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -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<std::string, std::set<std::string>> models_variants_installed;
 	};
 	// List of vendor configs contained in this snapshot.
 	std::vector<VendorConfig> 	vendor_configs;
diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp
index 7aac95fd6..40b3a12fd 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<std::string, std::map<std::string, std::set<std::string>>> &vendors) { m_vendors = vendors; m_dirty = true; }
+	void 				set_vendors(std::map<std::string, std::map<std::string, std::set<std::string>>> &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
+	const std::map<std::string, std::map<std::string, std::set<std::string>>> 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 a4e4d846b..730b97a32 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 + "<br>";
 
     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() + "<br>";
+        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 += "<br>";
+        for (const std::pair<std::string, std::set<std::string>> &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 += "<br>";
+        }
     }
 
     text += "<p align=\"right\"><a href=\"" + snapshot.id + "\">Activate</a></p>";
@@ -43,7 +55,7 @@ static std::string generate_html_page(const Config::SnapshotDB &snapshot_db)
         "<font color=\"#000000\">";
     text += "<table style=\"width:100%\">";
     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 70187ee99..0d1109615 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 dcb21f644..cef56b892 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 938eed498..2a3667eb3 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 babff7d80..940987536 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<const ConfigOptionStrings*>(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<std::string> 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 8dba13caf..964f350b9 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 <bubnikv@gmail.com>
Date: Wed, 11 Apr 2018 15:17:41 +0200
Subject: [PATCH 14/97] 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 000000000..28f17f10a
--- /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 29bc1868d..599217f89 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 9fabbe013..eeb5b6ac5 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 &section : 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<std::string> 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<std::string> 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 cc961829d..95b3caf1a 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<char*>(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> 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 8aa8cc53f..bd8e9b758 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 <vojtech@kral.hk>
Date: Wed, 11 Apr 2018 13:12:08 +0200
Subject: [PATCH 15/97] 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 eeb5b6ac5..b6c356576 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 9e5ce5f1b..ee77f877a 100644
--- a/xs/src/slic3r/GUI/AppConfig.cpp
+++ b/xs/src/slic3r/GUI/AppConfig.cpp
@@ -8,6 +8,7 @@
 #include <utility>
 #include <assert.h>
 #include <vector>
+#include <stdexcept>
 
 #include <boost/filesystem.hpp>
 #include <boost/nowide/cenv.hpp>
@@ -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 40b3a12fd..cac2759f1 100644
--- a/xs/src/slic3r/GUI/AppConfig.hpp
+++ b/xs/src/slic3r/GUI/AppConfig.hpp
@@ -6,6 +6,7 @@
 #include <string>
 
 #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 f13448c37..0c2a9dd59 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 66836074e..39bfbd398 100644
--- a/xs/src/slic3r/GUI/Preset.cpp
+++ b/xs/src/slic3r/GUI/Preset.cpp
@@ -6,6 +6,7 @@
 
 #include <fstream>
 #include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 
 #include <boost/nowide/cenv.hpp>
@@ -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 &section : 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<std::string>("name", model.id);
+            section.second.get<std::string>("variants", "");
+            std::vector<std::string> variants;
+            if (Slic3r::unescape_strings_cstyle(section.second.get<std::string>("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 4f734b85e..d6ccfd450 100644
--- a/xs/src/slic3r/GUI/Preset.hpp
+++ b/xs/src/slic3r/GUI/Preset.hpp
@@ -3,8 +3,12 @@
 
 #include <deque>
 
+#include <boost/filesystem/path.hpp>
+#include <boost/property_tree/ptree_fwd.hpp>
+
 #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<PrinterModel>          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 bd9e35ca8..ad27bf8c6 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<boost::filesystem::path> 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 &section : 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<std::string>("name", model.id);
-            section.second.get<std::string>("variants", "");
-            std::vector<std::string> variants;
-            if (Slic3r::unescape_strings_cstyle(section.second.get<std::string>("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 040d326b5..a16a6d889 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.cpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -1,9 +1,8 @@
 #include "PresetUpdater.hpp"
 
-#include <iostream>  // XXX
 #include <thread>
 #include <boost/algorithm/string.hpp>
-#include <boost/filesystem/path.hpp>
+#include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
 
 #include <wx/app.h>
@@ -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<VendorProfile> 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<VendorProfile> 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<VendorProfile> 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 aafe9569b..b10c61784 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<priv> p;
diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp
index bd8e9b758..2ac2ba700 100644
--- a/xs/src/slic3r/Utils/Semver.hpp
+++ b/xs/src/slic3r/Utils/Semver.hpp
@@ -3,6 +3,7 @@
 
 #include <string>
 #include <cstring>
+#include <ostream>
 #include <boost/optional.hpp>
 #include <boost/format.hpp>
 
@@ -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<std::string> metadata = boost::none,
+		boost::optional<std::string> 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<Semver> 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 964f350b9..46e4ace83 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 <vojtech@kral.hk>
Date: Wed, 11 Apr 2018 15:20:38 +0200
Subject: [PATCH 16/97] 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 b28b84df3..2082728cc 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 0c2a9dd59..52a896704 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 666379f02..1cb9f1c39 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 <vojtech@kral.hk>
Date: Wed, 11 Apr 2018 17:07:27 +0200
Subject: [PATCH 17/97] 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 83bb15684..7c8cd3a6b 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 21681398a..571aa8bb8 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 599217f89..3e4a30e3a 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 1b48670ca..7251f51e3 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 49cfff2bd..664bbd1bb 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 cef56b892..d70b47840 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 2ac2ba700..a1f4a92e8 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 a2b2328af..f38c4b407 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(&current_local));
 #endif
 }
 

From 9ab38f416d20d0ca8fc33c08d80e2be07fc878ff Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Thu, 12 Apr 2018 11:24:03 +0200
Subject: [PATCH 18/97] 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 eeb5b6ac5..c50021e2e 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 a7b8a5aa5..8f27027a4 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<std::string>	filaments;
diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp
index 730b97a32..5bc8b1012 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 += "<td>";
-//    text += _(L("ID:")) + " " + snapshot.id + "<br>";
-    text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "<br>";
-    text += _(L("slic3r version:")) + " " + snapshot.slic3r_version_captured.to_string() + "<br>";
+    // Format the row header.
+    text += std::string("<font size=\"5\"><b>") + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
     if (! snapshot.comment.empty())
-        text += _(L("user comment:")) + " " + snapshot.comment + "<br>";
+        text += " (" + snapshot.comment + ")";
+    text += "</b></font><br>";
+    // End of row header.
+//    text += _(L("ID:")) + " " + snapshot.id + "<br>";
+    // text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "<br>";
+    text += _(L("slic3r version:")) + " " + snapshot.slic3r_version_captured.to_string() + "<br>";
 //    text += "reason: " + snapshot.reason + "<br>";
     text += "print: " + snapshot.print + "<br>";
     text += "filaments: " + snapshot.filaments.front() + "<br>";
@@ -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 <vojtech@kral.hk>
Date: Thu, 12 Apr 2018 20:04:48 +0200
Subject: [PATCH 19/97] 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 2082728cc..b592c4289 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 3e4a30e3a..0285fe40e 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 95b3caf1a..5430e569c 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> 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> 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 43512e82f..c010a1748 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<Version>& 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 cac2759f1..ffda083ec 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 &section)
 		{ m_storage[section].clear(); }
 
+	typedef std::map<std::string, std::map<std::string, std::set<std::string>>> 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<std::string, std::map<std::string, std::set<std::string>>> &vendors) { m_vendors = vendors; m_dirty = true; }
-	void 				set_vendors(std::map<std::string, std::map<std::string, std::set<std::string>>> &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
-	const std::map<std::string, std::map<std::string, std::set<std::string>>> 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<std::string, std::map<std::string, std::string>> 	m_storage;
 	// Map of enabled vendors / models / variants
-	std::map<std::string, std::map<std::string, std::set<std::string>>> 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 52a896704..f353ab7f7 100644
--- a/xs/src/slic3r/GUI/ConfigWizard.cpp
+++ b/xs/src/slic3r/GUI/ConfigWizard.cpp
@@ -1,8 +1,8 @@
 #include "ConfigWizard_private.hpp"
 
+#include <iostream>   // XXX
 #include <algorithm>
 #include <utility>
-#include <boost/filesystem.hpp>
 
 #include <wx/settings.h>
 #include <wx/stattext.h>
@@ -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 652328aaa..137b276b8 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 <vector>
+#include <set>
+#include <unordered_map>
+#include <boost/filesystem.hpp>
 
 #include <wx/sizer.h>
 #include <wx/panel.h>
@@ -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<std::string, VendorProfile> 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<std::string, VendorProfile> vendors;
+	std::unordered_map<std::string, fs::path> vendors_rsrc;
 	std::unique_ptr<DynamicPrintConfig> custom_config;
 
 	wxBoxSizer *topsizer = nullptr;
diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
index 39bfbd398..26f0ff594 100644
--- a/xs/src/slic3r/GUI/Preset.cpp
+++ b/xs/src/slic3r/GUI/Preset.cpp
@@ -5,6 +5,8 @@
 #include "AppConfig.hpp"
 
 #include <fstream>
+#include <stdexcept>
+#include <boost/format.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
 #include <boost/algorithm/string/predicate.hpp>
@@ -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 d6ccfd450..8855bf1e7 100644
--- a/xs/src/slic3r/GUI/Preset.hpp
+++ b/xs/src/slic3r/GUI/Preset.hpp
@@ -58,7 +58,8 @@ public:
     };
     std::vector<PrinterModel>          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 ad27bf8c6..d1d66df60 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 a16a6d889..28b977321 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.cpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -1,21 +1,32 @@
 #include "PresetUpdater.hpp"
 
+#include <iostream>    // XXX
+#include <algorithm>
 #include <thread>
+#include <stack>
 #include <boost/algorithm/string.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
 
 #include <wx/app.h>
 #include <wx/event.h>
+#include <wx/msgdlg.h>
 
 #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<Update> 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<VendorProfile> 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<VendorProfile> 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 b10c61784..8fd6e4528 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<priv> p;
diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp
index a1f4a92e8..2c27ce982 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<std::string> metadata = boost::none,
 		boost::optional<std::string> prerelease = boost::none)
diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp
index 1cb9f1c39..4c1a637e4 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 <bubnikv@gmail.com>
Date: Fri, 13 Apr 2018 14:49:33 +0200
Subject: [PATCH 20/97] 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 a32d345ed..000000000
--- 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 28f17f10a..3bc2aeffd 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 9706ced2c..205eec218 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 3e4a30e3a..68d18af09 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 95b3caf1a..a80b0b6e9 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 43512e82f..c4243ca75 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 52a896704..30d1bf4e3 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 40ecf09a1..4e791e279 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<priv> p;
diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp
index 652328aaa..6881f6f55 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 d70b47840..036e08993 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 39bfbd398..853c229ff 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 d6ccfd450..08caf4d4e 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<PresetCollection*>(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<typename PreferedCondition>
+    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<typename PreferedCondition>
+    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<typename PreferedCondition>
+    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<Preset>::const_iterator find_preset_internal(const std::string &name) const
         { return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
 
+    size_t update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible);
+
     static std::vector<std::string> 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 ad27bf8c6..244915864 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<std::string>    initial_filament_profile_names;
+    std::string                 initial_printer_profile_name   = remove_ini_suffix(config.get("presets", "printer"));
+
     auto   *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(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<std::string> &prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("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 940987536..ed2b4b951 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 f39ff728c..bd9672bb2 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 1187a1cf5..d0d4057b2 100644
--- a/xs/xsp/GUI_Preset.xsp
+++ b/xs/xsp/GUI_Preset.xsp
@@ -44,9 +44,6 @@
 
     Ref<Preset>             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<std::string> 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<std::string>    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<DynamicPrintConfig>   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 c1ca58827..79b71143b 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -231,8 +231,6 @@ PresetCollection*	      	O_OBJECT_SLIC3R
 Ref<PresetCollection>      	O_OBJECT_SLIC3R_T
 PresetBundle*	    		O_OBJECT_SLIC3R
 Ref<PresetBundle>    		O_OBJECT_SLIC3R_T
-PresetHints*	    		O_OBJECT_SLIC3R
-Ref<PresetHints>    		O_OBJECT_SLIC3R_T
 TabIface*	   				O_OBJECT_SLIC3R
 Ref<TabIface> 				O_OBJECT_SLIC3R_T
 
diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
index 0214a158d..a2bca2c7b 100644
--- a/xs/xsp/typemap.xspt
+++ b/xs/xsp/typemap.xspt
@@ -208,8 +208,6 @@
 %typemap{Ref<PresetCollection>}{simple};
 %typemap{PresetBundle*};
 %typemap{Ref<PresetBundle>}{simple};
-%typemap{PresetHints*};
-%typemap{Ref<PresetHints>}{simple};
 %typemap{TabIface*};
 %typemap{Ref<TabIface>}{simple};
 

From 7dbb2ed6a3f97b585ed7341a9199bfd40627da6b Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Fri, 13 Apr 2018 15:08:58 +0200
Subject: [PATCH 21/97] 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 b592c4289..40cfcad89 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 28f17f10a..837bc7bac 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 cf82855cf..0ca094880 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 703d5ff66..733757e25 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 5430e569c..b1abc5c63 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 ee77f877a..d0f2f1019 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 730b97a32..8739b8fa2 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 f353ab7f7..5b49fc025 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 137b276b8..c93e0a80d 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<std::string, VendorProfile> vendors;
+	AppConfig appconfig_vendors;    // TODO: use order-preserving container
+	std::unordered_map<std::string, VendorProfile> vendors;   // TODO: just set?
 	std::unordered_map<std::string, fs::path> vendors_rsrc;
 	std::unique_ptr<DynamicPrintConfig> custom_config;
 
diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp
index 28b977321..257c7f552 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.cpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -4,6 +4,8 @@
 #include <algorithm>
 #include <thread>
 #include <stack>
+#include <stdexcept>
+#include <boost/format.hpp>
 #include <boost/algorithm/string.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
@@ -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<Update> Updates;
 struct PresetUpdater::priv
 {
 	int version_online_event;
-	AppConfig *app_config;
-	bool version_check;
-	bool preset_update;
+	std::vector<Index> 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<VendorProfile> 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<VendorProfile> 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<VendorProfile> 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<VendorProfile> vendors) const
 			GUI::get_app()->QueueEvent(evt);
 		})
 		.perform_sync();
+}
 
-	if (!preset_update) { return; }
+void PresetUpdater::priv::sync_config(const std::set<VendorProfile> 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<VendorProfile> 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 8fd6e4528..966dd1464 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<priv> p;
diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp
index 4c1a637e4..3a4d55a01 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 <bubnikv@gmail.com>
Date: Fri, 13 Apr 2018 16:15:30 +0200
Subject: [PATCH 22/97] 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 a80b0b6e9..8344e2822 100644
--- a/xs/src/slic3r/Config/Version.cpp
+++ b/xs/src/slic3r/Config/Version.cpp
@@ -15,11 +15,131 @@ namespace Config {
 
 static boost::optional<Semver> 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 c4243ca75..fb45c17eb 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 a1f4a92e8..538a3f144 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 2726267748b7ff23665794db121db2484fbc3060 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Mon, 16 Apr 2018 11:47:35 +0200
Subject: [PATCH 23/97] Bugfix: validation of equal layering rejected even some
 valid configurations

---
 xs/src/libslic3r/Print.cpp | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
index dcece7a9b..c19c97fae 100644
--- a/xs/src/libslic3r/Print.cpp
+++ b/xs/src/libslic3r/Print.cpp
@@ -598,6 +598,12 @@ std::string Print::validate() const
         if (! this->config.use_relative_e_distances)
             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
+        for (const auto* object : objects)
+            if (object->layer_height_profile.size() > most_layered_object->layer_height_profile.size())
+                most_layered_object = object;
+
         for (PrintObject *object : this->objects) {
             SlicingParameters slicing_params = object->slicing_parameters();
             if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON ||
@@ -614,12 +620,15 @@ std::string Print::validate() const
             object->layer_height_profile_valid = was_layer_height_profile_valid;
 
             if ( this->config.variable_layer_height ) {
-                PrintObject* first_object = this->objects.front();
                 int i = 0;
-                while ( i < first_object->layer_height_profile.size() && i < object->layer_height_profile.size() ) {
-                    if (std::abs(first_object->layer_height_profile[i] - object->layer_height_profile[i]) > EPSILON )
+                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;
                 }
             }
 

From c733e3151b2d15fdf1507c8417c2a96898d1d019 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 16 Apr 2018 16:52:11 +0200
Subject: [PATCH 24/97] 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 40cfcad89..473fc6b90 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 0f192c37c..4aef4d5c1 100644
--- a/xs/src/libslic3r/libslic3r.h
+++ b/xs/src/libslic3r/libslic3r.h
@@ -14,7 +14,7 @@
 #include <boost/thread.hpp>
 
 #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 a7b8a5aa5..584a37400 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 d0f2f1019..5104078b1 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 ffda083ec..88ba0a662 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 5b49fc025..a059d234b 100644
--- a/xs/src/slic3r/GUI/ConfigWizard.cpp
+++ b/xs/src/slic3r/GUI/ConfigWizard.cpp
@@ -1,8 +1,8 @@
 #include "ConfigWizard_private.hpp"
 
-#include <iostream>   // XXX
 #include <algorithm>
 #include <utility>
+#include <unordered_map>
 
 #include <wx/settings.h>
 #include <wx/stattext.h>
@@ -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<std::string, std::pair<std::string, std::string>> 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<std::string> 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 40ecf09a1..b34467011 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<priv> p;
diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp
index c93e0a80d..8aab1cc19 100644
--- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp
+++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp
@@ -15,6 +15,7 @@
 #include <wx/spinctrl.h>
 
 #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<std::string, VendorProfile> 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<Checkbox*> 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<std::string, VendorProfile> vendors;   // TODO: just set?
-	std::unordered_map<std::string, fs::path> vendors_rsrc;
+	std::unordered_map<std::string, std::string> vendors_rsrc;
 	std::unique_ptr<DynamicPrintConfig> 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 d70b47840..916c407af 100644
--- a/xs/src/slic3r/GUI/GUI.cpp
+++ b/xs/src/slic3r/GUI/GUI.cpp
@@ -37,6 +37,7 @@
 #include <wx/sizer.h>
 #include <wx/combo.h>
 #include <wx/window.h>
+#include <wx/msgdlg.h>
 
 #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<Tab *> 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<Tab *>& 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 2a3667eb3..6a23ed4eb 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 257c7f552..3291af7e0 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<std::string> &&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 966dd1464..1499570db 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.hpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.hpp
@@ -2,6 +2,7 @@
 #define slic3r_PresetUpdate_hpp_
 
 #include <memory>
+#include <vector>
 
 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<std::string> &&bundles);
 private:
 	struct priv;
 	std::unique_ptr<priv> p;
diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp
index 2c27ce982..3e9276be6 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<Semver> parse(const std::string &str)
 	{
 		semver_t ver = semver_zero();
diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp
index 46e4ace83..0d9f0b62e 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 c1ca58827..393338f3b 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -238,7 +238,6 @@ Ref<TabIface> 				O_OBJECT_SLIC3R_T
 
 PresetUpdater*              O_OBJECT_SLIC3R
 Ref<PresetUpdater>          O_OBJECT_SLIC3R_T
-Clone<PresetUpdater>        O_OBJECT_SLIC3R_T
 
 OctoPrint*                  O_OBJECT_SLIC3R
 Ref<OctoPrint>              O_OBJECT_SLIC3R_T
diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
index 0214a158d..2539811b3 100644
--- a/xs/xsp/typemap.xspt
+++ b/xs/xsp/typemap.xspt
@@ -208,6 +208,8 @@
 %typemap{Ref<PresetCollection>}{simple};
 %typemap{PresetBundle*};
 %typemap{Ref<PresetBundle>}{simple};
+%typemap{PresetUpdater*};
+%typemap{Ref<PresetUpdater>}{simple};
 %typemap{PresetHints*};
 %typemap{Ref<PresetHints>}{simple};
 %typemap{TabIface*};

From d26c8e5336bbc92b79875141f43182ccb7461616 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 16 Apr 2018 17:43:23 +0200
Subject: [PATCH 25/97] 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 762f5bd70..bf3c78964 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 <vojtech@kral.hk>
Date: Mon, 16 Apr 2018 18:33:33 +0200
Subject: [PATCH 26/97] 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 cd1ed64cb..467c13e16 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 d7dc04eb57941d04aa8757cbcd45457f49acbcf5 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Tue, 17 Apr 2018 08:18:12 +0200
Subject: [PATCH 27/97] Removed parameter filament_cooling_time (fixed value of
 14s for now)

---
 xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 4 ++--
 xs/src/libslic3r/Print.cpp                  | 2 --
 xs/src/libslic3r/PrintConfig.cpp            | 9 ---------
 xs/src/libslic3r/PrintConfig.hpp            | 2 --
 xs/src/slic3r/GUI/Preset.cpp                | 2 +-
 xs/src/slic3r/GUI/Tab.cpp                   | 1 -
 6 files changed, 3 insertions(+), 17 deletions(-)

diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
index 175de0276..c83c79a04 100644
--- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
+++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
@@ -67,7 +67,7 @@ public:
 
 	// Set the extruder properties.
 	void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed,
-                      float unloading_speed, float delay, float cooling_time, std::string ramming_parameters, float nozzle_diameter)
+                      float unloading_speed, float delay, std::string ramming_parameters, float nozzle_diameter)
 	{
         //while (m_filpar.size() < idx+1)   // makes sure the required element is in the vector
         m_filpar.push_back(FilamentParameters());
@@ -78,7 +78,7 @@ public:
         m_filpar[idx].loading_speed = loading_speed;
         m_filpar[idx].unloading_speed = unloading_speed;
         m_filpar[idx].delay = delay;
-        m_filpar[idx].cooling_time = cooling_time;
+        m_filpar[idx].cooling_time = 14.f; // let's fix it for now, cooling moves will be reworked for 1.41 anyway
         m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
 
         m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
index c19c97fae..e692b1e9e 100644
--- a/xs/src/libslic3r/Print.cpp
+++ b/xs/src/libslic3r/Print.cpp
@@ -186,7 +186,6 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
             || opt_key == "filament_loading_speed"
             || opt_key == "filament_unloading_speed"
             || opt_key == "filament_toolchange_delay"
-            || opt_key == "filament_cooling_time"
             || opt_key == "filament_ramming_parameters"
             || opt_key == "gcode_flavor"
             || opt_key == "single_extruder_multi_material"
@@ -1091,7 +1090,6 @@ void Print::_make_wipe_tower()
             this->config.filament_loading_speed.get_at(i),
             this->config.filament_unloading_speed.get_at(i),
             this->config.filament_toolchange_delay.get_at(i),
-            this->config.filament_cooling_time.get_at(i),
             this->config.filament_ramming_parameters.get_at(i),
             this->config.nozzle_diameter.get_at(i));
 
diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
index 1e7e0bacc..02995baf3 100644
--- a/xs/src/libslic3r/PrintConfig.cpp
+++ b/xs/src/libslic3r/PrintConfig.cpp
@@ -481,15 +481,6 @@ PrintConfigDef::PrintConfigDef()
     def->cli = "filament-toolchange-delay=f@";
     def->min = 0;
     def->default_value = new ConfigOptionFloats { 0. };
-    
-    def = this->add("filament_cooling_time", coFloats);
-    def->label = L("Cooling time");
-    def->tooltip = L("The filament is slowly moved back and forth after retraction into the cooling tube "
-                   "for this amount of time.");
-    def->cli = "filament_cooling_time=i@";
-    def->sidetext = L("s");
-    def->min = 0;
-    def->default_value = new ConfigOptionFloats { 14.f };
 
     def = this->add("filament_ramming_parameters", coStrings);
     def->label = L("Ramming parameters");
diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
index 967a87310..2e36ca665 100644
--- a/xs/src/libslic3r/PrintConfig.hpp
+++ b/xs/src/libslic3r/PrintConfig.hpp
@@ -476,7 +476,6 @@ public:
     ConfigOptionFloats              filament_loading_speed;
     ConfigOptionFloats              filament_unloading_speed;
     ConfigOptionFloats              filament_toolchange_delay;
-    ConfigOptionFloats              filament_cooling_time;
     ConfigOptionStrings             filament_ramming_parameters;
     ConfigOptionBool                gcode_comments;
     ConfigOptionEnum<GCodeFlavor>   gcode_flavor;
@@ -534,7 +533,6 @@ protected:
         OPT_PTR(filament_loading_speed);
         OPT_PTR(filament_unloading_speed);
         OPT_PTR(filament_toolchange_delay);
-        OPT_PTR(filament_cooling_time);
         OPT_PTR(filament_ramming_parameters);
         OPT_PTR(gcode_comments);
         OPT_PTR(gcode_flavor);
diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
index d48c9bf8f..34b4c5326 100644
--- a/xs/src/slic3r/GUI/Preset.cpp
+++ b/xs/src/slic3r/GUI/Preset.cpp
@@ -209,7 +209,7 @@ const std::vector<std::string>& Preset::filament_options()
     static std::vector<std::string> s_opts {
         "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
         "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_unloading_speed", "filament_toolchange_delay",
-        "filament_cooling_time", "filament_ramming_parameters", "temperature", "first_layer_temperature", "bed_temperature",
+        "filament_ramming_parameters", "temperature", "first_layer_temperature", "bed_temperature",
         "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers",
         "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers",
         "compatible_printers_condition", "inherits"
diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
index cc4b18c7c..3e4d8ce60 100644
--- a/xs/src/slic3r/GUI/Tab.cpp
+++ b/xs/src/slic3r/GUI/Tab.cpp
@@ -1287,7 +1287,6 @@ void TabFilament::build()
 		optgroup->append_single_option_line("filament_loading_speed");
         optgroup->append_single_option_line("filament_unloading_speed");
         optgroup->append_single_option_line("filament_toolchange_delay");
-        optgroup->append_single_option_line("filament_cooling_time");
         line = { _(L("Ramming")), "" };
         line.widget = [this](wxWindow* parent){
 			auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);

From b506aa11fab60fa5413fc82d3615e15a7a9624ea Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Tue, 17 Apr 2018 10:28:32 +0200
Subject: [PATCH 28/97] 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 3291af7e0..dc3b95d79 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 <bubnikv@gmail.com>
Date: Tue, 17 Apr 2018 10:55:18 +0200
Subject: [PATCH 29/97] 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 e55e24a7a..ab06a6455 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 <bubnikv@gmail.com>
Date: Tue, 17 Apr 2018 10:55:58 +0200
Subject: [PATCH 30/97] 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 851022489..3476a897d 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<ConfigOptionFloatOrPercent>("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<ConfigOptionFloatOrPercent>("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 <bubnikv@gmail.com>
Date: Tue, 17 Apr 2018 11:06:15 +0200
Subject: [PATCH 31/97] 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 0da7f22d1..b487aae4c 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 <vojtech@kral.hk>
Date: Tue, 17 Apr 2018 11:54:59 +0200
Subject: [PATCH 32/97] 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 3bc2aeffd..b43e26663 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 467c13e16..00f8399e6 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 d1d994ac1..66c8b3a95 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<priv> p;
diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
index 96d137165..bfdd46287 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 dc3b95d79..939be863c 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.cpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -9,6 +9,7 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
+#include <boost/log/trivial.hpp>
 
 #include <wx/app.h>
 #include <wx/event.h>
@@ -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 <enricoturri@seznam.cz>
Date: Tue, 17 Apr 2018 15:04:14 +0200
Subject: [PATCH 33/97] 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 aa86ae203..7f5e632bb 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 <enricoturri@seznam.cz>
Date: Tue, 17 Apr 2018 16:16:25 +0200
Subject: [PATCH 34/97] 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 13460cfed..75c19d89e 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 <vojtech@kral.hk>
Date: Tue, 17 Apr 2018 16:59:53 +0200
Subject: [PATCH 35/97] 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 473fc6b90..5ff8fdde1 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 5104078b1..100b2d69b 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 88ba0a662..6dcfec046 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 00f8399e6..c4ca7a886 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 beec4fe07..6f0754a03 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 651b95701..27add21ee 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 939be863c..439a619c6 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.cpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -14,7 +14,15 @@
 #include <wx/app.h>
 #include <wx/event.h>
 #include <wx/msgdlg.h>
+#include <wx/dialog.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/button.h>
+#include <wx/hyperlink.h>
+#include <wx/statbmp.h>
+#include <wx/checkbox.h>
 
+#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<std::string> &&bundles)
+void PresetUpdater::install_bundles_rsrc(std::vector<std::string> &&bundles)
 {
 	Updates updates;
 
@@ -377,7 +463,7 @@ void PresetUpdater::install_bundles_rsrc(AppConfig *app_config, std::vector<std:
 		updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors));
 	}
 
-	p->perform_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 1499570db..a53ed86eb 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<std::string> &&bundles);
+	void sync(PresetBundle *preset_bundle);
+	void slic3r_update_notify();
+	void config_update() const;
+	void install_bundles_rsrc(std::vector<std::string> &&bundles);
 private:
 	struct priv;
 	std::unique_ptr<priv> p;
diff --git a/xs/xsp/GUI_AppConfig.xsp b/xs/xsp/GUI_AppConfig.xsp
index de0e5a22b..08a88883d 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 ecf6f5a22..2c63db10c 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 3a4d55a01..53c3aa985 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 <vojtech@kral.hk>
Date: Tue, 17 Apr 2018 17:11:56 +0200
Subject: [PATCH 36/97] 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 5ff8fdde1..10375a9dc 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 439a619c6..2140a2c91 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 <enricoturri@seznam.cz>
Date: Wed, 18 Apr 2018 09:44:49 +0200
Subject: [PATCH 37/97] 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 75c19d89e..da04747c5 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 <enricoturri@seznam.cz>
Date: Wed, 18 Apr 2018 10:17:22 +0200
Subject: [PATCH 38/97] 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 da04747c5..d8af6f71c 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 <vojtech@kral.hk>
Date: Wed, 18 Apr 2018 11:40:43 +0200
Subject: [PATCH 39/97] 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 7c8cd3a6b..ed2686cdc 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 571aa8bb8..6f31401ea 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 c4ca7a886..0eee234db 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 66c8b3a95..eeb64c57f 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 faabca812..8e43ac3ac 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<std::string, VendorProfile> vendors;   // TODO: just set?
+	bool flag_startup;
+	bool flag_empty_datadir;
+	AppConfig appconfig_vendors;
+	std::unordered_map<std::string, VendorProfile> vendors;
 	std::unordered_map<std::string, std::string> vendors_rsrc;
 	std::unique_ptr<DynamicPrintConfig> custom_config;
 
diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
index bfdd46287..7de82cb5f 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 6ee9d4f54..92a6e6ebb 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 2140a2c91..bf9e1eb44 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<std::string> &&bundles)
+void PresetUpdater::install_bundles_rsrc(std::vector<std::string> &&bundles, bool snapshot)
 {
 	Updates updates;
 
@@ -468,7 +470,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> &&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 a53ed86eb..8fe3e021d 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<std::string> &&bundles);
+	void install_bundles_rsrc(std::vector<std::string> &&bundles, bool snapshot = true);
 private:
 	struct priv;
 	std::unique_ptr<priv> p;

From 0711f84ea0277dd503abe49cf0b58ddeb2d030f2 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Wed, 18 Apr 2018 12:33:07 +0200
Subject: [PATCH 40/97] 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 6731cd394..4c60577d5 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<ConfigOptionsGroup>(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<bool>(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 fa97a867519ef5dc91bf7fab4c7c5d3a104f0c50 Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Wed, 18 Apr 2018 13:35:51 +0200
Subject: [PATCH 41/97] 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 7fa9c998d..15dcb2fbb 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<std::string> PresetCollection::merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors)
+{
+    std::vector<std::string> 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 480d6b178..e80f138ee 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<std::string> merge_presets(PresetCollection &&other, const std::set<VendorProfile> &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 6f0754a03..717e7a6f0 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<std::string> 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<std::string> PresetBundle::merge_presets(PresetBundle &&other)
+{
+    this->vendors.insert(other.vendors.begin(), other.vendors.end());
+    std::vector<std::string> duplicate_prints    = this->prints   .merge_presets(std::move(other.prints),    this->vendors);
+    std::vector<std::string> duplicate_filaments = this->filaments.merge_presets(std::move(other.filaments), this->vendors);
+    std::vector<std::string> 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 27add21ee..9f2afbead 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<std::string>    merge_presets(PresetBundle &&other);
 
     // Set the "enabled" flag for printer vendors, printer models and printer variants
     // based on the user configuration.

From 8ab62d702c4a97579a7afbb455982c4d006ac911 Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Wed, 18 Apr 2018 18:06:07 +0200
Subject: [PATCH 42/97] 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 96c4b74d7..9cb60384f 100644
--- a/xs/src/xsinit.h
+++ b/xs/src/xsinit.h
@@ -85,8 +85,12 @@ namespace Slic3r {
     
 template<class T>
 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<class T>
 const char* perl_class_name(const T*) { return ClassTraits<T>::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<class T>
 const char* perl_class_name_ref(const T*) { return ClassTraits<T>::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<class T>
 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<class T>
 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 T> 
 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*>(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<T>::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 T>
 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<T>::name; }
 };
 
diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp
index 1376ff164..406d71828 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<TabIface> 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 0214a158d..797f3c19f 100644
--- a/xs/xsp/typemap.xspt
+++ b/xs/xsp/typemap.xspt
@@ -211,7 +211,6 @@
 %typemap{PresetHints*};
 %typemap{Ref<PresetHints>}{simple};
 %typemap{TabIface*};
-%typemap{Ref<TabIface>}{simple};
 
 %typemap{PrintRegionPtrs*};
 %typemap{PrintObjectPtrs*};

From c3c9ebdd12616aaa79ea0e6d2a06d3db06d36f31 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Wed, 18 Apr 2018 18:48:32 +0200
Subject: [PATCH 43/97] Fix wxPerl warning annoyance

---
 lib/Slic3r.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
index e55e24a7a..19915ddd8 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 <bubnikv@gmail.com>
Date: Wed, 18 Apr 2018 18:57:34 +0200
Subject: [PATCH 44/97] Fix of the previous commit. Once the Slic3r::GUI::Tab
 was rewritten from Ref<TabIface> 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 e55e24a7a..5e91f56ce 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 ad4cd058505ae33ce7e0b23fc513384f2de44805 Mon Sep 17 00:00:00 2001
From: Enrico Turri <enricoturri@seznam.cz>
Date: Thu, 19 Apr 2018 13:31:50 +0200
Subject: [PATCH 45/97] 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 d8af6f71c..65abf850e 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 bdaf1b01bedea5cac8b0d3d15dd48f87f531651c Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Thu, 19 Apr 2018 16:49:22 +0200
Subject: [PATCH 46/97] 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 0eee234db..a1ee5a748 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 8e43ac3ac..cdab2eb3c 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 <vojtech@kral.hk>
Date: Thu, 19 Apr 2018 18:29:19 +0200
Subject: [PATCH 47/97] 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 a19354403..527738644 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 7251f51e3..01a15fc43 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 100b2d69b..965e8185d 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 bf3c78964..87396d812 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<std::string> metadata = boost::none,
-		boost::optional<std::string> prerelease = boost::none)
+		boost::optional<const std::string&> metadata = boost::none,
+		boost::optional<const std::string&> 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<Semver> 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<const std::string&> meta) { meta ? strdup(*meta) : nullptr; }
+	void set_prerelease(boost::optional<const std::string&> 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<char*>(str.c_str())); }
 };
 
 

From 2e61420747b6e240fff989e8851c13edc6466375 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Thu, 19 Apr 2018 18:31:14 +0200
Subject: [PATCH 48/97] 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 b43e26663..5dd09868f 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 <bubnikv@gmail.com>
Date: Fri, 20 Apr 2018 10:26:23 +0200
Subject: [PATCH 49/97] 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 c82169308..18329aa5c 100644
--- a/xs/src/slic3r/Config/Snapshot.cpp
+++ b/xs/src/slic3r/Config/Snapshot.cpp
@@ -7,6 +7,7 @@
 
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/trim.hpp>
+#include <boost/nowide/cstdio.hpp>
 #include <boost/nowide/fstream.hpp>
 #include <boost/property_tree/ini_parser.hpp>
 #include <boost/property_tree/ptree.hpp>
@@ -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<std::string> 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<std::string, std::map<std::string, std::set<std::string>>> &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<std::string> 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 a7e70bb4b..77aee3e21 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<std::string, std::set<std::string>> models_variants_installed;
 	};
-	// List of vendor configs contained in this snapshot.
+	// List of vendor configs contained in this snapshot, sorted lexicographically.
 	std::vector<VendorConfig> 	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<Snapshot>& 	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<Snapshot>			m_snapshots;
 };
 
diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp
index 0e0df15f2..99af707e1 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 = "<tr bgcolor=\"";
-    text += row_even ? "#FFFFFF" : "#C0C0C0";
+    text += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5");
     text += "\">";
     text += "<td>";
     // Format the row header.
-    text += std::string("<font size=\"5\"><b>") + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
+    text += std::string("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active: ")) : "") + 
+        Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
     if (! snapshot.comment.empty())
         text += " (" + snapshot.comment + ")";
     text += "</b></font><br>";
     // End of row header.
 //    text += _(L("ID:")) + " " + snapshot.id + "<br>";
     // text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "<br>";
-    text += _(L("slic3r version:")) + " " + snapshot.slic3r_version_captured.to_string() + "<br>";
+    text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
 //    text += "reason: " + snapshot.reason + "<br>";
-    text += "print: " + snapshot.print + "<br>";
-    text += "filaments: " + snapshot.filaments.front() + "<br>";
-    text += "printer: " + snapshot.printer + "<br>";
+    text += _(L("print")) + ": " + snapshot.print + "<br>";
+    text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
+    text += _(L("printer")) + ": " + snapshot.printer + "<br>";
 
     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 += "<br>";
         for (const std::pair<std::string, std::set<std::string>> &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 += "<p align=\"right\"><a href=\"" + snapshot.id + "\">Activate</a></p>";
+    if (! snapshot_active)
+        text += "<p align=\"right\"><a href=\"" + snapshot.id + "\">" + _(L("Activate")) + "</a></p>";
     text += "</td>";
 	text += "</tr>";
     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 = 
         "<html>"
@@ -75,7 +79,7 @@ static std::string generate_html_page(const Config::SnapshotDB &snapshot_db)
     text += "<table style=\"width:100%\">";
     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 +=
         "</table>"
@@ -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 0d1109615..943601e73 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 7de82cb5f..88c3f421b 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 <vojtech@kral.hk>
Date: Fri, 20 Apr 2018 11:05:00 +0200
Subject: [PATCH 50/97] 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 f900137d3..0066aa5e6 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<typename T>
diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp
index 733757e25..f2415ac07 100644
--- a/xs/src/libslic3r/utils.cpp
+++ b/xs/src/libslic3r/utils.cpp
@@ -1,6 +1,12 @@
 #include <locale>
 #include <ctime>
 
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
 #include <boost/log/core.hpp>
 #include <boost/log/trivial.hpp>
 #include <boost/log/expressions.hpp>
@@ -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 bf9e1eb44..9b271492c 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 <vojtech@kral.hk>
Date: Fri, 20 Apr 2018 11:06:12 +0200
Subject: [PATCH 51/97] 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 19915ddd8..d249bd10b 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 e0421a3ba6ca566dab584da32ecd32329ae45b1e Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Fri, 20 Apr 2018 14:53:11 +0200
Subject: [PATCH 52/97] 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 9b271492c..473fcf84f 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 f8b1dc550685a8ca76c4d7a810151eabf08db2c1 Mon Sep 17 00:00:00 2001
From: Enrico Turri <enricoturri@seznam.cz>
Date: Mon, 23 Apr 2018 08:44:24 +0200
Subject: [PATCH 53/97] 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 65abf850e..ff6d73399 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 d59865491..5a29ab394 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 537cb0c8f..bd49bedb6 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 33c0d1dca392db9ae852123834938b700104c2a1 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 23 Apr 2018 11:16:47 +0200
Subject: [PATCH 54/97] 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 473fcf84f..a64d05eca 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.cpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -1,9 +1,9 @@
 #include "PresetUpdater.hpp"
 
-#include <iostream>    // XXX
 #include <algorithm>
 #include <thread>
 #include <stack>
+#include <ostream>
 #include <stdexcept>
 #include <boost/format.hpp>
 #include <boost/algorithm/string.hpp>
@@ -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<Update> 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<VendorProfile> 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<VendorProfile> 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<std::string> &&bundles, bool snapshot)
+void PresetUpdater::install_bundles_rsrc(std::vector<std::string> 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 8fe3e021d..287f20652 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<std::string> &&bundles, bool snapshot = true);
+
+	// "Update" a list of bundles from resources (behaves like an online update).
+	void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true);
 private:
 	struct priv;
 	std::unique_ptr<priv> p;

From a7a8030feaf5d22dfa05f6427879ec3e2613b211 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 23 Apr 2018 13:58:03 +0200
Subject: [PATCH 55/97] 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 a64d05eca..8dd974537 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<VendorProfile> 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 a3c3eb5d2af4a9e6e809377f78e3d211a2f00182 Mon Sep 17 00:00:00 2001
From: Enrico Turri <enricoturri@seznam.cz>
Date: Mon, 23 Apr 2018 15:30:41 +0200
Subject: [PATCH 56/97] 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 5a29ab394..ce78eab8e 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 bd49bedb6..4834f7aed 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 ccd1c01d0b46354af7fe2a11a205ceee9f6c5a74 Mon Sep 17 00:00:00 2001
From: Enrico Turri <enricoturri@seznam.cz>
Date: Tue, 24 Apr 2018 14:21:31 +0200
Subject: [PATCH 57/97] 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 4834f7aed..9d8b82242 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 <vojtech@kral.hk>
Date: Tue, 24 Apr 2018 18:06:42 +0200
Subject: [PATCH 58/97] 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 10375a9dc..89a8e7974 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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

literal 0
HcmV?d00001

diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index 7798333fb..2f03daaa7 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -220,6 +220,8 @@ add_library(libslic3r_gui STATIC
     ${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp
     ${LIBDIR}/slic3r/GUI/ConfigWizard.cpp
     ${LIBDIR}/slic3r/GUI/ConfigWizard.hpp
+    ${LIBDIR}/slic3r/GUI/UpdateDialogs.cpp
+    ${LIBDIR}/slic3r/GUI/UpdateDialogs.hpp
     ${LIBDIR}/slic3r/Utils/Http.cpp
     ${LIBDIR}/slic3r/Utils/Http.hpp
     ${LIBDIR}/slic3r/Utils/OctoPrint.cpp
diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp
index a1ee5a748..84f401ba1 100644
--- a/xs/src/slic3r/GUI/ConfigWizard.cpp
+++ b/xs/src/slic3r/GUI/ConfigWizard.cpp
@@ -207,7 +207,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent) :
 	others_buttons(new wxPanel(parent)),
 	cbox_reset(nullptr)
 {
-	if (wizard_p()->flag_startup && 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 eeb64c57f..73fce7cd2 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 cdab2eb3c..474394bc3 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<std::string, VendorProfile> vendors;
 	std::unordered_map<std::string, std::string> 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 88c3f421b..bd74e118e 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<ConfigWizard::RunReason>(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 92a6e6ebb..a8bbdccc7 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 000000000..076554e23
--- /dev/null
+++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp
@@ -0,0 +1,203 @@
+#include "UpdateDialogs.hpp"
+
+#include <wx/settings.h>
+#include <wx/sizer.h>
+#include <wx/event.h>
+#include <wx/stattext.h>
+#include <wx/button.h>
+#include <wx/hyperlink.h>
+#include <wx/statbmp.h>
+#include <wx/checkbox.h>
+
+#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<std::string, std::string> &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<std::string, std::string> &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 000000000..ae949b8dd
--- /dev/null
+++ b/xs/src/slic3r/GUI/UpdateDialogs.hpp
@@ -0,0 +1,92 @@
+#ifndef slic3r_UpdateDialogs_hpp_
+#define slic3r_UpdateDialogs_hpp_
+
+#include <string>
+#include <unordered_map>
+
+#include <wx/dialog.h>
+#include <wx/font.h>
+#include <wx/bitmap.h>
+
+#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<std::string, std::string> &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<std::string, std::string> &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 8dd974537..ec152df63 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.cpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -2,7 +2,7 @@
 
 #include <algorithm>
 #include <thread>
-#include <stack>
+#include <unordered_map>
 #include <ostream>
 #include <stdexcept>
 #include <boost/format.hpp>
@@ -14,18 +14,13 @@
 #include <wx/app.h>
 #include <wx/event.h>
 #include <wx/msgdlg.h>
-#include <wx/dialog.h>
-#include <wx/sizer.h>
-#include <wx/stattext.h>
-#include <wx/button.h>
-#include <wx/hyperlink.h>
-#include <wx/statbmp.h>
-#include <wx/checkbox.h>
 
 #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<Update> 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<Incompat> incompats;
+	std::vector<Update> 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<std::string, std::string> 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<std::string, std::string> 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<std::string> bundles, bool snapshot)
+void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
 {
 	Updates updates;
 
@@ -557,7 +549,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> 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 287f20652..6a53cca81 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<std::string> bundles, bool snapshot = true);
+	void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
 private:
 	struct priv;
 	std::unique_ptr<priv> p;
diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp
index 0d9f0b62e..ca90c54f2 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 53c3aa985..dc874acab 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 <vojtech@kral.hk>
Date: Tue, 24 Apr 2018 18:29:36 +0200
Subject: [PATCH 59/97] 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 076554e23..5d198661f 100644
--- a/xs/src/slic3r/GUI/UpdateDialogs.cpp
+++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp
@@ -156,7 +156,9 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, s
 	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"
+
+		"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"
 	)));
 	text->Wrap(CONTENT_WIDTH);
 	content_sizer->Add(text);

From 0feb4d823f629144e8e74645a7e032f03ab2c7fc Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Tue, 24 Apr 2018 18:40:06 +0200
Subject: [PATCH 60/97] 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 5d198661f..9a689e03d 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<std::string, std::string> &incompats) :
+MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, wxString> &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 ae949b8dd..f12fd3333 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<std::string, std::string> &incompats);
+	MsgDataIncompatible(const std::unordered_map<std::string, wxString> &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 ec152df63..54841a370 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<std::string, std::string> incompats_map;
+		std::unordered_map<std::string, wxString> 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 <bubnikv@gmail.com>
Date: Wed, 25 Apr 2018 10:37:31 +0200
Subject: [PATCH 61/97] 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 cd2baeffb..121cbb017 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<CoolingLine>    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<Extruder> &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<Line>   lines;
-    };
-    std::vector<Adjustment> 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<PerExtruderAdjustments> per_extruder_adjustments(num_extruders);
+    std::vector<size_t>                 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<size_t> by_slowdown_layer_time;
-    by_slowdown_layer_time.reserve(num_extruders);
+    std::vector<size_t> 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<PerExtruderAdjustments*> 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<float, float> 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<const Adjustment::Line*> lines;
+    std::vector<const CoolingLine*> 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 <enricoturri@seznam.cz>
Date: Wed, 25 Apr 2018 10:59:06 +0200
Subject: [PATCH 62/97] 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 ce78eab8e..f41394ccc 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 e93391e0f8c7d808e0c48951e478918e28693b22 Mon Sep 17 00:00:00 2001
From: Enrico Turri <enricoturri@seznam.cz>
Date: Wed, 25 Apr 2018 13:55:45 +0200
Subject: [PATCH 63/97] 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 ff6d73399..6cdf2a3e5 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 <vojtech@kral.hk>
Date: Wed, 25 Apr 2018 13:44:06 +0200
Subject: [PATCH 64/97] 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 18329aa5c..704fbcfa1 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> &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 77aee3e21..a916dfe92 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<std::string, std::set<std::string>> models_variants_installed;
 	};
diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp
index dc09ac870..a85322eca 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<Semver> 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 99af707e1..c0affcab7 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 += "</b></font><br>";
     // End of row header.
-//    text += _(L("ID:")) + " " + snapshot.id + "<br>";
-    // text += _(L("time captured:")) + " " + Utils::format_local_date_time(snapshot.time_captured) + "<br>";
     text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
-//    text += "reason: " + snapshot.reason + "<br>";
     text += _(L("print")) + ": " + snapshot.print + "<br>";
     text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
     text += _(L("printer")) + ": " + snapshot.printer + "<br>";
 
+    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 += "<br>";
         for (const std::pair<std::string, std::set<std::string>> &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 += "<br>";
         }
+        if (! vc.version.is_current_slic3r_supported()) { compatible = false; }
     }
 
-    if (! snapshot_active)
+    if (! compatible) {
+        text += "<p align=\"right\">" + _(L("Incompatible with this Slic3r")) + "</p>";
+    }
+    else if (! snapshot_active)
         text += "<p align=\"right\"><a href=\"" + snapshot.id + "\">" + _(L("Activate")) + "</a></p>";
     text += "</td>";
 	text += "</tr>";
diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp
index 87396d812..ae9d21c6b 100644
--- a/xs/src/slic3r/Utils/Semver.hpp
+++ b/xs/src/slic3r/Utils/Semver.hpp
@@ -4,6 +4,7 @@
 #include <string>
 #include <cstring>
 #include <ostream>
+#include <stdexcept>
 #include <boost/optional.hpp>
 #include <boost/format.hpp>
 
@@ -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<Semver> parse(const std::string &str)
 	{
 		semver_t ver = semver_zero();

From 1a4827ba33dfba5e6567d4e942fa91f662dc040d Mon Sep 17 00:00:00 2001
From: Enrico Turri <enricoturri@seznam.cz>
Date: Wed, 25 Apr 2018 14:38:44 +0200
Subject: [PATCH 65/97] 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 7f5e632bb..a016ab5aa 100644
--- a/xs/src/slic3r/GUI/3DScene.cpp
+++ b/xs/src/slic3r/GUI/3DScene.cpp
@@ -751,7 +751,10 @@ std::vector<double> GLVolumeCollection::get_current_print_zs() const
     // Collect layer top positions of all volumes.
     std::vector<double> 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 <vojtech@kral.hk>
Date: Wed, 25 Apr 2018 15:14:01 +0200
Subject: [PATCH 66/97] 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 9a689e03d..e11ecdf5e 100644
--- a/xs/src/slic3r/GUI/UpdateDialogs.cpp
+++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp
@@ -138,7 +138,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::unordered_map<std::string, std::stri
 	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);
+	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 ae9d21c6b..736f9b891 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<const std::string&> meta) { meta ? strdup(*meta) : nullptr; }
-	void set_prerelease(boost::optional<const std::string&> pre) { pre ? strdup(*pre) : nullptr; }
+	void set_metadata(boost::optional<const std::string&> meta) { ver.metadata = meta ? strdup(*meta) : nullptr; }
+	void set_prerelease(boost::optional<const std::string&> 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 <enricoturri@seznam.cz>
Date: Wed, 25 Apr 2018 15:16:39 +0200
Subject: [PATCH 67/97] 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 7b5752cd2..0cc8b2dec 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 <vojtech@kral.hk>
Date: Wed, 25 Apr 2018 15:20:46 +0200
Subject: [PATCH 68/97] 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 54841a370..2f9fc6871 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 <enricoturri@seznam.cz>
Date: Wed, 25 Apr 2018 15:31:37 +0200
Subject: [PATCH 69/97] 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 06929085c..a9ce04543 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 <vojtech@kral.hk>
Date: Wed, 25 Apr 2018 16:42:33 +0200
Subject: [PATCH 70/97] 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 5dd09868f..f07d8b280 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 0da7f22d1..b313c4dc5 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 <vojtech@kral.hk>
Date: Wed, 25 Apr 2018 17:43:01 +0200
Subject: [PATCH 71/97] 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 b313c4dc5..d82921fd8 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 965e8185d..70f4ba110 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 6dcfec046..16469f0e9 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 2f9fc6871..9c5fe0748 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 <bubnikv@gmail.com>
Date: Wed, 25 Apr 2018 22:06:44 +0200
Subject: [PATCH 72/97] 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 ee4f6abea..f69b7e8a8 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 121cbb017..ca9fc6555 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<PerExtruderAdjustments> per_extruder_adjustments(num_extruders);
-    std::vector<size_t>                 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<size_t>                 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<float, float> 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 <bubnikv@gmail.com>
Date: Wed, 25 Apr 2018 22:54:52 +0200
Subject: [PATCH 73/97] 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 f69b7e8a8..2f444cf9d 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 ca9fc6555..141d197ca 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<PerExtruderAdjustments> 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<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const
 {
     const FullPrintConfig       &config        = m_gcodegen.config();
     const std::vector<Extruder> &extruders     = m_gcodegen.writer().extruders();
-    const size_t                 num_extruders = extruders.size();
-
-    std::vector<PerExtruderAdjustments> 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<size_t>                 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<PerExtruderAdjustments> per_extruder_adjustments(extruders.size());
+    std::vector<size_t>                 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<float> 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<float> 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<size_t> 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<PerExtruderAdjustments*>::iterator it_begin,
+    std::vector<PerExtruderAdjustments*>::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<PerExtruderAdjustments*>::iterator it_begin,
+    std::vector<PerExtruderAdjustments*>::iterator it_end,
+    float time_stretch)
+{
+    // Slow down. Try to equalize the feedrates.
+    std::vector<PerExtruderAdjustments*> 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<PerExtruderAdjustments> &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<PerExtruderAdjustments*> 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<PerExtruderAdjustments*> 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<PerExtruderAdjustments>    &per_extruder_adjustments)
+{
+    // First sort the adjustment lines by of multiple extruders by their position in the source G-code.
     std::vector<const CoolingLine*> 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 f85c470b3..bf4b082e2 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<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const;
+    float       calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &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<PerExtruderAdjustments> &per_extruder_adjustments);
 
     GCode&              m_gcodegen;
     std::string         m_gcode;
@@ -34,6 +43,9 @@ private:
     std::vector<char>   m_axis;
     std::vector<float>  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 <enricoturri@seznam.cz>
Date: Thu, 26 Apr 2018 11:03:15 +0200
Subject: [PATCH 74/97] 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 6cdf2a3e5..88137185b 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 <enricoturri@seznam.cz>
Date: Thu, 26 Apr 2018 12:14:49 +0200
Subject: [PATCH 75/97] 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 88137185b..190e96052 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 <yusanka@gmail.com>
Date: Thu, 26 Apr 2018 12:40:17 +0200
Subject: [PATCH 76/97] 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 06929085c..210f29670 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 1e78a600a..7902fa128 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 8d57420a3..f01ef671c 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 4b2ecab60..009509282 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<std::string> &default_filament_profiles = preset.config.option<ConfigOptionStrings>("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 de04fc4cd..1ed5d1b36 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 <enricoturri@seznam.cz>
Date: Thu, 26 Apr 2018 13:03:54 +0200
Subject: [PATCH 77/97] 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 799bd6661..b7ecee5a4 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 <enricoturri@seznam.cz>
Date: Thu, 26 Apr 2018 13:40:29 +0200
Subject: [PATCH 78/97] 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 69fd3524d..c66de08ed 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 <yusanka@gmail.com>
Date: Thu, 26 Apr 2018 15:11:02 +0200
Subject: [PATCH 79/97] 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 d0a2ccec2..5a3b011d4 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 c7eb25c75..1856d94cf 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<void(const std::string&)>;
 
 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 <yusanka@gmail.com>
Date: Thu, 26 Apr 2018 16:33:37 +0200
Subject: [PATCH 80/97] 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 5a3b011d4..5cae9172e 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 <yusanka@gmail.com>
Date: Thu, 26 Apr 2018 17:46:24 +0200
Subject: [PATCH 81/97] 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 664bbd1bb..3c359fd66 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 =
             "<html>"
@@ -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 <bubnikv@gmail.com>
Date: Thu, 26 Apr 2018 18:56:16 +0200
Subject: [PATCH 82/97] 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 141d197ca..b786f9bce 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<PerExtruderAdjustments> 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<PerExtruderAdjustments> 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 <enricoturri@seznam.cz>
Date: Fri, 27 Apr 2018 09:54:21 +0200
Subject: [PATCH 83/97] 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 c66de08ed..d431708c1 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 e9c5f7515..a7d77e0b9 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<std::string>& colors);

From 6d38943222d45415316e102c14119c837bef499c Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Fri, 27 Apr 2018 12:29:18 +0200
Subject: [PATCH 84/97] 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 42a931033..6bac39bd7 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 e11ecdf5e..62534e598 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_map<std::string, w
 MsgDataIncompatible::~MsgDataIncompatible() {}
 
 
+// MsgDataLegacy
+
+MsgDataLegacy::MsgDataLegacy() :
+	MsgDialog(_(L("Configuration update")), _(L("Configuration update")))
+{
+	auto *text = new wxStaticText(this, wxID_ANY, 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()
+	));
+	text->Wrap(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 f12fd3333..e339fbe0d 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 <enricoturri@seznam.cz>
Date: Fri, 27 Apr 2018 12:56:35 +0200
Subject: [PATCH 85/97] 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 7b5bf7e8a..dde64a28f 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<Instance> InstancesList;
         typedef std::map<int, ObjectMetadata> IdToMetadataMap;
         typedef std::map<int, Geometry> IdToGeometryMap;
+        typedef std::map<int, std::vector<coordf_t>> 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<char> 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<std::string> objects;
+            boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
+
+            for (const std::string& object : objects)
+            {
+                std::vector<std::string> 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<std::string> 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<std::string> 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<coordf_t> 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<double> layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector<double>();
+            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 <enricoturri@seznam.cz>
Date: Fri, 27 Apr 2018 14:08:22 +0200
Subject: [PATCH 86/97] 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 190e96052..17fcd4624 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 <vojtech@kral.hk>
Date: Fri, 27 Apr 2018 15:19:07 +0200
Subject: [PATCH 87/97] 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 000000000..3ce1f109c
--- /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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

literal 0
HcmV?d00001

diff --git a/doc/updating/setting_sys.png b/doc/updating/setting_sys.png
new file mode 100644
index 0000000000000000000000000000000000000000..842a8bf736350d3934fa7998ac552d6ab898c330
GIT binary patch
literal 3863
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
V=N@Fvgr0G)F2qp#hvxG){{!@3fLQ<l

literal 0
HcmV?d00001

diff --git a/doc/updating/setting_user.png b/doc/updating/setting_user.png
new file mode 100644
index 0000000000000000000000000000000000000000..ffec5e0f3e27120225de03786328b36fd462d459
GIT binary patch
literal 3957
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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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
F{ul1$0T2KH

literal 0
HcmV?d00001


From 879d22c7ca1e74b22aefaec82d1435a1e6acc525 Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Fri, 27 Apr 2018 15:39:00 +0200
Subject: [PATCH 88/97] 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 869207b4d..4a013bf2d 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 <vojtech@kral.hk>
Date: Fri, 27 Apr 2018 15:44:32 +0200
Subject: [PATCH 89/97] 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 62534e598..da212cf13 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 <enricoturri@seznam.cz>
Date: Mon, 30 Apr 2018 08:57:41 +0200
Subject: [PATCH 90/97] 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 17fcd4624..352a61264 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 <yusanka@gmail.com>
Date: Mon, 30 Apr 2018 11:11:48 +0200
Subject: [PATCH 91/97] 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 7902fa128..09a2fe50f 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<Choice>(m_parent, opt, id)));
+		m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
     } else if (opt.gui_type.compare("color") == 0) {
-		m_fields.emplace(id, STDMOVE(ColourPicker::Create<ColourPicker>(m_parent, opt, id)));
+		m_fields.emplace(id, STDMOVE(ColourPicker::Create<ColourPicker>(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<Choice>(m_parent, opt, id)));
+		m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(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<TextCtrl>(m_parent, opt, id)));
+				m_fields.emplace(id, STDMOVE(TextCtrl::Create<TextCtrl>(parent(), opt, id)));
                 break;
 			case coBool:
 			case coBools:
-				m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(m_parent, opt, id)));
+				m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(parent(), opt, id)));
 				break;
 			case coInt:
 			case coInts:
-				m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(m_parent, opt, id)));
+				m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(parent(), opt, id)));
 				break;
             case coEnum:
-				m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(m_parent, opt, id)));
+				m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
 				break;
             case coPoints:
-				m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(m_parent, opt, id)));
+				m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(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 f01ef671c..01d9184fd 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<wxFlexGridSizer*>(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL);
         static_cast<wxFlexGridSizer*>(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 <enricoturri@seznam.cz>
Date: Mon, 30 Apr 2018 12:03:06 +0200
Subject: [PATCH 92/97] 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 98683cd8a..b446f456b 100644
--- a/xs/src/libslic3r/Format/AMF.cpp
+++ b/xs/src/libslic3r/Format/AMF.cpp
@@ -24,6 +24,12 @@
 
 #include <assert.h>
 
+// 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<Instance>   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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
     stream << "<amf unit=\"millimeter\">\n";
     stream << "<metadata type=\"cad\">Slic3r " << SLIC3R_VERSION << "</metadata>\n";
+    stream << "<metadata type=\"" << SLIC3RPE_AMF_VERSION << "\">" << VERSION_AMF << "</metadata>\n";
 
     if (export_print_config)
     {

From 5624b8afd21733cf813b5664f03228176f03a9f0 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 30 Apr 2018 14:31:57 +0200
Subject: [PATCH 93/97] 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 89a8e7974..04dc80323 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 5f1c11951..f67697b13 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 "$<TARGET_FILE:XS>" "${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 6bac39bd7..20a8a45db 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 d4b5988a8..c1ecc3dfd 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 000000000..532960cfc
--- /dev/null
+++ b/xs/src/slic3r/GUI/MsgDialog.cpp
@@ -0,0 +1,87 @@
+#include "MsgDialog.hpp"
+
+#include <wx/settings.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/button.h>
+#include <wx/statbmp.h>
+#include <wx/scrolwin.h>
+
+#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 000000000..a01127023
--- /dev/null
+++ b/xs/src/slic3r/GUI/MsgDialog.hpp
@@ -0,0 +1,65 @@
+#ifndef slic3r_MsgDialog_hpp_
+#define slic3r_MsgDialog_hpp_
+
+#include <string>
+#include <unordered_map>
+
+#include <wx/dialog.h>
+#include <wx/font.h>
+#include <wx/bitmap.h>
+
+#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 da212cf13..33c9c0c28 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<std::string, std::string> &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<std::string, wxString> &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 e339fbe0d..62548b98b 100644
--- a/xs/src/slic3r/GUI/UpdateDialogs.hpp
+++ b/xs/src/slic3r/GUI/UpdateDialogs.hpp
@@ -4,11 +4,8 @@
 #include <string>
 #include <unordered_map>
 
-#include <wx/dialog.h>
-#include <wx/font.h>
-#include <wx/bitmap.h>
-
 #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 be04af1f9..aa95bd647 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 <enricoturri@seznam.cz>
Date: Mon, 30 Apr 2018 15:27:01 +0200
Subject: [PATCH 94/97] 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 dde64a28f..0467962c3 100644
--- a/xs/src/libslic3r/Format/3mf.cpp
+++ b/xs/src/libslic3r/Format/3mf.cpp
@@ -16,6 +16,12 @@
 #include <Eigen/Dense>
 #include <miniz/miniz_zip.h>
 
+// 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<int, Geometry> IdToGeometryMap;
         typedef std::map<int, std::vector<coordf_t>> 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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\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 << "</" << METADATA_TAG << ">\n";
         stream << " <" << RESOURCES_TAG << ">\n";
 
         BuildItemsList build_items;

From 2d4cac00189aa75cf75637d0345a5befb2d80b8a Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Mon, 30 Apr 2018 14:20:33 +0200
Subject: [PATCH 95/97] 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 b932985bf..7ea16d942 100644
--- a/xs/src/slic3r/GUI/ButtonsDescription.cpp
+++ b/xs/src/slic3r/GUI/ButtonsDescription.cpp
@@ -2,6 +2,7 @@
 #include <wx/sizer.h>
 #include <wx/stattext.h>
 #include <wx/statbmp.h>
+#include <wx/clrpicker.h>
 
 #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<wxButton*>(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 1856d94cf..729f73d16 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 6bac39bd7..d743d8708 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 d4b5988a8..62a791df5 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 5e9b249ef..bb1d938a8 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 1ed5d1b36..62030bce3 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 <vojtech@kral.hk>
Date: Mon, 30 Apr 2018 17:07:30 +0200
Subject: [PATCH 96/97] 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 532960cfc..6fba47a37 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 33c9c0c28..70d9c851c 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<std::string, wxString> &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 <vojtech@kral.hk>
Date: Tue, 1 May 2018 10:35:07 +0200
Subject: [PATCH 97/97] 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 eca809ec1..bc59ac2f9 100644
--- a/xs/src/slic3r/GUI/Preset.cpp
+++ b/xs/src/slic3r/GUI/Preset.cpp
@@ -18,6 +18,7 @@
 #include <boost/property_tree/ini_parser.hpp>
 #include <boost/property_tree/ptree.hpp>
 #include <boost/locale.hpp>
+#include <boost/log/trivial.hpp>
 
 #include <wx/image.h>
 #include <wx/choice.h>
@@ -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<std::string>("name", model.id);
             section.second.get<std::string>("variants", "");
+            const auto variants_field = section.second.get<std::string>("variants", "");
             std::vector<std::string> variants;
-            if (Slic3r::unescape_strings_cstyle(section.second.get<std::string>("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 9c5fe0748..3b786e0d6 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<VendorProfile> 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%")