diff --git a/src/libslic3r/Channel.hpp b/src/libslic3r/Channel.hpp
new file mode 100644
index 000000000..8d1a07d35
--- /dev/null
+++ b/src/libslic3r/Channel.hpp
@@ -0,0 +1,104 @@
+#ifndef slic3r_Channel_hpp_
+#define slic3r_Channel_hpp_
+
+#include <deque>
+#include <condition_variable>
+#include <mutex>
+#include <utility>
+#include <boost/optional.hpp>
+
+
+namespace Slic3r {
+
+
+template<class T> class Channel
+{
+private:
+    using UniqueLock = std::unique_lock<std::mutex>;
+    using Queue = std::deque<T>;
+public:
+    class Guard
+    {
+    public:
+        Guard(UniqueLock lock, const Queue &queue) : m_lock(std::move(lock)), m_queue(queue) {}
+        Guard(const Guard &other) = delete;
+        Guard(Guard &&other) = delete;
+        ~Guard() {}
+
+        // Access trampolines
+        size_t size() const noexcept { return m_queue.size(); }
+        bool empty() const noexcept { return m_queue.empty(); }
+        typename Queue::const_iterator begin() const noexcept { return m_queue.begin(); }
+        typename Queue::const_iterator end() const noexcept { return m_queue.end(); }
+        typename Queue::const_reference operator[](size_t i) const { return m_queue[i]; }
+
+        Guard& operator=(const Guard &other) = delete;
+        Guard& operator=(Guard &&other) = delete;
+    private:
+        UniqueLock m_lock;
+        const Queue &m_queue;
+    };
+
+
+    Channel() {}
+    ~Channel() {}
+
+    void push(const T& item, bool silent = false)
+    {
+        {
+            UniqueLock lock(m_mutex);
+            m_queue.push_back(item);
+        }
+        if (! silent) { m_condition.notify_one(); }
+    }
+
+    void push(T &&item, bool silent = false)
+    {
+        {
+            UniqueLock lock(m_mutex);
+            m_queue.push_back(std::forward(item));
+        }
+        if (! silent) { m_condition.notify_one(); }
+    }
+
+    T pop()
+    {
+        UniqueLock lock(m_mutex);
+        m_condition.wait(lock, [this]() { return !m_queue.empty(); });
+        auto item = std::move(m_queue.front());
+        m_queue.pop_front();
+        return item;
+    }
+
+    boost::optional<T> try_pop()
+    {
+        UniqueLock lock(m_mutex);
+        if (m_queue.empty()) {
+            return boost::none;
+        } else {
+            auto item = std::move(m_queue.front());
+            m_queue.pop();
+            return item;
+        }
+    }
+
+    // Unlocked observers
+    // Thread unsafe! Keep in mind you need to re-verify the result after acquiring lock!
+    size_t size() const noexcept { return m_queue.size(); }
+    bool empty() const noexcept { return m_queue.empty(); }
+
+    Guard read() const
+    {
+        return Guard(UniqueLock(m_mutex), m_queue);
+    }
+
+private:
+    Queue m_queue;
+    std::mutex m_mutex;
+    std::condition_variable m_condition;
+};
+
+
+} // namespace Slic3r
+
+#endif // slic3r_Channel_hpp_
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 419aa7206..6b40f50bf 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -1297,10 +1297,11 @@ void PrintConfigDef::init_fff_params()
     def->default_value = new ConfigOptionString("");
     
     def = this->add("printhost_cafile", coString);
-    def->label = "HTTPS CA file";
+    def->label = "HTTPS CA File";
     def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
                    "If left blank, the default OS CA certificate repository is used.";
     def->cli = "printhost-cafile=s";
+    def->mode = comAdvanced;
     def->default_value = new ConfigOptionString("");
 
     def = this->add("print_host", coString);
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index e8031309c..3b6bad16d 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -103,12 +103,12 @@ add_library(libslic3r_gui STATIC
     GUI/ProgressIndicator.hpp
     GUI/ProgressStatusBar.hpp
     GUI/ProgressStatusBar.cpp
+    GUI/PrintHostDialogs.cpp
+    GUI/PrintHostDialogs.hpp
     Utils/Http.cpp
     Utils/Http.hpp
     Utils/FixModelByWin10.cpp
     Utils/FixModelByWin10.hpp
-    Utils/PrintHostSendDialog.cpp
-    Utils/PrintHostSendDialog.hpp
     Utils/OctoPrint.cpp
     Utils/OctoPrint.hpp
     Utils/Duet.cpp
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index f2249de3d..b31fa6b5f 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -19,9 +19,12 @@
 //#undef NDEBUG
 #include <cassert>
 #include <stdexcept>
+#include <cctype>
+#include <algorithm>
 
 #include <boost/format.hpp>
 #include <boost/filesystem/path.hpp>
+#include <boost/filesystem.hpp>
 #include <boost/log/trivial.hpp>
 #include <boost/nowide/cstdio.hpp>
 
@@ -62,6 +65,11 @@ PrinterTechnology BackgroundSlicingProcess::current_printer_technology() const
 	return m_print->technology();
 }
 
+static bool isspace(int ch)
+{
+	return std::isspace(ch) != 0;
+}
+
 // This function may one day be merged into the Print, but historically the print was separated
 // from the G-code generator.
 void BackgroundSlicingProcess::process_fff()
@@ -80,8 +88,8 @@ void BackgroundSlicingProcess::process_fff()
 		    	PlaceholderParser pp;
 		    	std::string normal_print_time = stats.estimated_normal_print_time;
 		    	std::string silent_print_time = stats.estimated_silent_print_time;
-				normal_print_time.erase(std::remove_if(normal_print_time.begin(), normal_print_time.end(), std::isspace), normal_print_time.end());
-				silent_print_time.erase(std::remove_if(silent_print_time.begin(), silent_print_time.end(), std::isspace), silent_print_time.end());
+				normal_print_time.erase(std::remove_if(normal_print_time.begin(), normal_print_time.end(), isspace), normal_print_time.end());
+				silent_print_time.erase(std::remove_if(silent_print_time.begin(), silent_print_time.end(), isspace), silent_print_time.end());
 		    	pp.set("print_time",        		new ConfigOptionString(normal_print_time));
 		    	pp.set("normal_print_time", 		new ConfigOptionString(normal_print_time));
 		    	pp.set("silent_print_time", 		new ConfigOptionString(silent_print_time));
@@ -373,6 +381,22 @@ void BackgroundSlicingProcess::schedule_export(const std::string &path)
 	m_export_path = path;
 }
 
+void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job)
+{
+	assert(m_export_path.empty());
+	if (! m_export_path.empty())
+		return;
+
+	const boost::filesystem::path path = boost::filesystem::temp_directory_path()
+		/ boost::filesystem::unique_path(".upload.%%%%-%%%%-%%%%-%%%%.gcode");
+
+	// Guard against entering the export step before changing the export path.
+	tbb::mutex::scoped_lock lock(m_print->state_mutex());
+	this->invalidate_step(bspsGCodeFinalize);
+	m_export_path = path.string();
+	m_upload_job = std::move(upload_job);
+}
+
 void BackgroundSlicingProcess::reset_export()
 {
 	assert(! this->running());
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index bb072fb98..222ed147e 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -9,6 +9,7 @@
 #include <wx/event.h>
 
 #include "libslic3r/Print.hpp"
+#include "slic3r/Utils/PrintHost.hpp"
 
 namespace Slic3r {
 
@@ -86,6 +87,9 @@ public:
 	// Set the export path of the G-code.
 	// Once the path is set, the G-code 
 	void schedule_export(const std::string &path);
+	// Set print host upload job data to be enqueued to the PrintHostJobQueue
+	// after current print slicing is complete
+	void schedule_upload(Slic3r::PrintHostJob upload_job);
 	// Clear m_export_path.
 	void reset_export();
 	// Once the G-code export is scheduled, the apply() methods will do nothing.
@@ -143,6 +147,9 @@ private:
 	// Output path provided by the user. The output path may be set even if the slicing is running,
 	// but once set, it cannot be re-set.
 	std::string 				m_export_path;
+	// Print host upload job to schedule after slicing is complete, used by schedule_upload(),
+	// empty by default (ie. no upload to schedule)
+	PrintHostJob                m_upload_job;
 	// Thread, on which the background processing is executed. The thread will always be present
 	// and ready to execute the slicing process.
 	std::thread		 			m_thread;
diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp
index 119838dad..7f62d0901 100644
--- a/src/slic3r/GUI/GLGizmo.cpp
+++ b/src/slic3r/GUI/GLGizmo.cpp
@@ -1292,9 +1292,11 @@ void GLGizmoMove3D::on_render(const GLCanvas3D::Selection& selection) const
 
         // draw grabbers
         render_grabbers(box);
-        render_grabber_extension(X, box, false);
-        render_grabber_extension(Y, box, false);
-        render_grabber_extension(Z, box, false);
+        for (unsigned int i = 0; i < 3; ++i)
+        {
+            if (m_grabbers[i].enabled)
+                render_grabber_extension((Axis)i, box, false);
+        }
     }
     else
     {
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 73d6a03b0..f4047ae3e 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -27,6 +27,7 @@
 #include "3DScene.hpp"
 
 #include "../Utils/PresetUpdater.hpp"
+#include "../Utils/PrintHost.hpp"
 #include "ConfigWizard_private.hpp"
 #include "slic3r/Config/Snapshot.hpp"
 #include "ConfigSnapshotDialog.hpp"
@@ -72,6 +73,7 @@ GUI_App::GUI_App()
     : wxApp()
 #if ENABLE_IMGUI
     , m_imgui(new ImGuiWrapper())
+    , m_printhost_queue(new PrintHostJobQueue())
 #endif // ENABLE_IMGUI
 {}
 
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index 9df90259a..875a92456 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -27,6 +27,7 @@ class AppConfig;
 class PresetBundle;
 class PresetUpdater;
 class ModelObject;
+class PrintHostJobQueue;
 
 namespace GUI
 {
@@ -91,6 +92,8 @@ class GUI_App : public wxApp
     std::unique_ptr<ImGuiWrapper> m_imgui;
 #endif // ENABLE_IMGUI
 
+    std::unique_ptr<PrintHostJobQueue> m_printhost_queue;
+
 public:
     bool            OnInit() override;
 
@@ -161,6 +164,8 @@ public:
     ImGuiWrapper* imgui() { return m_imgui.get(); }
 #endif // ENABLE_IMGUI
 
+    PrintHostJobQueue& printhost_queue() { return *m_printhost_queue.get(); }
+
 };
 DECLARE_APP(GUI_App)
 
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index b3dc8ba75..a74d5f72a 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -794,7 +794,7 @@ void MainFrame::update_ui_from_settings()
 {
     bool bp_on = wxGetApp().app_config->get("background_processing") == "1";
     m_menu_item_reslice_now->Enable(bp_on);
-    m_plater->sidebar().show_button(baReslice, !bp_on);
+    m_plater->sidebar().show_reslice(!bp_on);
     m_plater->sidebar().Layout();
     if (m_plater)
         m_plater->update_ui_from_settings();
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index af8769814..67ea8c717 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -54,7 +54,9 @@
 #include "PresetBundle.hpp"
 #include "BackgroundSlicingProcess.hpp"
 #include "ProgressStatusBar.hpp"
+#include "PrintHostDialogs.hpp"
 #include "../Utils/ASCIIFolding.hpp"
+#include "../Utils/PrintHost.hpp"
 #include "../Utils/FixModelByWin10.hpp"
 
 #include <wx/glcanvas.h>    // Needs to be last because reasons :-/
@@ -64,6 +66,7 @@ using boost::optional;
 namespace fs = boost::filesystem;
 using Slic3r::_3DScene;
 using Slic3r::Preset;
+using Slic3r::PrintHostJob;
 
 
 namespace Slic3r {
@@ -447,7 +450,6 @@ struct Sidebar::priv
 
     wxButton *btn_export_gcode;
     wxButton *btn_reslice;
-    // wxButton *btn_print;  // XXX: remove
     wxButton *btn_send_gcode;
 
     priv(Plater *plater) : plater(plater) {}
@@ -543,13 +545,11 @@ Sidebar::Sidebar(Plater *parent)
     p->object_settings->Hide();
     p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20);
 
-    // Buttons in the scrolled area
     wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG);
-    p->btn_send_gcode = new wxButton(p->scrolled, wxID_ANY, _(L("Send to printer")));
+    p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer")));
     p->btn_send_gcode->SetBitmap(arrow_up);
+    p->btn_send_gcode->SetFont(wxGetApp().bold_font());
     p->btn_send_gcode->Hide();
-    auto *btns_sizer_scrolled = new wxBoxSizer(wxHORIZONTAL);
-    btns_sizer_scrolled->Add(p->btn_send_gcode);
 
     // Info boxes
     p->object_info = new ObjectInfo(p->scrolled);
@@ -559,7 +559,6 @@ Sidebar::Sidebar(Plater *parent)
     scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, 2);
     scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND);
     scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, 20);
-    scrolled_sizer->Add(btns_sizer_scrolled, 0, wxEXPAND, 0);
     scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, 20);
 
     // Buttons underneath the scrolled area
@@ -571,6 +570,7 @@ Sidebar::Sidebar(Plater *parent)
 
     auto *btns_sizer = new wxBoxSizer(wxVERTICAL);
     btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, 5);
+    btns_sizer->Add(p->btn_send_gcode, 0, wxEXPAND | wxTOP, 5);
     btns_sizer->Add(p->btn_export_gcode, 0, wxEXPAND | wxTOP, 5);
 
     auto *sizer = new wxBoxSizer(wxVERTICAL);
@@ -820,14 +820,6 @@ void Sidebar::show_sliced_info_sizer(const bool show)
     p->scrolled->Refresh();
 }
 
-void Sidebar::show_buttons(const bool show)
-{
-    p->btn_reslice->Show(show);
-    TabPrinter *tab = dynamic_cast<TabPrinter*>(wxGetApp().get_tab(Preset::TYPE_PRINTER));
-	if (tab && p->plater->printer_technology() == ptFFF)
-        p->btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty());
-}
-
 void Sidebar::enable_buttons(bool enable)
 {
     p->btn_reslice->Enable(enable);
@@ -835,23 +827,8 @@ void Sidebar::enable_buttons(bool enable)
     p->btn_send_gcode->Enable(enable);
 }
 
-void Sidebar::show_button(ButtonAction but_action, bool show)
-{
-    switch (but_action)
-    {
-    case baReslice:
-        p->btn_reslice->Show(show);
-        break;
-    case baExportGcode:
-        p->btn_export_gcode->Show(show);
-        break;
-    case baSendGcode:
-        p->btn_send_gcode->Show(show);
-        break;
-    default:
-        break;
-    }
-}
+void Sidebar::show_reslice(bool show) { p->btn_reslice->Show(show); }
+void Sidebar::show_send(bool show) { p->btn_send_gcode->Show(show); }
 
 bool Sidebar::is_multifilament()
 {
@@ -1008,6 +985,7 @@ struct Plater::priv
     };
     // returns bit mask of UpdateBackgroundProcessReturnState
     unsigned int update_background_process();
+    void export_gcode(fs::path output_path, PrintHostJob upload_job);
     void async_apply_config();
     void reload_from_disk();
     void fix_through_netfabb(const int obj_idx);
@@ -2059,6 +2037,45 @@ unsigned int Plater::priv::update_background_process()
     return return_state;
 }
 
+void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job)
+{
+    wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty");
+
+    if (model.objects.empty())
+        return;
+
+    if (background_process.is_export_scheduled()) {
+        GUI::show_error(q, _(L("Another export job is currently running.")));
+        return;
+    }
+
+    // bitmask of UpdateBackgroundProcessReturnState
+    unsigned int state = update_background_process();
+    if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
+#if ENABLE_REMOVE_TABS_FROM_PLATER
+        view3D->reload_scene(false);
+#else
+        canvas3D->reload_scene(false);
+#endif // ENABLE_REMOVE_TABS_FROM_PLATER
+    if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
+        return;
+
+    if (! output_path.empty()) {
+        background_process.schedule_export(output_path.string());
+    } else {
+        background_process.schedule_upload(std::move(upload_job));
+    }
+
+    if (! background_process.running()) {
+        // The print is valid and it should be started.
+        if (background_process.start())
+            statusbar()->set_cancel_callback([this]() {
+                statusbar()->set_status_text(L("Cancelling"));
+                background_process.stop();
+            });
+    }
+}
+
 void Plater::priv::async_apply_config()
 {
     // bitmask of UpdateBackgroundProcessReturnState
@@ -2912,42 +2929,26 @@ void Plater::export_gcode(fs::path output_path)
     if (p->model.objects.empty())
         return;
 
-    if (this->p->background_process.is_export_scheduled()) {
-        GUI::show_error(this, _(L("Another export job is currently running.")));
-        return;
-    }
-
-    // bitmask of UpdateBackgroundProcessReturnState
-    unsigned int state = this->p->update_background_process();
-    if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
-#if ENABLE_REMOVE_TABS_FROM_PLATER
-        this->p->view3D->reload_scene(false);
-#else
-        this->p->canvas3D->reload_scene(false);
-#endif // ENABLE_REMOVE_TABS_FROM_PLATER
-    if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
-        return;
-
     // select output file
     if (output_path.empty()) {
         // XXX: take output path from CLI opts? Ancient Slic3r versions used to do that...
 
         // If possible, remove accents from accented latin characters.
         // This function is useful for generating file names to be processed by legacy firmwares.
-		fs::path default_output_file;
+        fs::path default_output_file;
         try {
-			default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string());
+            default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string());
         } catch (const std::exception &ex) {
             show_error(this, ex.what());
             return;
         }
-		default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
+        default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
         auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string());
 
-		wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")),
+        wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")),
             start_dir,
             default_output_file.filename().string(),
-			GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
+            GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
             wxFD_SAVE | wxFD_OVERWRITE_PROMPT
         );
 
@@ -2958,23 +2959,15 @@ void Plater::export_gcode(fs::path output_path)
         }
     } else {
         try {
-			output_path = this->p->background_process.current_print()->output_filepath(output_path.string());
+            output_path = this->p->background_process.current_print()->output_filepath(output_path.string());
         } catch (const std::exception &ex) {
             show_error(this, ex.what());
             return;
         }
     }
 
-    if (! output_path.empty())
-        this->p->background_process.schedule_export(output_path.string());
-
-    if ((! output_path.empty() || this->p->background_processing_enabled()) && ! this->p->background_process.running()) {
-        // The print is valid and it should be started.
-        if (this->p->background_process.start())
-            this->p->statusbar()->set_cancel_callback([this]() {
-                this->p->statusbar()->set_status_text(L("Cancelling"));
-                this->p->background_process.stop();
-            });
+    if (! output_path.empty()) {
+        p->export_gcode(std::move(output_path), PrintHostJob());
     }
 }
 
@@ -3077,7 +3070,28 @@ void Plater::reslice()
 
 void Plater::send_gcode()
 {
-//    p->send_gcode_file = export_gcode();
+    if (p->model.objects.empty()) { return; }
+
+    PrintHostJob upload_job(p->config);
+    if (upload_job.empty()) { return; }
+
+    // Obtain default output path
+    fs::path default_output_file;
+    try {
+        default_output_file = this->p->background_process.current_print()->output_filepath("");
+    } catch (const std::exception &ex) {
+        show_error(this, ex.what());
+        return;
+    }
+    default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
+
+    Slic3r::PrintHostSendDialog dlg(default_output_file);
+    if (dlg.ShowModal() == wxID_OK) {
+        upload_job.upload_data.upload_path = dlg.filename();
+        upload_job.upload_data.start_print = dlg.start_print();
+
+        p->export_gcode(fs::path(), std::move(upload_job));
+    }
 }
 
 void Plater::on_extruders_change(int num_extruders)
@@ -3127,14 +3141,6 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
             opt_key == "single_extruder_multi_material") {
             update_scheduled = true;
         } 
-//         else if(opt_key == "serial_port") {
-//             sidebar()->p->btn_print->Show(config.get("serial_port"));  // ???: btn_print is removed
-//             Layout();
-//         } 
-        else if (opt_key == "print_host") {
-            sidebar().show_button(baReslice, !p->config->option<ConfigOptionString>(opt_key)->value.empty());
-            Layout();
-        }
         else if(opt_key == "variable_layer_height") {
             if (p->config->opt_bool("variable_layer_height") != true) {
 #if ENABLE_REMOVE_TABS_FROM_PLATER
@@ -3174,6 +3180,11 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
         }
     }
 
+    {
+        const auto prin_host_opt = p->config->option<ConfigOptionString>("print_host");
+        p->sidebar->show_send(prin_host_opt != nullptr && !prin_host_opt->value.empty());
+    }
+
     if (update_scheduled) 
         update();
 
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 96755ab10..334ab740a 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -55,14 +55,6 @@ private:
     int extruder_idx = -1;
 };
 
-enum ButtonAction
-{
-    baUndef,
-    baReslice,
-    baExportGcode,
-    baSendGcode
-};
-
 class Sidebar : public wxPanel
 {
     /*ConfigMenuIDs*/int    m_mode;
@@ -88,9 +80,9 @@ public:
     void                    update_objects_list_extruder_column(int extruders_count);
     void                    show_info_sizer();
     void                    show_sliced_info_sizer(const bool show);
-    void                    show_buttons(const bool show);
-    void                    show_button(ButtonAction but_action, bool show);
     void                    enable_buttons(bool enable);
+    void                    show_reslice(bool show);
+    void                    show_send(bool show);
     bool                    is_multifilament();
     void                    set_mode_value(const /*ConfigMenuIDs*/int mode) { m_mode = mode; }
 
@@ -103,6 +95,8 @@ private:
 class Plater: public wxPanel
 {
 public:
+    using fs_path = boost::filesystem::path;
+
     Plater(wxWindow *parent, MainFrame *main_frame);
     Plater(Plater &&) = delete;
     Plater(const Plater &) = delete;
diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp
new file mode 100644
index 000000000..a5de7c3c6
--- /dev/null
+++ b/src/slic3r/GUI/PrintHostDialogs.cpp
@@ -0,0 +1,49 @@
+#include "PrintHostDialogs.hpp"
+
+#include <wx/frame.h>
+#include <wx/event.h>
+#include <wx/progdlg.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/checkbox.h>
+
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/MsgDialog.hpp"
+#include "slic3r/GUI/I18N.hpp"
+
+namespace fs = boost::filesystem;
+
+namespace Slic3r {
+
+PrintHostSendDialog::PrintHostSendDialog(const fs::path &path)
+    : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE)
+    , txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring()))
+    , box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))))
+{
+    auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
+    label_dir_hint->Wrap(CONTENT_WIDTH);
+
+    content_sizer->Add(txt_filename, 0, wxEXPAND);
+    content_sizer->Add(label_dir_hint);
+    content_sizer->AddSpacer(VERT_SPACING);
+    content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
+
+    btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
+
+    txt_filename->SetFocus();
+    wxString stem(path.stem().wstring());
+    txt_filename->SetSelection(0, stem.Length());
+
+    Fit();
+}
+
+fs::path PrintHostSendDialog::filename() const
+{
+    return fs::path(txt_filename->GetValue().wx_str());
+}
+
+bool PrintHostSendDialog::start_print() const
+{
+    return box_print->GetValue(); }
+}
diff --git a/src/slic3r/Utils/PrintHostSendDialog.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp
similarity index 60%
rename from src/slic3r/Utils/PrintHostSendDialog.hpp
rename to src/slic3r/GUI/PrintHostDialogs.hpp
index dc4a8d6f7..d27fbe576 100644
--- a/src/slic3r/Utils/PrintHostSendDialog.hpp
+++ b/src/slic3r/GUI/PrintHostDialogs.hpp
@@ -20,19 +20,30 @@
 
 namespace Slic3r {
 
+
 class PrintHostSendDialog : public GUI::MsgDialog
 {
-private:
-	wxTextCtrl *txt_filename;
-	wxCheckBox *box_print;
-	bool can_start_print;
-
 public:
-	PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
-	boost::filesystem::path filename() const;
-	bool print() const;
+    PrintHostSendDialog(const boost::filesystem::path &path);
+    boost::filesystem::path filename() const;
+    bool start_print() const;
+
+private:
+    wxTextCtrl *txt_filename;
+    wxCheckBox *box_print;
+    bool can_start_print;
 };
 
+
+class PrintHostQueueDialog : public wxDialog
+{
+public:
+    PrintHostQueueDialog();
+
+private:
+};
+
+
 }
 
 #endif
diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp
index c242e918e..4eda7bd46 100644
--- a/src/slic3r/Utils/Duet.cpp
+++ b/src/slic3r/Utils/Duet.cpp
@@ -1,5 +1,4 @@
 #include "Duet.hpp"
-#include "PrintHostSendDialog.hpp"
 
 #include <algorithm>
 #include <ctime>
@@ -21,6 +20,7 @@
 #include "slic3r/GUI/GUI.hpp"
 #include "slic3r/GUI/I18N.hpp"
 #include "slic3r/GUI/MsgDialog.hpp"
+#include "slic3r/GUI/PrintHostDialogs.hpp"   // XXX
 #include "Http.hpp"
 
 namespace fs = boost::filesystem;
@@ -62,10 +62,10 @@ bool Duet::send_gcode(const std::string &filename) const
 	const auto errortitle = _(L("Error while uploading to the Duet"));
 	fs::path filepath(filename);
 
-	PrintHostSendDialog send_dialog(filepath.filename(), true);
+	PrintHostSendDialog send_dialog(filepath.filename());
 	if (send_dialog.ShowModal() != wxID_OK) { return false; }
 
-	const bool print = send_dialog.print(); 
+	const bool print = send_dialog.start_print();
 	const auto upload_filepath = send_dialog.filename();
 	const auto upload_filename = upload_filepath.filename();
 	const auto upload_parent_path = upload_filepath.parent_path();
@@ -136,6 +136,11 @@ bool Duet::send_gcode(const std::string &filename) const
 	return res;
 }
 
+bool Duet::upload(PrintHostUpload upload_data) const
+{
+	throw "unimplemented";
+}
+
 bool Duet::has_auto_discovery() const
 {
 	return false;
diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp
index bc210d7a4..db21fd0a1 100644
--- a/src/slic3r/Utils/Duet.hpp
+++ b/src/slic3r/Utils/Duet.hpp
@@ -24,6 +24,7 @@ public:
 	wxString get_test_failed_msg (wxString &msg) const;
 	// Send gcode file to duet, filename is expected to be in UTF-8
 	bool send_gcode(const std::string &filename) const;
+	bool upload(PrintHostUpload upload_data) const;
 	bool has_auto_discovery() const;
 	bool can_test() const;
 private:
diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp
index d975578fd..b2e2d4d45 100644
--- a/src/slic3r/Utils/OctoPrint.cpp
+++ b/src/slic3r/Utils/OctoPrint.cpp
@@ -1,14 +1,14 @@
 #include "OctoPrint.hpp"
-#include "PrintHostSendDialog.hpp"
 
 #include <algorithm>
 #include <boost/format.hpp>
 #include <boost/log/trivial.hpp>
 
 #include "libslic3r/PrintConfig.hpp"
+#include "slic3r/GUI/I18N.hpp"
+#include "slic3r/GUI/PrintHostDialogs.hpp"   // XXX
 #include "Http.hpp"
 
-#include "slic3r/GUI/I18N.hpp"
 
 namespace fs = boost::filesystem;
 
@@ -66,10 +66,10 @@ bool OctoPrint::send_gcode(const std::string &filename) const
 	const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
 	fs::path filepath(filename);
 
-	PrintHostSendDialog send_dialog(filepath.filename(), true);
+	PrintHostSendDialog send_dialog(filepath.filename());
 	if (send_dialog.ShowModal() != wxID_OK) { return false; }
 
-	const bool print = send_dialog.print();
+	const bool print = send_dialog.start_print();
 	const auto upload_filepath = send_dialog.filename();
 	const auto upload_filename = upload_filepath.filename();
 	const auto upload_parent_path = upload_filepath.parent_path();
@@ -101,7 +101,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const
 	auto http = Http::post(std::move(url));
 	set_auth(http);
 	http.form_add("print", print ? "true" : "false")
-		.form_add("path", upload_parent_path.string())
+		.form_add("path", upload_parent_path.string())      // XXX: slashes on windows ???
 		.form_add_file("file", filename, upload_filename.string())
 		.on_complete([&](std::string body, unsigned status) {
 			BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body;
@@ -129,6 +129,11 @@ bool OctoPrint::send_gcode(const std::string &filename) const
 	return res;
 }
 
+bool OctoPrint::upload(PrintHostUpload upload_data) const
+{
+	throw "unimplemented";
+}
+
 bool OctoPrint::has_auto_discovery() const
 {
 	return true;
diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp
index f6c4d58c8..314e4cfae 100644
--- a/src/slic3r/Utils/OctoPrint.hpp
+++ b/src/slic3r/Utils/OctoPrint.hpp
@@ -24,6 +24,7 @@ public:
 	wxString get_test_failed_msg (wxString &msg) const;
 	// Send gcode file to octoprint, filename is expected to be in UTF-8
 	bool send_gcode(const std::string &filename) const;
+	bool upload(PrintHostUpload upload_data) const;
 	bool has_auto_discovery() const;
 	bool can_test() const;
 private:
diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp
index dd72bae40..570d72f68 100644
--- a/src/slic3r/Utils/PrintHost.cpp
+++ b/src/slic3r/Utils/PrintHost.cpp
@@ -1,7 +1,15 @@
 #include "OctoPrint.hpp"
 #include "Duet.hpp"
 
+#include <vector>
+#include <thread>
+#include <boost/optional.hpp>
+
 #include "libslic3r/PrintConfig.hpp"
+#include "libslic3r/Channel.hpp"
+
+using boost::optional;
+
 
 namespace Slic3r {
 
@@ -10,13 +18,42 @@ PrintHost::~PrintHost() {}
 
 PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
 {
-	PrintHostType kind = config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
-	if (kind == htOctoPrint) {
-		return new OctoPrint(config);
-	} else if (kind == htDuet) {
-		return new Duet(config);
-	}
-	return nullptr;
+    PrintHostType kind = config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
+    if (kind == htOctoPrint) {
+        return new OctoPrint(config);
+    } else if (kind == htDuet) {
+        return new Duet(config);
+    }
+    return nullptr;
+}
+
+
+struct PrintHostJobQueue::priv
+{
+    std::vector<PrintHostJob> jobs;
+    Channel<unsigned> channel;
+
+    std::thread bg_thread;
+    optional<PrintHostJob> bg_job;
+};
+
+PrintHostJobQueue::PrintHostJobQueue()
+    : p(new priv())
+{
+    std::shared_ptr<priv> p2 = p;
+    p->bg_thread = std::thread([p2]() {
+        // Wait for commands on the channel:
+        auto cmd = p2->channel.pop();
+        // TODO
+    });
+}
+
+PrintHostJobQueue::~PrintHostJobQueue()
+{
+    // TODO: stop the thread
+    // if (p && p->bg_thread.joinable()) {
+    //     p->bg_thread.detach();
+    // }
 }
 
 
diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp
index bc828ea46..53f7c43d3 100644
--- a/src/slic3r/Utils/PrintHost.hpp
+++ b/src/slic3r/Utils/PrintHost.hpp
@@ -3,31 +3,87 @@
 
 #include <memory>
 #include <string>
+#include <boost/filesystem/path.hpp>
+
 #include <wx/string.h>
 
 
 namespace Slic3r {
 
-
 class DynamicPrintConfig;
 
+
+struct PrintHostUpload
+{
+    boost::filesystem::path source_path;
+    boost::filesystem::path upload_path;
+    bool start_print = false;
+};
+
+
 class PrintHost
 {
 public:
-	virtual ~PrintHost();
+    virtual ~PrintHost();
 
-	virtual bool test(wxString &curl_msg) const = 0;
-	virtual wxString get_test_ok_msg () const = 0;
-	virtual wxString get_test_failed_msg (wxString &msg) const = 0;
-	// Send gcode file to print host, filename is expected to be in UTF-8
-	virtual bool send_gcode(const std::string &filename) const = 0;
-	virtual bool has_auto_discovery() const = 0;
-	virtual bool can_test() const = 0;
+    virtual bool test(wxString &curl_msg) const = 0;
+    virtual wxString get_test_ok_msg () const = 0;
+    virtual wxString get_test_failed_msg (wxString &msg) const = 0;
+    // Send gcode file to print host, filename is expected to be in UTF-8
+    virtual bool send_gcode(const std::string &filename) const = 0;         // XXX: remove in favor of upload()
+    virtual bool upload(PrintHostUpload upload_data) const = 0;
+    virtual bool has_auto_discovery() const = 0;
+    virtual bool can_test() const = 0;
 
-	static PrintHost* get_print_host(DynamicPrintConfig *config);
+    static PrintHost* get_print_host(DynamicPrintConfig *config);
 };
 
 
+struct PrintHostJob
+{
+    PrintHostUpload upload_data;
+    std::unique_ptr<PrintHost> printhost;
+
+    PrintHostJob() {}
+    PrintHostJob(const PrintHostJob&) = delete;
+    PrintHostJob(PrintHostJob &&other)
+        : upload_data(std::move(other.upload_data))
+        , printhost(std::move(other.printhost))
+    {}
+
+    PrintHostJob(DynamicPrintConfig *config)
+        : printhost(PrintHost::get_print_host(config))
+    {}
+
+    PrintHostJob& operator=(const PrintHostJob&) = delete;
+    PrintHostJob& operator=(PrintHostJob &&other)
+    {
+        upload_data = std::move(other.upload_data);
+        printhost = std::move(other.printhost);
+        return *this;
+    }
+
+    bool empty() const { return !printhost; }
+    operator bool() const { return !!printhost; }
+};
+
+
+class PrintHostJobQueue
+{
+public:
+    PrintHostJobQueue();
+    PrintHostJobQueue(const PrintHostJobQueue &) = delete;
+    PrintHostJobQueue(PrintHostJobQueue &&other) = delete;
+    ~PrintHostJobQueue();
+
+    PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete;
+    PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete;
+
+private:
+    struct priv;
+    std::shared_ptr<priv> p;
+};
+
 
 
 }
diff --git a/src/slic3r/Utils/PrintHostSendDialog.cpp b/src/slic3r/Utils/PrintHostSendDialog.cpp
deleted file mode 100644
index 84428d3b5..000000000
--- a/src/slic3r/Utils/PrintHostSendDialog.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#include "PrintHostSendDialog.hpp"
-
-#include <wx/frame.h>
-#include <wx/event.h>
-#include <wx/progdlg.h>
-#include <wx/sizer.h>
-#include <wx/stattext.h>
-#include <wx/textctrl.h>
-#include <wx/checkbox.h>
-
-#include "slic3r/GUI/GUI.hpp"
-#include "slic3r/GUI/MsgDialog.hpp"
-#include "slic3r/GUI/I18N.hpp"
-
-namespace fs = boost::filesystem;
-
-namespace Slic3r {
-
-PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) :
-	MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE),
-	txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())),
-	box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))),
-	can_start_print(can_start_print)
-{
-	auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
-	label_dir_hint->Wrap(CONTENT_WIDTH);
-
-	content_sizer->Add(txt_filename, 0, wxEXPAND);
-	content_sizer->Add(label_dir_hint);
-	content_sizer->AddSpacer(VERT_SPACING);
-	content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
-
-	btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
-
-	txt_filename->SetFocus();
-	wxString stem(path.stem().wstring());
-	txt_filename->SetSelection(0, stem.Length());
-
-	box_print->Enable(can_start_print);
-
-	Fit();
-}
-
-fs::path PrintHostSendDialog::filename() const 
-{
-	return fs::path(txt_filename->GetValue().wx_str());
-}
-
-bool PrintHostSendDialog::print() const 
-{ 
-	return box_print->GetValue(); }
-}