From a123099f804e869e5e11d643a25993f15b98d33c Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Fri, 1 Mar 2019 11:00:34 +0100
Subject: [PATCH 01/11] Implemented another behavior of the "Slice Now" /
 "Export/Send G-code" buttons (SPE-831)

---
 src/slic3r/GUI/3DScene.cpp                  |  7 +-
 src/slic3r/GUI/BackgroundSlicingProcess.hpp |  3 +
 src/slic3r/GUI/GLCanvas3D.cpp               | 55 +++++++++----
 src/slic3r/GUI/GLCanvas3D.hpp               |  5 +-
 src/slic3r/GUI/MainFrame.cpp                |  3 +-
 src/slic3r/GUI/Plater.cpp                   | 91 ++++++++++++++++++++-
 src/slic3r/GUI/Plater.hpp                   |  7 +-
 7 files changed, 145 insertions(+), 26 deletions(-)

diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index 88815d9a6..f88b4e73d 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -834,6 +834,8 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
     ModelInstance::EPrintVolumeState state = ModelInstance::PVS_Inside;
     bool all_contained = true;
 
+    bool contained_min_one = false;
+
     for (GLVolume* volume : this->volumes)
     {
         if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled))
@@ -843,6 +845,9 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
         bool contained = print_volume.contains(bb);
         all_contained &= contained;
 
+        if (contained)
+            contained_min_one = true;
+
         volume->is_outside = !contained;
 
         if ((state == ModelInstance::PVS_Inside) && volume->is_outside)
@@ -855,7 +860,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
     if (out_state != nullptr)
         *out_state = state;
 
-    return all_contained;
+    return /*all_contained*/ contained_min_one; // #ys_FIXME_delete_after_testing
 }
 
 void GLVolumeCollection::reset_outside_state()
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index a2299e7bf..a15d3faef 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -123,6 +123,9 @@ public:
     // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs),
     // and it does not account for the OctoPrint scheduling.
     bool    finished() const { return m_print->finished(); }
+    
+    // set status line
+    void    set_status(const std::string & status_str) const { m_print->set_status(100, status_str); }
 
 private:
 	void 	thread_proc();
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 4f456c392..b29bb16e0 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -3067,7 +3067,7 @@ GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const
     return (it != m_gizmos.end()) ? it->second : nullptr;
 }
 
-const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 9, 91, 134 };
+const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 120, 120, 120 };//{ 9, 91, 134 };
 const unsigned char GLCanvas3D::WarningTexture::Opacity = 255;
 
 GLCanvas3D::WarningTexture::WarningTexture()
@@ -3101,16 +3101,23 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
 
     // Look at the end of our vector and generate proper texture.
     std::string text;
+    bool red_colored = false;
     switch (m_warnings.back()) {
         case ObjectOutside      : text = L("Detected object outside print volume"); break;
         case ToolpathOutside    : text = L("Detected toolpath outside print volume"); break;
         case SomethingNotShown  : text = L("Some objects are not visible when editing supports"); break;
+        case ObjectClashed: {
+            text = L("Detected object outside print volume\n"
+                     "Resolve a clash to continue slicing/export process correctly"); 
+            red_colored = true;
+            break;
+        }
     }
 
-    _generate(text, canvas); // GUI::GLTexture::reset() is called at the beginning of generate(...)
+    _generate(text, canvas, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...)
 }
 
-bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanvas3D& canvas)
+bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored/* = false*/)
 {
     reset();
 
@@ -3127,7 +3134,8 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanva
 
     // calculates texture size
     wxCoord w, h;
-    memDC.GetTextExtent(msg, &w, &h);
+//     memDC.GetTextExtent(msg, &w, &h);
+    memDC.GetMultiLineTextExtent(msg, &w, &h);
 
     int pow_of_two_size = next_highest_power_of_2(std::max<unsigned int>(w, h));
 
@@ -3144,8 +3152,9 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanva
     memDC.Clear();
 
     // draw message
-    memDC.SetTextForeground(*wxWHITE);
-    memDC.DrawText(msg, 0, 0);
+    memDC.SetTextForeground(red_colored ? wxColour(255,72,65/*204,204*/) : *wxWHITE);
+//     memDC.DrawText(msg, 0, 0);
+    memDC.DrawLabel(msg, wxRect(0,0, m_original_width, m_original_height), wxALIGN_CENTER);
 
     memDC.SelectObject(wxNullBitmap);
 
@@ -4392,23 +4401,33 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
     if (!m_volumes.empty())
     {
         ModelInstance::EPrintVolumeState state;
-        bool contained = m_volumes.check_outside_state(m_config, &state);
 
-        if (!contained)
-        {
-            _set_warning_texture(WarningTexture::ObjectOutside, true);
-            post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside));
-        }
-        else
-        {
-            m_volumes.reset_outside_state();
-            _set_warning_texture(WarningTexture::ObjectOutside, false);
-            post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty()));
-        }
+        const bool contained_min_one = m_volumes.check_outside_state(m_config, &state);
+
+        _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstance::PVS_Partly_Outside);
+        _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstance::PVS_Fully_Outside);
+
+        post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, 
+                               contained_min_one && !m_model->objects.empty() && state != ModelInstance::PVS_Partly_Outside));
+
+// #ys_FIXME_delete_after_testing
+//         bool contained = m_volumes.check_outside_state(m_config, &state);
+//         if (!contained)
+//         {
+//             _set_warning_texture(WarningTexture::ObjectOutside, true);
+//             post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside));
+//         }
+//         else
+//         {
+//             m_volumes.reset_outside_state();
+//             _set_warning_texture(WarningTexture::ObjectOutside, false);
+//             post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty()));
+//         }
     }
     else
     {
         _set_warning_texture(WarningTexture::ObjectOutside, false);
+        _set_warning_texture(WarningTexture::ObjectClashed, false);
         post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
     }
 
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 008e77056..614f75ac2 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -751,7 +751,8 @@ private:
         enum Warning {
             ObjectOutside,
             ToolpathOutside,
-            SomethingNotShown
+            SomethingNotShown,
+            ObjectClashed
         };
 
         // Sets a warning of the given type to be active/inactive. If several warnings are active simultaneously,
@@ -770,7 +771,7 @@ private:
         std::vector<Warning> m_warnings;
 
         // Generates the texture with given text.
-        bool _generate(const std::string& msg, const GLCanvas3D& canvas);
+        bool _generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored = false);
     };
 
     class LegendTexture : public GUI::GLTexture
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index cddf081eb..1d1982ffa 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -881,9 +881,10 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const
 // Update the UI based on the current preferences.
 void MainFrame::update_ui_from_settings()
 {
-    bool bp_on = wxGetApp().app_config->get("background_processing") == "1";
+    const bool bp_on = wxGetApp().app_config->get("background_processing") == "1";
 //     m_menu_item_reslice_now->Enable(!bp_on);
     m_plater->sidebar().show_reslice(!bp_on);
+    m_plater->sidebar().show_export(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 e917269d2..784443c64 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -487,6 +487,12 @@ ConfigOptionsGroup* FreqChangedParams::get_og(const bool is_fff)
 
 // Sidebar / private
 
+enum class ActionButtonType : int {
+    abReslice,
+    abExport,
+    abSendGCode
+};
+
 struct Sidebar::priv
 {
     Plater *plater;
@@ -942,8 +948,9 @@ void Sidebar::enable_buttons(bool enable)
     p->btn_send_gcode->Enable(enable);
 }
 
-void Sidebar::show_reslice(bool show) { p->btn_reslice->Show(show); }
-void Sidebar::show_send(bool show) { p->btn_send_gcode->Show(show); }
+void Sidebar::show_reslice(bool show)   const { p->btn_reslice->Show(show); }
+void Sidebar::show_export(bool show)    const { p->btn_export_gcode->Show(show); }
+void Sidebar::show_send(bool show)      const { p->btn_send_gcode->Show(show); }
 
 bool Sidebar::is_multifilament()
 {
@@ -1033,6 +1040,8 @@ struct Plater::priv
 
     wxTimer                     background_process_timer;
 
+    std::string                 label_btn_export;
+
     static const std::regex pattern_bundle;
     static const std::regex pattern_3mf;
     static const std::regex pattern_zip_amf;
@@ -1122,6 +1131,7 @@ struct Plater::priv
     void on_3dcanvas_mouse_dragging_finished(SimpleEvent&);
 
     void update_object_menu();
+    void show_action_buttons(const bool is_ready_to_slice) const;
 
     // Set the bed shape to a single closed 2D polygon(array of two element arrays),
     // triangulate the bed and store the triangles into m_bed.m_triangles,
@@ -2093,6 +2103,31 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
     // jinak background_process.running() -> Zobraz "Slicing ..."
     // jinak pokud ! background_process.empty() && ! background_process.finished() -> je neco ke slajsovani (povol tlacitko) "Slice Now"
 
+    if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
+    {
+        const wxString invalid_str = _(L("Invalid data"));
+        for (auto btn : {ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport})
+            sidebar->set_btn_label(btn, invalid_str);
+    }
+    else
+    {
+        if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ||
+            (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 )
+            background_process.set_status("Ready to slice");
+
+        sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export));
+        sidebar->set_btn_label(ActionButtonType::abSendGCode, _(L("Send G-code")));
+        
+        const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? 
+                                      _(L("Slicing")) + dots : _(L("Slice now"));
+        sidebar->set_btn_label(ActionButtonType::abReslice, slice_string);
+
+        if (background_process.finished())
+            show_action_buttons(false);
+        else if (!background_process.empty())
+            show_action_buttons(true);
+    }
+
     return return_state;
 }
 
@@ -2388,6 +2423,9 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
             this->update_sla_scene();
         break;
     }
+
+    if (wxGetApp().get_mode() == comSimple)
+        show_action_buttons(false);
 }
 
 void Plater::priv::on_layer_editing_toggled(bool enable)
@@ -2755,6 +2793,37 @@ void Plater::priv::update_object_menu()
 #endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS
 }
 
+void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const 
+{
+    wxWindowUpdateLocker noUpdater(sidebar);
+    const auto prin_host_opt = config->option<ConfigOptionString>("print_host");
+    const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty();
+
+    // when a background processing is ON, export_btn and/or send_btn are showing 
+    if (wxGetApp().app_config->get("background_processing") == "1")
+    {
+        sidebar->show_reslice(false);
+        sidebar->show_export(true);
+        sidebar->show_send(send_gcode_shown);
+    }
+    else
+    {
+        sidebar->show_reslice(is_ready_to_slice);
+        sidebar->show_export(!is_ready_to_slice);
+        sidebar->show_send(send_gcode_shown && !is_ready_to_slice);
+    }
+}
+
+void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
+{
+    switch (btn_type)
+    {
+        case ActionButtonType::abReslice:   p->btn_reslice->SetLabelText(label);        break;
+        case ActionButtonType::abExport:    p->btn_export_gcode->SetLabelText(label);   break;
+        case ActionButtonType::abSendGCode: p->btn_send_gcode->SetLabelText(label);     break;
+    }
+}
+
 // Plater / Public
 
 Plater::Plater(wxWindow *parent, MainFrame *main_frame)
@@ -3081,6 +3150,22 @@ void Plater::reslice()
     this->p->background_process.set_task(PrintBase::TaskParams());
     // Only restarts if the state is valid.
     this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART);
+
+    if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
+        return;
+
+    if (p->background_process.running())
+    {
+        if (wxGetApp().get_mode() == comSimple)
+            p->sidebar->set_btn_label(ActionButtonType::abReslice, _(L("Slicing")) + dots);
+        else
+        {
+            p->sidebar->set_btn_label(ActionButtonType::abReslice, _(L("Slice now")));
+            p->show_action_buttons(false);
+        }
+    }
+    else if (!p->background_process.empty() && !p->background_process.idle())
+        p->show_action_buttons(true);
 }
 
 void Plater::reslice_SLA_supports(const ModelObject &object)
@@ -3282,6 +3367,8 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology)
     }
     //FIXME for SLA synchronize 
     //p->background_process.apply(Model)!
+
+    p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); // #ys_FIXME_rename
 }
 
 void Plater::changed_object(int obj_idx)
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 4d489c82a..3370dd92f 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -38,6 +38,7 @@ class GLCanvas3D;
 using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
 
 class Plater;
+enum class ActionButtonType : int;
 
 class PresetComboBox : public wxBitmapComboBox
 {
@@ -86,8 +87,10 @@ public:
     void                    show_info_sizer();
     void                    show_sliced_info_sizer(const bool show);
     void                    enable_buttons(bool enable);
-    void                    show_reslice(bool show);
-    void                    show_send(bool show);
+    void                    set_btn_label(const ActionButtonType btn_type, const wxString& label) const;
+    void                    show_reslice(bool show) const;
+    void                    show_export(bool show) const;
+    void                    show_send(bool show) const;
     bool                    is_multifilament();
     void                    set_mode_value(const /*ConfigOptionMode*/int mode) { m_mode = mode; }
 

From 84a96d3ba068fee41dde516d1ad1e50d6ede2cb6 Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Fri, 1 Mar 2019 12:03:14 +0100
Subject: [PATCH 02/11] Added a tooltip for the "Slice now" button and changed
 its behavior according to a hold of Shift + some code refactoring

---
 src/slic3r/GUI/GUI_App.cpp | 15 +--------------
 src/slic3r/GUI/Plater.cpp  | 36 +++++++++++++++++++++++++++++++++---
 src/slic3r/GUI/Plater.hpp  |  7 ++++---
 3 files changed, 38 insertions(+), 20 deletions(-)

diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index eb02d622c..5526add4d 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -525,20 +525,7 @@ void GUI_App::save_mode(const /*ConfigOptionMode*/int mode)
 // Update view mode according to selected menu
 void GUI_App::update_mode()
 {
-    wxWindowUpdateLocker noUpdates(&sidebar());
-
-    const ConfigOptionMode mode = wxGetApp().get_mode();
-
-    obj_list()->get_sizer()->Show(mode > comSimple);
-    sidebar().set_mode_value(mode);
-//    sidebar().show_buttons(mode == comExpert);
-    obj_list()->unselect_objects();
-    obj_list()->update_selections();
-    obj_list()->update_object_menu();
-
-    sidebar().update_mode_sizer(mode);
-
-    sidebar().Layout();
+    sidebar().update_mode();
 
     for (auto tab : tabs_list)
         tab->update_visibility();
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 784443c64..bfde4c5c0 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -655,7 +655,13 @@ Sidebar::Sidebar(Plater *parent)
 
     // Events
     p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(); });
-    p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->reslice(); });
+    p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&)
+    {
+        const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT);
+        p->plater->reslice();
+        if (export_gcode_after_slicing)
+            p->plater->export_gcode();
+    });
     p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
 }
 
@@ -753,9 +759,15 @@ void Sidebar::update_presets(Preset::Type preset_type)
     wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
 }
 
-void Sidebar::update_mode_sizer(const Slic3r::ConfigOptionMode& mode)
+void Sidebar::update_mode_sizer() const
 {
-    p->mode_sizer->SetMode(mode);
+    p->mode_sizer->SetMode(m_mode);
+}
+
+void Sidebar::update_reslice_btn_tooltip() const
+{
+    const wxString tooltip = m_mode == comSimple ? wxEmptyString : _(L("Hold Shift to Slice & Export G-code"));
+    p->btn_reslice->SetToolTip(tooltip);
 }
 
 ObjectManipulation* Sidebar::obj_manipul()
@@ -958,6 +970,24 @@ bool Sidebar::is_multifilament()
 }
 
 
+void Sidebar::update_mode()
+{
+    m_mode = wxGetApp().get_mode();
+
+    update_reslice_btn_tooltip();
+    update_mode_sizer();
+
+    wxWindowUpdateLocker noUpdates(this);
+
+    p->object_list->get_sizer()->Show(m_mode > comSimple);
+
+    p->object_list->unselect_objects();
+    p->object_list->update_selections();
+    p->object_list->update_object_menu();
+    
+    Layout();
+}
+
 std::vector<PresetComboBox*>& Sidebar::combos_filament()
 {
     return p->combos_filament;
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 3370dd92f..78035d0ab 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -62,7 +62,7 @@ private:
 
 class Sidebar : public wxPanel
 {
-    /*ConfigOptionMode*/int    m_mode;
+    ConfigOptionMode    m_mode;
 public:
     Sidebar(Plater *parent);
     Sidebar(Sidebar &&) = delete;
@@ -74,7 +74,8 @@ public:
     void init_filament_combo(PresetComboBox **combo, const int extr_idx);
     void remove_unused_filament_combos(const int current_extruder_count);
     void update_presets(Slic3r::Preset::Type preset_type);
-    void update_mode_sizer(const Slic3r::ConfigOptionMode& mode);
+    void update_mode_sizer() const;
+    void update_reslice_btn_tooltip() const;
 
     ObjectManipulation*     obj_manipul();
     ObjectList*             obj_list();
@@ -92,7 +93,7 @@ public:
     void                    show_export(bool show) const;
     void                    show_send(bool show) const;
     bool                    is_multifilament();
-    void                    set_mode_value(const /*ConfigOptionMode*/int mode) { m_mode = mode; }
+    void                    update_mode();
 
     std::vector<PresetComboBox*>& combos_filament();
 private:

From 95ca670efbdcb6fe94cf16863dbd63f91f52ab36 Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Tue, 5 Mar 2019 14:26:44 +0100
Subject: [PATCH 03/11] Fixed OSX and Linux build

---
 src/slic3r/GUI/Plater.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 5454268bf..587c3579f 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -766,7 +766,7 @@ void Sidebar::update_mode_sizer() const
 
 void Sidebar::update_reslice_btn_tooltip() const
 {
-    const wxString tooltip = m_mode == comSimple ? wxEmptyString : _(L("Hold Shift to Slice & Export G-code"));
+    const wxString tooltip = m_mode == comSimple ? wxString("") : _(L("Hold Shift to Slice & Export G-code"));
     p->btn_reslice->SetToolTip(tooltip);
 }
 

From 063d812d64c43bc94baaa8751b1f673e2101e7bc Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Fri, 8 Mar 2019 15:40:28 +0100
Subject: [PATCH 04/11] Added case of a slicing cancellation

---
 src/slic3r/GUI/Plater.cpp | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 587c3579f..7edc2bae1 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -2458,7 +2458,12 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
         break;
     }
 
-    if (wxGetApp().get_mode() == comSimple)
+    if (canceled) {
+        if (wxGetApp().get_mode() == comSimple)
+            sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
+        show_action_buttons(true);
+    }
+    else if (wxGetApp().get_mode() == comSimple)
         show_action_buttons(false);
 }
 

From 40abbc7184e802a94be327960153a183942d0043 Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Mon, 11 Mar 2019 10:23:59 +0100
Subject: [PATCH 05/11] Fixed an updating of the button's show during SLA
 slicing

---
 src/slic3r/GUI/Plater.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 7edc2bae1..636b916ba 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -2157,7 +2157,11 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
 
         if (background_process.finished())
             show_action_buttons(false);
-        else if (!background_process.empty())
+        else if (!background_process.empty() && 
+                 !background_process.running()) /* Do not update buttons if background process is running
+                                                 * This condition is important for SLA mode especially, 
+                                                 * when this function is called several times during calculations 
+                                                 * */
             show_action_buttons(true);
     }
 

From 75cf1cde9254eb77760b5ada94141c9e53a148b7 Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Tue, 12 Mar 2019 09:41:57 +0100
Subject: [PATCH 06/11] Refactoring of the Slice / Export G-code button

---
 src/slic3r/GUI/BackgroundSlicingProcess.hpp |  3 ---
 src/slic3r/GUI/Plater.cpp                   | 21 +++++++++------------
 2 files changed, 9 insertions(+), 15 deletions(-)

diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index a15d3faef..9ea20163d 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -124,9 +124,6 @@ public:
     // and it does not account for the OctoPrint scheduling.
     bool    finished() const { return m_print->finished(); }
     
-    // set status line
-    void    set_status(const std::string & status_str) const { m_print->set_status(100, status_str); }
-
 private:
 	void 	thread_proc();
 	void 	thread_proc_safe();
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index c01d89352..eac611115 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -684,9 +684,10 @@ Sidebar::Sidebar(Plater *parent)
     p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&)
     {
         const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT);
-        p->plater->reslice();
         if (export_gcode_after_slicing)
             p->plater->export_gcode();
+        else
+            p->plater->reslice();
     });
     p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
 }
@@ -1098,6 +1099,7 @@ struct Plater::priv
     wxTimer                     background_process_timer;
 
     std::string                 label_btn_export;
+    std::string                 label_btn_send;
 
     static const std::regex pattern_bundle;
     static const std::regex pattern_3mf;
@@ -2150,28 +2152,22 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
 		wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
 	}
 
-    //FIXME update "Slice Now / Schedule background process"
-    //background_process.is_export_scheduled() - byl zavolan "Export G-code", background processing ma jmeno export souboru
-    //background_process.is_upload_scheduled() - byl zavolan "Send to OctoPrint", jeste nebylo doslajsovano (pak se preda upload fronte a background process zapomene)
-    //background_process.empty() - prazdna plocha
-    // pokud (return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0 -> doslo k chybe (gray out "Slice now") mozna "Invalid data"???
-    // jinak background_process.running() -> Zobraz "Slicing ..."
-    // jinak pokud ! background_process.empty() && ! background_process.finished() -> je neco ke slajsovani (povol tlacitko) "Slice Now"
-
     if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
     {
+        // Validation of the background data failed.
         const wxString invalid_str = _(L("Invalid data"));
         for (auto btn : {ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport})
             sidebar->set_btn_label(btn, invalid_str);
     }
     else
     {
+        // Background data is valid.
         if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ||
             (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 )
-            background_process.set_status("Ready to slice");
+            this->statusbar()->set_status_text(L("Ready to slice"));
 
         sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export));
-        sidebar->set_btn_label(ActionButtonType::abSendGCode, _(L("Send G-code")));
+        sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send));
         
         const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? 
                                       _(L("Slicing")) + dots : _(L("Slice now"));
@@ -3447,7 +3443,8 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology)
     //FIXME for SLA synchronize 
     //p->background_process.apply(Model)!
 
-    p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); // #ys_FIXME_rename
+    p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export");
+    p->label_btn_send   = printer_technology == ptFFF ? L("Send G-code")   : L("Send to printer");
 }
 
 void Plater::changed_object(int obj_idx)

From 9522cd1d4f255aac752da1640a874e2a3e04ab4e Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 11 Mar 2019 11:49:24 +0100
Subject: [PATCH 07/11] OnInit: Move preset loading out of EVT_IDLE/once,
 remove temp workaround of sidebar scrollbar

---
 src/slic3r/GUI/GUI_App.cpp        | 16 +++-------------
 src/slic3r/GUI/GUI_ObjectList.cpp |  5 +----
 2 files changed, 4 insertions(+), 17 deletions(-)

diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 73ff19a9d..afbf6b568 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -167,17 +167,6 @@ bool GUI_App::OnInit()
         if (app_config->dirty() && app_config->get("autosave") == "1")
             app_config->save();
 
-        // ! Temporary workaround for the correct behavior of the Scrolled sidebar panel 
-        // Do this "manipulations" only once ( after (re)create of the application )
-        if (sidebar().obj_list()->GetMinHeight() > 15 * wxGetApp().em_unit())
-        {
-            wxWindowUpdateLocker noUpdates_sidebar(&sidebar());
-            sidebar().obj_list()->SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit()));
-
-            // !!! to correct later layouts
-            update_mode(); // update view mode after fix of the object_list size
-        }
-
         this->obj_manipul()->update_if_dirty();
 
         // Preset updating & Configwizard are done after the above initializations,
@@ -205,11 +194,12 @@ bool GUI_App::OnInit()
                 }
                 preset_updater->sync(preset_bundle);
             });
-
-            load_current_presets();
         }
     });
 
+    load_current_presets();
+    update_mode(); // update view mode after fix of the object_list size
+
     mainframe->Show(true);
     m_initialized = true;
     return true;
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index eb3744bef..6a605a667 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -115,10 +115,7 @@ ObjectList::~ObjectList()
 
 void ObjectList::create_objects_ctrl()
 {
-    // temporary workaround for the correct behavior of the Scrolled sidebar panel:
-    // 1. set a height of the list to some big value 
-    // 2. change it to the normal min value (200) after first whole App updating/layouting
-    SetMinSize(wxSize(-1, 3000));   // #ys_FIXME 
+    SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit()));
 
     m_sizer = new wxBoxSizer(wxVERTICAL);
     m_sizer->Add(this, 1, wxGROW);

From 4e510dc3e71d3eb96045c90cdf944396d0ca7f61 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 11 Mar 2019 09:48:24 +0100
Subject: [PATCH 08/11] PostProcessor on Unix: Execute using default shell
 #1908 escape gcode path, collect stderr

---
 src/libslic3r/GCode/PostProcessor.cpp | 76 ++++++++++++++++++---------
 src/slic3r/GUI/Plater.cpp             |  9 ++--
 2 files changed, 57 insertions(+), 28 deletions(-)

diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp
index e86463b84..df4acc1bf 100644
--- a/src/libslic3r/GCode/PostProcessor.cpp
+++ b/src/libslic3r/GCode/PostProcessor.cpp
@@ -2,6 +2,7 @@
 
 #include <boost/algorithm/string.hpp>
 #include <boost/log/trivial.hpp>
+#include <boost/format.hpp>
 #include <boost/filesystem.hpp>
 
 #ifdef WIN32
@@ -88,7 +89,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line)
 // Run the script. If it is a perl script, run it through the bundled perl interpreter.
 // If it is a batch file, run it through the cmd.exe.
 // Otherwise run it directly.
-static int run_script_win32(const std::string &script, const std::string &gcode)
+static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/)
 {
     // Unpack the argument list provided by the user.
     int     nArgs;
@@ -132,9 +133,46 @@ static int run_script_win32(const std::string &script, const std::string &gcode)
 }
 
 #else
-    #include <sys/stat.h> //for getting filesystem UID/GID
-    #include <unistd.h> //for getting current UID/GID
-    #include <boost/process.hpp>
+    // POSIX
+
+#include <cstdlib>   // getenv()
+#include <sstream>
+#include <boost/process.hpp>
+
+namespace process = boost::process;
+
+static int run_script(const std::string &script, const std::string &gcode, std::string &std_err)
+{
+    // Try to obtain user's default shell
+    const char *shell = ::getenv("SHELL");
+    if (shell == nullptr) { shell = "sh"; }
+
+    // Quote and escape the gcode path argument
+    std::string command { script };
+    command.append(" '");
+    for (char c : gcode) {
+        if (c == '\'') { command.append("'\\''"); }
+        else { command.push_back(c); }
+    }
+    command.push_back('\'');
+
+    BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command;
+
+    process::ipstream istd_err;
+    process::child child(shell, "-c", command, process::std_err > istd_err);
+
+    std_err.clear();
+    std::string line;
+
+    while (child.running() && std::getline(istd_err, line)) {
+        std_err.append(line);
+        std_err.push_back('\n');
+    }
+
+    child.wait();
+    return child.exit_code();
+}
+
 #endif
 
 namespace Slic3r {
@@ -158,27 +196,15 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
             if (script.empty())
                 continue;
             BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
-#ifdef WIN32
-            int result = run_script_win32(script, gcode_file.string());
-#else
-            //FIXME testing existence of a script is risky, as the script line may contain the script and some additional command line parameters.
-            // We would have to process the script line into parameters before testing for the existence of the command, the command may be looked up
-            // in the PATH etc.
-            if (! boost::filesystem::exists(boost::filesystem::path(script)))
-                throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + script);
-            struct stat info;
-            if (stat(script.c_str(), &info))
-                throw std::runtime_error(std::string("Cannot read information for post-processing script: ") + script);
-            boost::filesystem::perms script_perms = boost::filesystem::status(script).permissions();
-            //if UID matches, check UID perm. else if GID matches, check GID perm. Otherwise check other perm.
-            if (!(script_perms & ((info.st_uid == geteuid()) ? boost::filesystem::perms::owner_exe
-                               : ((info.st_gid == getegid()) ? boost::filesystem::perms::group_exe
-                                                             : boost::filesystem::perms::others_exe))))
-                throw std::runtime_error(std::string("The configured post-processing script is not executable: check permissions. ") + script);
-    		int result = boost::process::system(script, gcode_file);
-    		if (result < 0)
-    			BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned.";
-#endif
+
+            std::string std_err;
+            const int result = run_script(script, gcode_file.string(), std_err);
+            if (result != 0) {
+                const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
+                    : (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
+                BOOST_LOG_TRIVIAL(error) << msg;
+                throw std::runtime_error(msg);
+            }
         }
     }
 }
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index eac611115..aac21e7db 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -2463,14 +2463,17 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
     this->statusbar()->reset_cancel_callback();
     this->statusbar()->stop_busy();
   
-	bool canceled = evt.GetInt() < 0;
-    bool success  = evt.GetInt() > 0;
+    const bool canceled = evt.GetInt() < 0;
+	const bool error = evt.GetInt() == 0;
+    const bool success  = evt.GetInt() > 0;
     // Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
     this->background_process.reset_export();
-    if (! success) {
+
+    if (error) {
         wxString message = evt.GetString();
         if (message.IsEmpty())
             message = _(L("Export failed"));
+        show_error(q, message);
         this->statusbar()->set_status_text(message);
     }
 	if (canceled)

From 09c9f567f23c8c9248759ee71adcb902fbbdba38 Mon Sep 17 00:00:00 2001
From: Vojtech Kral <vojtech@kral.hk>
Date: Mon, 11 Mar 2019 13:59:58 +0100
Subject: [PATCH 09/11] Fix crash on exit

---
 src/slic3r/GUI/Tab.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 0d53c3fd9..1d7a9a0c2 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -751,6 +751,10 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo
 
 void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
 {
+	if (wxGetApp().plater() == nullptr) {
+		return;
+	}
+
     const bool is_fff = supports_printer_technology(ptFFF);
     ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff);
     if (opt_key == "fill_density" || opt_key == "pad_enable")

From 077b4cbfbc73941bcd802645e7f14a7aaf657a96 Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Tue, 12 Mar 2019 11:39:16 +0100
Subject: [PATCH 10/11] Fixed layout for the action buttons

+ Tried to use DoubleBuffered for the controls drawing on the Sidebar (under MSW only)
---
 src/slic3r/GUI/Plater.cpp | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index aac21e7db..2fddd4784 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -555,8 +555,6 @@ void Sidebar::priv::show_preset_comboboxes()
 {
     const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA;
 
-    wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent());
-    
     for (size_t i = 0; i < 4; ++i)
         sizer_presets->Show(i, !showSLA);
 
@@ -580,6 +578,10 @@ Sidebar::Sidebar(Plater *parent)
     p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1));
     p->scrolled->SetScrollbars(0, 20, 1, 2);
 
+#ifdef __WINDOWS__
+    p->scrolled->SetDoubleBuffered(true);
+#endif //__WINDOWS__
+
     // Sizer in the scrolled area
     auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL);
     p->scrolled->SetSizer(scrolled_sizer);
@@ -645,9 +647,7 @@ Sidebar::Sidebar(Plater *parent)
     p->object_settings->Hide();
     p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5);
 
-    wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG);
     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();
 
@@ -757,6 +757,8 @@ void Sidebar::update_presets(Preset::Type preset_type)
 
 	case Preset::TYPE_PRINTER:
 	{
+        wxWindowUpdateLocker noUpdates_scrolled(p->scrolled);
+
 		// Update the print choosers to only contain the compatible presets, update the dirty flags.
         if (print_tech == ptFFF)
 			preset_bundle.prints.update_platter_ui(p->combo_print);
@@ -2893,6 +2895,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
         sidebar->show_export(!is_ready_to_slice);
         sidebar->show_send(send_gcode_shown && !is_ready_to_slice);
     }
+    sidebar->Layout();
 }
 
 void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const

From f5b5e48ad78342bffa9b806c838f7bd038226750 Mon Sep 17 00:00:00 2001
From: Enrico Turri <enricoturri@seznam.cz>
Date: Tue, 12 Mar 2019 11:57:39 +0100
Subject: [PATCH 11/11] Added cancel mechanism to
 GCodeAnalyzer::calc_gcode_preview_data()

---
 src/libslic3r/GCode.cpp          |  2 +-
 src/libslic3r/GCode/Analyzer.cpp | 51 ++++++++++++++++++++++++++------
 src/libslic3r/GCode/Analyzer.hpp | 12 ++++----
 3 files changed, 50 insertions(+), 15 deletions(-)

diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 2907415bf..94be37b21 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -487,7 +487,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
     // starts analyzer calculations
     if (m_enable_analyzer) {
         BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data";
-        m_analyzer.calc_gcode_preview_data(*preview_data);
+        m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); });
         m_analyzer.reset();
     }
 
diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp
index e1fb140bb..f1f828776 100644
--- a/src/libslic3r/GCode/Analyzer.cpp
+++ b/src/libslic3r/GCode/Analyzer.cpp
@@ -137,22 +137,22 @@ const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode)
     return m_process_output;
 }
 
-void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data)
+void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
 {
     // resets preview data
     preview_data.reset();
 
     // calculates extrusion layers
-    _calc_gcode_preview_extrusion_layers(preview_data);
+    _calc_gcode_preview_extrusion_layers(preview_data, cancel_callback);
 
     // calculates travel
-    _calc_gcode_preview_travel(preview_data);
+    _calc_gcode_preview_travel(preview_data, cancel_callback);
 
     // calculates retractions
-    _calc_gcode_preview_retractions(preview_data);
+    _calc_gcode_preview_retractions(preview_data, cancel_callback);
 
     // calculates unretractions
-    _calc_gcode_preview_unretractions(preview_data);
+    _calc_gcode_preview_unretractions(preview_data, cancel_callback);
 }
 
 bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role)
@@ -676,7 +676,7 @@ bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const
     return ((int)erNone <= value) && (value <= (int)erMixed);
 }
 
-void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data)
+void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
 {
     struct Helper
     {
@@ -725,9 +725,18 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
     GCodePreviewData::Range feedrate_range;
     GCodePreviewData::Range volumetric_rate_range;
 
+    // to avoid to call the callback too often
+    unsigned int cancel_callback_threshold = (unsigned int)extrude_moves->second.size() / 25;
+    unsigned int cancel_callback_curr = 0;
+
     // constructs the polylines while traversing the moves
     for (const GCodeMove& move : extrude_moves->second)
     {
+        // to avoid to call the callback too often
+        cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold;
+        if (cancel_callback_curr == 0)
+            cancel_callback();
+
         if ((data != move.data) || (z != move.start_position.z()) || (position != move.start_position) || (volumetric_rate != move.data.feedrate * (float)move.data.mm3_per_mm))
         {
             // store current polyline
@@ -769,7 +778,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
     preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range);
 }
 
-void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
+void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
 {
     struct Helper
     {
@@ -797,9 +806,17 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
     GCodePreviewData::Range width_range;
     GCodePreviewData::Range feedrate_range;
 
+    // to avoid to call the callback too often
+    unsigned int cancel_callback_threshold = (unsigned int)travel_moves->second.size() / 25;
+    unsigned int cancel_callback_curr = 0;
+
     // constructs the polylines while traversing the moves
     for (const GCodeMove& move : travel_moves->second)
     {
+        cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold;
+        if (cancel_callback_curr == 0)
+            cancel_callback();
+
         GCodePreviewData::Travel::EType move_type = (move.delta_extruder < 0.0f) ? GCodePreviewData::Travel::Retract : ((move.delta_extruder > 0.0f) ? GCodePreviewData::Travel::Extrude : GCodePreviewData::Travel::Move);
         GCodePreviewData::Travel::Polyline::EDirection move_direction = ((move.start_position.x() != move.end_position.x()) || (move.start_position.y() != move.end_position.y())) ? GCodePreviewData::Travel::Polyline::Generic : GCodePreviewData::Travel::Polyline::Vertical;
 
@@ -840,28 +857,44 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
     preview_data.ranges.feedrate.update_from(feedrate_range);
 }
 
-void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data)
+void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
 {
     TypeToMovesMap::iterator retraction_moves = m_moves_map.find(GCodeMove::Retract);
     if (retraction_moves == m_moves_map.end())
         return;
 
+    // to avoid to call the callback too often
+    unsigned int cancel_callback_threshold = (unsigned int)retraction_moves->second.size() / 25;
+    unsigned int cancel_callback_curr = 0;
+
     for (const GCodeMove& move : retraction_moves->second)
     {
+        cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold;
+        if (cancel_callback_curr == 0)
+            cancel_callback();
+
         // store position
         Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
         preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height);
     }
 }
 
-void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data)
+void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
 {
     TypeToMovesMap::iterator unretraction_moves = m_moves_map.find(GCodeMove::Unretract);
     if (unretraction_moves == m_moves_map.end())
         return;
 
+    // to avoid to call the callback too often
+    unsigned int cancel_callback_threshold = (unsigned int)unretraction_moves->second.size() / 25;
+    unsigned int cancel_callback_curr = 0;
+
     for (const GCodeMove& move : unretraction_moves->second)
     {
+        cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold;
+        if (cancel_callback_curr == 0)
+            cancel_callback();
+
         // store position
         Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
         preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height);
diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp
index c74a4558c..4c201c640 100644
--- a/src/libslic3r/GCode/Analyzer.hpp
+++ b/src/libslic3r/GCode/Analyzer.hpp
@@ -122,7 +122,8 @@ public:
     const std::string& process_gcode(const std::string& gcode);
 
     // Calculates all data needed for gcode visualization
-    void calc_gcode_preview_data(GCodePreviewData& preview_data);
+    // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
+    void calc_gcode_preview_data(GCodePreviewData& preview_data, std::function<void()> cancel_callback = std::function<void()>());
 
     // Return an estimate of the memory consumed by the time estimator.
     size_t memory_used() const;
@@ -237,10 +238,11 @@ private:
     // Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole)
     bool _is_valid_extrusion_role(int value) const;
 
-    void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data);
-    void _calc_gcode_preview_travel(GCodePreviewData& preview_data);
-    void _calc_gcode_preview_retractions(GCodePreviewData& preview_data);
-    void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data);
+    // All the following methods throw CanceledException through print->throw_if_canceled() (sent by the caller as callback).
+    void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
+    void _calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
+    void _calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
+    void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
 };
 
 } // namespace Slic3r