OctoPrint basics working, niceties to-do

This commit is contained in:
Vojtech Kral 2018-12-14 15:27:34 +01:00
parent b613334b81
commit 862217a6b3
16 changed files with 450 additions and 210 deletions

View file

@ -1,6 +1,7 @@
#ifndef slic3r_Channel_hpp_ #ifndef slic3r_Channel_hpp_
#define slic3r_Channel_hpp_ #define slic3r_Channel_hpp_
#include <memory>
#include <deque> #include <deque>
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
@ -13,32 +14,26 @@ namespace Slic3r {
template<class T> class Channel template<class T> class Channel
{ {
private:
using UniqueLock = std::unique_lock<std::mutex>;
using Queue = std::deque<T>;
public: public:
class Guard using UniqueLock = std::unique_lock<std::mutex>;
template<class Ptr> class Unlocker
{ {
public: public:
Guard(UniqueLock lock, const Queue &queue) : m_lock(std::move(lock)), m_queue(queue) {} Unlocker(UniqueLock lock) : m_lock(std::move(lock)) {}
Guard(const Guard &other) = delete; Unlocker(const Unlocker &other) noexcept : m_lock(std::move(other.m_lock)) {} // XXX: done beacuse of MSVC 2013 not supporting init of deleter by move
Guard(Guard &&other) = delete; Unlocker(Unlocker &&other) noexcept : m_lock(std::move(other.m_lock)) {}
~Guard() {} Unlocker& operator=(const Unlocker &other) = delete;
Unlocker& operator=(Unlocker &&other) { m_lock = std::move(other.m_lock); }
// Access trampolines void operator()(Ptr*) { m_lock.unlock(); }
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: private:
UniqueLock m_lock; mutable UniqueLock m_lock; // XXX: mutable: see above
const Queue &m_queue;
}; };
using Queue = std::deque<T>;
using LockedConstPtr = std::unique_ptr<const Queue, Unlocker<const Queue>>;
using LockedPtr = std::unique_ptr<Queue, Unlocker<Queue>>;
Channel() {} Channel() {}
~Channel() {} ~Channel() {}
@ -56,7 +51,7 @@ public:
{ {
{ {
UniqueLock lock(m_mutex); UniqueLock lock(m_mutex);
m_queue.push_back(std::forward(item)); m_queue.push_back(std::forward<T>(item));
} }
if (! silent) { m_condition.notify_one(); } if (! silent) { m_condition.notify_one(); }
} }
@ -82,19 +77,22 @@ public:
} }
} }
// Unlocked observers // Unlocked observers/hints
// Thread unsafe! Keep in mind you need to re-verify the result after acquiring lock! // Thread unsafe! Keep in mind you need to re-verify the result after locking!
size_t size() const noexcept { return m_queue.size(); } size_t size_hint() const noexcept { return m_queue.size(); }
bool empty() const noexcept { return m_queue.empty(); }
Guard read() const LockedConstPtr lock_read() const
{ {
return Guard(UniqueLock(m_mutex), m_queue); return LockedConstPtr(&m_queue, Unlocker<const Queue>(UniqueLock(m_mutex)));
} }
LockedPtr lock_rw()
{
return LockedPtr(&m_queue, Unlocker<Queue>(UniqueLock(m_mutex)));
}
private: private:
Queue m_queue; Queue m_queue;
std::mutex m_mutex; mutable std::mutex m_mutex;
std::condition_variable m_condition; std::condition_variable m_condition;
}; };

View file

@ -65,11 +65,6 @@ PrinterTechnology BackgroundSlicingProcess::current_printer_technology() const
return m_print->technology(); 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 // This function may one day be merged into the Print, but historically the print was separated
// from the G-code generator. // from the G-code generator.
void BackgroundSlicingProcess::process_fff() void BackgroundSlicingProcess::process_fff()
@ -88,6 +83,27 @@ void BackgroundSlicingProcess::process_fff()
m_print->set_status(95, "Running post-processing scripts"); m_print->set_status(95, "Running post-processing scripts");
run_post_process_scripts(export_path, m_fff_print->config()); run_post_process_scripts(export_path, m_fff_print->config());
m_print->set_status(100, "G-code file exported to " + export_path); m_print->set_status(100, "G-code file exported to " + export_path);
} else if (! m_upload_job.empty()) {
// A print host upload job has been scheduled
// XXX: is fs::path::string() right?
// Generate a unique temp path to which the gcode is copied
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
/ boost::filesystem::unique_path(".printhost.%%%%-%%%%-%%%%-%%%%.gcode");
if (copy_file(m_temp_output_path, source_path.string()) != 0) {
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
}
m_print->set_status(95, "Running post-processing scripts");
run_post_process_scripts(source_path.string(), m_fff_print->config());
m_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str());
m_upload_job.upload_data.source_path = std::move(source_path);
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job));
} else { } else {
m_print->set_status(100, "Slicing complete"); m_print->set_status(100, "Slicing complete");
} }
@ -373,13 +389,10 @@ void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job)
if (! m_export_path.empty()) if (! m_export_path.empty())
return; 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. // Guard against entering the export step before changing the export path.
tbb::mutex::scoped_lock lock(m_print->state_mutex()); tbb::mutex::scoped_lock lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize); this->invalidate_step(bspsGCodeFinalize);
m_export_path = path.string(); m_export_path = std::string();
m_upload_job = std::move(upload_job); m_upload_job = std::move(upload_job);
} }

View file

@ -73,7 +73,6 @@ GUI_App::GUI_App()
: wxApp() : wxApp()
#if ENABLE_IMGUI #if ENABLE_IMGUI
, m_imgui(new ImGuiWrapper()) , m_imgui(new ImGuiWrapper())
, m_printhost_queue(new PrintHostJobQueue())
#endif // ENABLE_IMGUI #endif // ENABLE_IMGUI
{} {}
@ -142,6 +141,8 @@ bool GUI_App::OnInit()
update_mode(); update_mode();
SetTopWindow(mainframe); SetTopWindow(mainframe);
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
CallAfter([this]() { CallAfter([this]() {
// temporary workaround for the correct behavior of the Scrolled sidebar panel // temporary workaround for the correct behavior of the Scrolled sidebar panel
auto& panel = sidebar(); auto& panel = sidebar();

View file

@ -92,7 +92,7 @@ class GUI_App : public wxApp
std::unique_ptr<ImGuiWrapper> m_imgui; std::unique_ptr<ImGuiWrapper> m_imgui;
#endif // ENABLE_IMGUI #endif // ENABLE_IMGUI
std::unique_ptr<PrintHostJobQueue> m_printhost_queue; std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
public: public:
bool OnInit() override; bool OnInit() override;
@ -164,7 +164,7 @@ public:
ImGuiWrapper* imgui() { return m_imgui.get(); } ImGuiWrapper* imgui() { return m_imgui.get(); }
#endif // ENABLE_IMGUI #endif // ENABLE_IMGUI
PrintHostJobQueue& printhost_queue() { return *m_printhost_queue.get(); } PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
}; };
DECLARE_APP(GUI_App) DECLARE_APP(GUI_App)

View file

@ -18,6 +18,7 @@
#include "ProgressStatusBar.hpp" #include "ProgressStatusBar.hpp"
#include "3DScene.hpp" #include "3DScene.hpp"
#include "AppConfig.hpp" #include "AppConfig.hpp"
#include "PrintHostDialogs.hpp"
#include "wxExtensions.hpp" #include "wxExtensions.hpp"
#include "I18N.hpp" #include "I18N.hpp"
@ -30,7 +31,8 @@ namespace GUI {
MainFrame::MainFrame(const bool no_plater, const bool loaded) : MainFrame::MainFrame(const bool no_plater, const bool loaded) :
wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE), wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE),
m_no_plater(no_plater), m_no_plater(no_plater),
m_loaded(loaded) m_loaded(loaded),
m_printhost_queue_dlg(new PrintHostQueueDialog(this))
{ {
// Load the icon either from the exe, or from the ico file. // Load the icon either from the exe, or from the ico file.
#if _WIN32 #if _WIN32
@ -375,6 +377,10 @@ void MainFrame::init_menubar()
append_menu_item(windowMenu, wxID_ANY, L("Select Printer Settings Tab\tCtrl+4"), L("Show the printer settings"), append_menu_item(windowMenu, wxID_ANY, L("Select Printer Settings Tab\tCtrl+4"), L("Show the printer settings"),
[this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer_empty.png"); [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer_empty.png");
#endif // ENABLE_REMOVE_TABS_FROM_PLATER #endif // ENABLE_REMOVE_TABS_FROM_PLATER
windowMenu->AppendSeparator();
append_menu_item(windowMenu, wxID_ANY, L("Print Host Upload Queue"), L("Display the Print Host Upload Queue window"),
[this](wxCommandEvent&) { m_printhost_queue_dlg->ShowModal(); }, "arrow_up.png");
} }
// View menu // View menu

View file

@ -21,7 +21,9 @@ class ProgressStatusBar;
namespace GUI namespace GUI
{ {
class Tab; class Tab;
class PrintHostQueueDialog;
enum QuickSlice enum QuickSlice
{ {
@ -52,6 +54,8 @@ class MainFrame : public wxFrame
wxMenuItem* m_menu_item_repeat { nullptr }; wxMenuItem* m_menu_item_repeat { nullptr };
wxMenuItem* m_menu_item_reslice_now { nullptr }; wxMenuItem* m_menu_item_reslice_now { nullptr };
PrintHostQueueDialog *m_printhost_queue_dlg;
std::string get_base_name(const wxString full_name) const ; std::string get_base_name(const wxString full_name) const ;
std::string get_dir_name(const wxString full_name) const ; std::string get_dir_name(const wxString full_name) const ;
@ -93,6 +97,8 @@ public:
void select_tab(size_t tab) const; void select_tab(size_t tab) const;
void select_view(const std::string& direction); void select_view(const std::string& direction);
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
Plater* m_plater { nullptr }; Plater* m_plater { nullptr };
wxNotebook* m_tabpanel { nullptr }; wxNotebook* m_tabpanel { nullptr };
wxProgressDialog* m_progress_dialog { nullptr }; wxProgressDialog* m_progress_dialog { nullptr };

View file

@ -3144,7 +3144,7 @@ void Plater::send_gcode()
} }
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()));
Slic3r::PrintHostSendDialog dlg(default_output_file); PrintHostSendDialog dlg(default_output_file);
if (dlg.ShowModal() == wxID_OK) { if (dlg.ShowModal() == wxID_OK) {
upload_job.upload_data.upload_path = dlg.filename(); upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.start_print = dlg.start_print(); upload_job.upload_data.start_print = dlg.start_print();

View file

@ -1,20 +1,28 @@
#include "PrintHostDialogs.hpp" #include "PrintHostDialogs.hpp"
#include <algorithm>
#include <wx/frame.h> #include <wx/frame.h>
#include <wx/event.h>
#include <wx/progdlg.h> #include <wx/progdlg.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/stattext.h> #include <wx/stattext.h>
#include <wx/textctrl.h> #include <wx/textctrl.h>
#include <wx/checkbox.h> #include <wx/checkbox.h>
#include <wx/button.h>
#include <wx/dataview.h>
#include <wx/wupdlock.h>
#include <wx/debug.h>
#include "slic3r/GUI/GUI.hpp" #include "GUI.hpp"
#include "slic3r/GUI/MsgDialog.hpp" #include "MsgDialog.hpp"
#include "slic3r/GUI/I18N.hpp" #include "I18N.hpp"
#include "../Utils/PrintHost.hpp"
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
namespace Slic3r { namespace Slic3r {
namespace GUI {
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) 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) : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE)
@ -45,5 +53,95 @@ fs::path PrintHostSendDialog::filename() const
bool PrintHostSendDialog::start_print() const bool PrintHostSendDialog::start_print() const
{ {
return box_print->GetValue(); } return box_print->GetValue();
} }
wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id)
: wxEvent(winid, eventType)
, job_id(job_id)
{}
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, int progress)
: wxEvent(winid, eventType)
, job_id(job_id)
, progress(progress)
{}
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error)
: wxEvent(winid, eventType)
, job_id(job_id)
, error(std::move(error))
{}
wxEvent *PrintHostQueueDialog::Event::Clone() const
{
return new Event(*this);
}
PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
: wxDialog(parent, wxID_ANY, _(L("Print host upload queue")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this)
, on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this)
{
enum { HEIGHT = 800, WIDTH = 400, SPACING = 5 };
SetMinSize(wxSize(HEIGHT, WIDTH));
auto *topsizer = new wxBoxSizer(wxVERTICAL);
job_list = new wxDataViewListCtrl(this, wxID_ANY);
job_list->AppendTextColumn("ID", wxDATAVIEW_CELL_INERT);
job_list->AppendProgressColumn("Progress", wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn("Status", wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn("Host", wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn("Filename", wxDATAVIEW_CELL_INERT);
auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
auto *btn_cancel = new wxButton(this, wxID_DELETE, _(L("Cancel selected")));
auto *btn_close = new wxButton(this, wxID_CANCEL, _(L("Close")));
btnsizer->Add(btn_cancel, 0, wxRIGHT, SPACING);
btnsizer->AddStretchSpacer();
btnsizer->Add(btn_close);
topsizer->Add(job_list, 1, wxEXPAND | wxBOTTOM, SPACING);
topsizer->Add(btnsizer, 0, wxEXPAND);
SetSizer(topsizer);
}
void PrintHostQueueDialog::append_job(const PrintHostJob &job)
{
wxCHECK_RET(!job.empty(), "PrintHostQueueDialog: Attempt to append an empty job");
wxVector<wxVariant> fields;
fields.push_back(wxVariant(wxString::Format("%d", job_list->GetItemCount() + 1)));
fields.push_back(wxVariant(0));
fields.push_back(wxVariant(_(L("Enqueued"))));
fields.push_back(wxVariant(job.printhost->get_host()));
fields.push_back(wxVariant(job.upload_data.upload_path.string()));
job_list->AppendItem(fields);
}
void PrintHostQueueDialog::on_progress(Event &evt)
{
wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list");
const wxVariant status(evt.progress < 100 ? _(L("Uploading")) : _(L("Complete")));
job_list->SetValue(wxVariant(evt.progress), evt.job_id, 1);
job_list->SetValue(status, evt.job_id, 2);
}
void PrintHostQueueDialog::on_error(Event &evt)
{
wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list");
// TODO
}
}}

View file

@ -2,24 +2,27 @@
#define slic3r_PrintHostSendDialog_hpp_ #define slic3r_PrintHostSendDialog_hpp_
#include <string> #include <string>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <wx/string.h> #include <wx/string.h>
#include <wx/frame.h>
#include <wx/event.h> #include <wx/event.h>
#include <wx/progdlg.h> #include <wx/dialog.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include "slic3r/GUI/GUI.hpp" #include "GUI.hpp"
#include "slic3r/GUI/MsgDialog.hpp" #include "GUI_Utils.hpp"
#include "MsgDialog.hpp"
#include "../Utils/PrintHost.hpp"
class wxTextCtrl;
class wxCheckBox;
class wxDataViewListCtrl;
namespace Slic3r { namespace Slic3r {
struct PrintHostJob;
namespace GUI {
class PrintHostSendDialog : public GUI::MsgDialog class PrintHostSendDialog : public GUI::MsgDialog
{ {
@ -38,12 +41,38 @@ private:
class PrintHostQueueDialog : public wxDialog class PrintHostQueueDialog : public wxDialog
{ {
public: public:
PrintHostQueueDialog(); class Event : public wxEvent
{
public:
size_t job_id;
int progress = 0; // in percent
wxString error;
Event(wxEventType eventType, int winid, size_t job_id);
Event(wxEventType eventType, int winid, size_t job_id, int progress);
Event(wxEventType eventType, int winid, size_t job_id, wxString error);
virtual wxEvent *Clone() const;
};
PrintHostQueueDialog(wxWindow *parent);
void append_job(const PrintHostJob &job);
private: private:
wxDataViewListCtrl *job_list;
// Note: EventGuard prevents delivery of progress evts to a freed PrintHostQueueDialog
EventGuard on_progress_evt;
EventGuard on_error_evt;
void on_progress(Event&);
void on_error(Event&);
}; };
wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
}
}}
#endif #endif

View file

@ -20,7 +20,6 @@
#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX
#include "Http.hpp" #include "Http.hpp"
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
@ -55,89 +54,90 @@ wxString Duet::get_test_failed_msg (wxString &msg) const
return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg); return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg);
} }
bool Duet::send_gcode(const std::string &filename) const // bool Duet::send_gcode(const std::string &filename) const
{ // {
enum { PROGRESS_RANGE = 1000 }; // enum { PROGRESS_RANGE = 1000 };
const auto errortitle = _(L("Error while uploading to the Duet")); // const auto errortitle = _(L("Error while uploading to the Duet"));
fs::path filepath(filename); // fs::path filepath(filename);
PrintHostSendDialog send_dialog(filepath.filename()); // GUI::PrintHostSendDialog send_dialog(filepath.filename());
if (send_dialog.ShowModal() != wxID_OK) { return false; } // if (send_dialog.ShowModal() != wxID_OK) { return false; }
const bool print = send_dialog.start_print(); // const bool print = send_dialog.start_print();
const auto upload_filepath = send_dialog.filename(); // const auto upload_filepath = send_dialog.filename();
const auto upload_filename = upload_filepath.filename(); // const auto upload_filename = upload_filepath.filename();
const auto upload_parent_path = upload_filepath.parent_path(); // const auto upload_parent_path = upload_filepath.parent_path();
wxProgressDialog progress_dialog( // wxProgressDialog progress_dialog(
_(L("Duet upload")), // _(L("Duet upload")),
_(L("Sending G-code file to Duet...")), // _(L("Sending G-code file to Duet...")),
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); // PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
progress_dialog.Pulse(); // progress_dialog.Pulse();
wxString connect_msg; // wxString connect_msg;
if (!connect(connect_msg)) { // if (!connect(connect_msg)) {
auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg); // auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
GUI::show_error(&progress_dialog, std::move(errormsg)); // GUI::show_error(&progress_dialog, std::move(errormsg));
return false; // return false;
} // }
bool res = true; // bool res = true;
auto upload_cmd = get_upload_url(upload_filepath.string()); // auto upload_cmd = get_upload_url(upload_filepath.string());
BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%") // BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
% filepath.string() // % filepath.string()
% upload_filename.string() // % upload_filename.string()
% upload_parent_path.string() // % upload_parent_path.string()
% print // % print
% upload_cmd; // % upload_cmd;
auto http = Http::post(std::move(upload_cmd)); // auto http = Http::post(std::move(upload_cmd));
http.set_post_body(filename) // http.set_post_body(filename)
.on_complete([&](std::string body, unsigned status) { // .on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body; // BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
progress_dialog.Update(PROGRESS_RANGE); // progress_dialog.Update(PROGRESS_RANGE);
int err_code = get_err_code_from_body(body); // int err_code = get_err_code_from_body(body);
if (err_code != 0) { // if (err_code != 0) {
auto msg = format_error(body, L("Unknown error occured"), 0); // auto msg = format_error(body, L("Unknown error occured"), 0);
GUI::show_error(&progress_dialog, std::move(msg)); // GUI::show_error(&progress_dialog, std::move(msg));
res = false; // res = false;
} else if (print) { // } else if (print) {
wxString errormsg; // wxString errormsg;
res = start_print(errormsg, upload_filepath.string()); // res = start_print(errormsg, upload_filepath.string());
if (!res) { // if (!res) {
GUI::show_error(&progress_dialog, std::move(errormsg)); // GUI::show_error(&progress_dialog, std::move(errormsg));
} // }
} // }
}) // })
.on_error([&](std::string body, std::string error, unsigned status) { // .on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; // BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status)); // auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
GUI::show_error(&progress_dialog, std::move(errormsg)); // GUI::show_error(&progress_dialog, std::move(errormsg));
res = false; // res = false;
}) // })
.on_progress([&](Http::Progress progress, bool &cancel) { // .on_progress([&](Http::Progress progress, bool &cancel) {
if (cancel) { // if (cancel) {
// Upload was canceled // // Upload was canceled
res = false; // res = false;
} else if (progress.ultotal > 0) { // } else if (progress.ultotal > 0) {
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal; // int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing // cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
} else { // } else {
cancel = !progress_dialog.Pulse(); // cancel = !progress_dialog.Pulse();
} // }
}) // })
.perform_sync(); // .perform_sync();
disconnect(); // disconnect();
return res; // return res;
} // }
bool Duet::upload(PrintHostUpload upload_data) const bool Duet::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const
{ {
// XXX: TODO
throw "unimplemented"; throw "unimplemented";
} }

View file

@ -22,11 +22,10 @@ public:
bool test(wxString &curl_msg) const; bool test(wxString &curl_msg) const;
wxString get_test_ok_msg () const; wxString get_test_ok_msg () const;
wxString get_test_failed_msg (wxString &msg) const; wxString get_test_failed_msg (wxString &msg) const;
// Send gcode file to duet, filename is expected to be in UTF-8 bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const;
bool send_gcode(const std::string &filename) const;
bool upload(PrintHostUpload upload_data) const;
bool has_auto_discovery() const; bool has_auto_discovery() const;
bool can_test() const; bool can_test() const;
virtual std::string get_host() const { return host; }
private: private:
std::string host; std::string host;
std::string password; std::string password;

View file

@ -4,9 +4,10 @@
#include <boost/format.hpp> #include <boost/format.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <wx/progdlg.h>
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX
#include "Http.hpp" #include "Http.hpp"
@ -59,32 +60,19 @@ wxString OctoPrint::get_test_failed_msg (wxString &msg) const
_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))); _(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required.")));
} }
bool OctoPrint::send_gcode(const std::string &filename) const bool OctoPrint::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const
{ {
enum { PROGRESS_RANGE = 1000 }; const auto upload_filename = upload_data.upload_path.filename();
const auto upload_parent_path = upload_data.upload_path.parent_path();
const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
fs::path filepath(filename);
PrintHostSendDialog send_dialog(filepath.filename());
if (send_dialog.ShowModal() != wxID_OK) { return false; }
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();
wxProgressDialog progress_dialog(
_(L("OctoPrint upload")),
_(L("Sending G-code file to the OctoPrint server...")),
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
progress_dialog.Pulse();
wxString test_msg; wxString test_msg;
if (!test(test_msg)) { if (! test(test_msg)) {
auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
GUI::show_error(&progress_dialog, std::move(errormsg)); // TODO:
return false;
// auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
// GUI::show_error(&progress_dialog, std::move(errormsg));
// return false;
} }
bool res = true; bool res = true;
@ -92,36 +80,31 @@ bool OctoPrint::send_gcode(const std::string &filename) const
auto url = make_url("api/files/local"); auto url = make_url("api/files/local");
BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%") BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%")
% filepath.string() % upload_data.source_path.string()
% url % url
% upload_filename.string() % upload_filename.string()
% upload_parent_path.string() % upload_parent_path.string()
% print; % upload_data.start_print;
auto http = Http::post(std::move(url)); auto http = Http::post(std::move(url));
set_auth(http); set_auth(http);
http.form_add("print", print ? "true" : "false") http.form_add("print", upload_data.start_print ? "true" : "false")
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
.form_add_file("file", filename, upload_filename.string()) .form_add_file("file", upload_data.source_path.string(), upload_filename.string())
.on_complete([&](std::string body, unsigned status) { .on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body; BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body;
progress_dialog.Update(PROGRESS_RANGE);
}) })
.on_error([&](std::string body, std::string error, unsigned status) { .on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status)); error_fn(std::move(body), std::move(error), status);
GUI::show_error(&progress_dialog, std::move(errormsg));
res = false; res = false;
}) })
.on_progress([&](Http::Progress progress, bool &cancel) { .on_progress([&](Http::Progress progress, bool &cancel) {
prorgess_fn(std::move(progress), cancel);
if (cancel) { if (cancel) {
// Upload was canceled // Upload was canceled
BOOST_LOG_TRIVIAL(error) << "Octoprint: Upload canceled";
res = false; res = false;
} else if (progress.ultotal > 0) {
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
} else {
cancel = !progress_dialog.Pulse();
} }
}) })
.perform_sync(); .perform_sync();
@ -129,11 +112,6 @@ bool OctoPrint::send_gcode(const std::string &filename) const
return res; return res;
} }
bool OctoPrint::upload(PrintHostUpload upload_data) const
{
throw "unimplemented";
}
bool OctoPrint::has_auto_discovery() const bool OctoPrint::has_auto_discovery() const
{ {
return true; return true;

View file

@ -22,11 +22,10 @@ public:
bool test(wxString &curl_msg) const; bool test(wxString &curl_msg) const;
wxString get_test_ok_msg () const; wxString get_test_ok_msg () const;
wxString get_test_failed_msg (wxString &msg) const; wxString get_test_failed_msg (wxString &msg) const;
// Send gcode file to octoprint, filename is expected to be in UTF-8 bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const;
bool send_gcode(const std::string &filename) const;
bool upload(PrintHostUpload upload_data) const;
bool has_auto_discovery() const; bool has_auto_discovery() const;
bool can_test() const; bool can_test() const;
virtual std::string get_host() const { return host; }
private: private:
std::string host; std::string host;
std::string apikey; std::string apikey;

View file

@ -1,15 +1,21 @@
#include "OctoPrint.hpp" #include "PrintHost.hpp"
#include "Duet.hpp"
#include <vector> #include <vector>
#include <thread> #include <thread>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/filesystem.hpp>
#include <wx/app.h>
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Channel.hpp" #include "libslic3r/Channel.hpp"
#include "OctoPrint.hpp"
#include "Duet.hpp"
#include "../GUI/PrintHostDialogs.hpp"
namespace fs = boost::filesystem;
using boost::optional; using boost::optional;
using Slic3r::GUI::PrintHostQueueDialog;
namespace Slic3r { namespace Slic3r {
@ -30,30 +36,130 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
struct PrintHostJobQueue::priv struct PrintHostJobQueue::priv
{ {
std::vector<PrintHostJob> jobs; // XXX: comment on how bg thread works
Channel<unsigned> channel;
PrintHostJobQueue *q;
Channel<PrintHostJob> channel_jobs;
Channel<size_t> channel_cancels;
size_t job_id = 0;
int prev_progress = -1;
std::thread bg_thread; std::thread bg_thread;
optional<PrintHostJob> bg_job; bool bg_exit = false;
PrintHostQueueDialog *queue_dialog;
priv(PrintHostJobQueue *q) : q(q) {}
void start_bg_thread();
void bg_thread_main();
void progress_fn(Http::Progress progress, bool &cancel);
void error_fn(std::string body, std::string error, unsigned http_status);
void perform_job(PrintHostJob the_job);
}; };
PrintHostJobQueue::PrintHostJobQueue() PrintHostJobQueue::PrintHostJobQueue(PrintHostQueueDialog *queue_dialog)
: p(new priv()) : p(new priv(this))
{ {
std::shared_ptr<priv> p2 = p; p->queue_dialog = queue_dialog;
p->bg_thread = std::thread([p2]() {
// Wait for commands on the channel:
auto cmd = p2->channel.pop();
// TODO
});
} }
PrintHostJobQueue::~PrintHostJobQueue() PrintHostJobQueue::~PrintHostJobQueue()
{ {
// TODO: stop the thread if (p && p->bg_thread.joinable()) {
// if (p && p->bg_thread.joinable()) { p->bg_exit = true;
// p->bg_thread.detach(); p->channel_jobs.push(PrintHostJob()); // Push an empty job to wake up bg_thread in case it's sleeping
// } p->bg_thread.detach(); // Let the background thread go, it should exit on its own
}
}
void PrintHostJobQueue::priv::start_bg_thread()
{
if (bg_thread.joinable()) { return; }
std::shared_ptr<priv> p2 = q->p;
bg_thread = std::thread([p2]() {
p2->bg_thread_main();
});
}
void PrintHostJobQueue::priv::bg_thread_main()
{
// bg thread entry point
try {
// Pick up jobs from the job channel:
while (! bg_exit) {
auto job = channel_jobs.pop(); // Sleeps in a cond var if there are no jobs
if (! job.cancelled) {
perform_job(std::move(job));
}
job_id++;
}
} catch (...) {
wxTheApp->OnUnhandledException();
}
}
void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel)
{
if (bg_exit) {
cancel = true;
return;
}
if (channel_cancels.size_hint() > 0) {
// Lock both queues
auto cancels = channel_cancels.lock_rw();
auto jobs = channel_jobs.lock_rw();
for (size_t cancel_id : *cancels) {
if (cancel_id == job_id) {
cancel = true;
} else if (cancel_id > job_id) {
jobs->at(cancel_id - job_id).cancelled = true;
}
}
cancels->clear();
}
int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0;
if (gui_progress != prev_progress) {
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, gui_progress);
wxQueueEvent(queue_dialog, evt);
prev_progress = gui_progress;
}
}
void PrintHostJobQueue::priv::error_fn(std::string body, std::string error, unsigned http_status)
{
// TODO
}
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
if (bg_exit || the_job.empty()) { return; }
const fs::path gcode_path = the_job.upload_data.source_path;
the_job.printhost->upload(std::move(the_job.upload_data),
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](std::string body, std::string error, unsigned http_status) { this->error_fn(std::move(body), std::move(error), http_status); }
);
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, 100);
wxQueueEvent(queue_dialog, evt);
fs::remove(gcode_path); // XXX: error handling
}
void PrintHostJobQueue::enqueue(PrintHostJob job)
{
p->start_bg_thread();
p->queue_dialog->append_job(job);
p->channel_jobs.push(std::move(job));
} }

View file

@ -7,6 +7,8 @@
#include <wx/string.h> #include <wx/string.h>
#include "Http.hpp"
namespace Slic3r { namespace Slic3r {
@ -29,11 +31,10 @@ public:
virtual bool test(wxString &curl_msg) const = 0; virtual bool test(wxString &curl_msg) const = 0;
virtual wxString get_test_ok_msg () const = 0; virtual wxString get_test_ok_msg () const = 0;
virtual wxString get_test_failed_msg (wxString &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 upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const = 0;
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 has_auto_discovery() const = 0;
virtual bool can_test() const = 0; virtual bool can_test() const = 0;
virtual std::string get_host() const = 0;
static PrintHost* get_print_host(DynamicPrintConfig *config); static PrintHost* get_print_host(DynamicPrintConfig *config);
}; };
@ -43,6 +44,7 @@ struct PrintHostJob
{ {
PrintHostUpload upload_data; PrintHostUpload upload_data;
std::unique_ptr<PrintHost> printhost; std::unique_ptr<PrintHost> printhost;
bool cancelled = false;
PrintHostJob() {} PrintHostJob() {}
PrintHostJob(const PrintHostJob&) = delete; PrintHostJob(const PrintHostJob&) = delete;
@ -68,10 +70,12 @@ struct PrintHostJob
}; };
namespace GUI { class PrintHostQueueDialog; }
class PrintHostJobQueue class PrintHostJobQueue
{ {
public: public:
PrintHostJobQueue(); PrintHostJobQueue(GUI::PrintHostQueueDialog *queue_dialog);
PrintHostJobQueue(const PrintHostJobQueue &) = delete; PrintHostJobQueue(const PrintHostJobQueue &) = delete;
PrintHostJobQueue(PrintHostJobQueue &&other) = delete; PrintHostJobQueue(PrintHostJobQueue &&other) = delete;
~PrintHostJobQueue(); ~PrintHostJobQueue();
@ -79,6 +83,9 @@ public:
PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete; PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete;
PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete; PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete;
void enqueue(PrintHostJob job);
void cancel(size_t id);
private: private:
struct priv; struct priv;
std::shared_ptr<priv> p; std::shared_ptr<priv> p;