diff --git a/resources/web/device/css/dark.css b/resources/web/device/css/dark.css
new file mode 100644
index 000000000..a5f041e8a
--- /dev/null
+++ b/resources/web/device/css/dark.css
@@ -0,0 +1,25 @@
+body {
+ background-color:#4c4c54;
+ font-family: Arial, sans-serif;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ margin: 0;
+}
+
+.container {
+ text-align: center;
+ padding: 30px;
+ border-radius: 10px;
+ background-color: #272727;
+ box-shadow: 0 4px 6px rgba(39, 39, 39, 0.1);
+}
+
+h1 {
+ color: #ffffff;
+}
+
+p {
+ color: #ffffff;
+}
\ No newline at end of file
diff --git a/resources/web/device/css/home.css b/resources/web/device/css/home.css
new file mode 100644
index 000000000..3f4cc4eb9
--- /dev/null
+++ b/resources/web/device/css/home.css
@@ -0,0 +1,18 @@
+body
+{
+ background-color:#eeeeee;
+ font-family: Arial, sans-serif;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ margin: 0;
+}
+
+.container {
+ text-align: center;
+ padding: 30px;
+ border-radius: 10px;
+ background-color: #ffffff;
+ box-shadow: 0 4px 6px rgba(39, 39, 39, 0.1);
+}
\ No newline at end of file
diff --git a/resources/web/device/missing_connection.html b/resources/web/device/missing_connection.html
new file mode 100644
index 000000000..a66ea4561
--- /dev/null
+++ b/resources/web/device/missing_connection.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+Printer Connection Required
+
+
+
+
+
+
+
+
+
+
+
Printer Connection
+
Please set up your printer connection to view the device.
+

+
+
+
diff --git a/resources/web/device/setup_connection.gif b/resources/web/device/setup_connection.gif
new file mode 100644
index 000000000..ba6220915
Binary files /dev/null and b/resources/web/device/setup_connection.gif differ
diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp
index 1621c42a6..39add72de 100644
--- a/src/libslic3r/Preset.cpp
+++ b/src/libslic3r/Preset.cpp
@@ -803,7 +803,8 @@ static std::vector s_Preset_printer_options {
"scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", "template_custom_gcode",
"nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types",
//SoftFever
- "host_type", "print_host", "printhost_apikey",
+ "host_type", "print_host", "printhost_apikey",
+ "print_host_webui",
"printhost_cafile","printhost_port","printhost_authorization_type",
"printhost_user", "printhost_password", "printhost_ssl_ignore_revoke"
};
@@ -1694,6 +1695,11 @@ std::pair PresetCollection::load_external_preset(
{
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
DynamicPrintConfig cfg(this->default_preset_for(combined_config).config);
+ // SoftFever: ignore print connection info from project
+ cfg.erase("print_host");
+ cfg.erase("print_host_webui");
+ cfg.erase("printhost_apikey");
+ cfg.erase("printhost_cafile");
const auto &keys = cfg.keys();
cfg.apply_only(combined_config, keys, true);
std::string &inherits = Preset::inherits(cfg);
diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp
index f9ad98aa6..ef8989faf 100644
--- a/src/libslic3r/PresetBundle.cpp
+++ b/src/libslic3r/PresetBundle.cpp
@@ -1552,6 +1552,9 @@ DynamicPrintConfig PresetBundle::full_config_secure() const
{
DynamicPrintConfig config = this->full_config();
//BBS example: config.erase("print_host");
+ config.erase("print_host_webui");
+ config.erase("printhost_apikey");
+ config.erase("printhost_cafile");
return config;
}
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 9600f8f10..b794be20e 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -75,11 +75,11 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology)
static t_config_enum_values s_keys_map_PrintHostType{
{ "prusalink", htPrusaLink },
{ "octoprint", htOctoPrint },
- //{ "duet", htDuet },
- //{ "flashair", htFlashAir },
- //{ "astrobox", htAstroBox },
- //{ "repetier", htRepetier },
- //{ "mks", htMKS }
+ { "duet", htDuet },
+ { "flashair", htFlashAir },
+ { "astrobox", htAstroBox },
+ { "repetier", htRepetier },
+ { "mks", htMKS }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
@@ -404,6 +404,14 @@ void PrintConfigDef::init_common_params()
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
+ def = this->add("print_host_webui", coString);
+ def->label = L("Device UI");
+ def->tooltip = L("Specify the URL of your device user interface if it's not same as print_host");
+ def->mode = comAdvanced;
+ def->cli = ConfigOptionDef::nocli;
+ def->set_default_value(new ConfigOptionString(""));
+
+
def = this->add("printhost_apikey", coString);
def->label = L("API Key / Password");
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
@@ -2011,18 +2019,18 @@ void PrintConfigDef::init_fff_params()
def->enum_keys_map = &ConfigOptionEnum::get_enum_values();
def->enum_values.push_back("prusalink");
def->enum_values.push_back("octoprint");
- //def->enum_values.push_back("duet");
- //def->enum_values.push_back("flashair");
- //def->enum_values.push_back("astrobox");
- //def->enum_values.push_back("repetier");
- //def->enum_values.push_back("mks");
+ def->enum_values.push_back("duet");
+ def->enum_values.push_back("flashair");
+ def->enum_values.push_back("astrobox");
+ def->enum_values.push_back("repetier");
+ def->enum_values.push_back("mks");
def->enum_labels.push_back("PrusaLink");
def->enum_labels.push_back("OctoPrint");
- //def->enum_labels.push_back("Duet");
- //def->enum_labels.push_back("FlashAir");
- //def->enum_labels.push_back("AstroBox");
- //def->enum_labels.push_back("Repetier");
- //def->enum_labels.push_back("MKS");
+ def->enum_labels.push_back("Duet");
+ def->enum_labels.push_back("FlashAir");
+ def->enum_labels.push_back("AstroBox");
+ def->enum_labels.push_back("Repetier");
+ def->enum_labels.push_back("MKS");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionEnum(htOctoPrint));
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 62caa3e26..5e66eed39 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -245,6 +245,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Monitor.hpp
GUI/WebViewDialog.cpp
GUI/WebViewDialog.hpp
+ GUI/PrinterWebView.cpp
+ GUI/PrinterWebView.hpp
GUI/WebDownPluginDlg.hpp
GUI/WebDownPluginDlg.cpp
GUI/WebGuideDialog.hpp
@@ -429,6 +431,17 @@ set(SLIC3R_GUI_SOURCES
Utils/PrintHost.cpp
Utils/NetworkAgent.cpp
Utils/NetworkAgent.hpp
+ Utils/MKS.hpp
+ Utils/MKS.cpp
+ Utils/Duet.cpp
+ Utils/Duet.hpp
+ Utils/FlashAir.cpp
+ Utils/FlashAir.hpp
+ Utils/AstroBox.cpp
+ Utils/AstroBox.hpp
+ Utils/Repetier.cpp
+ Utils/Repetier.hpp
+
Utils/CalibUtils.cpp
Utils/CalibUtils.hpp
)
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index dd113dcbf..7994e5dfe 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -83,6 +83,7 @@ wxDEFINE_EVENT(EVT_UPDATE_PRESET_CB, SimpleEvent);
// BBS: backup
wxDEFINE_EVENT(EVT_BACKUP_POST, wxCommandEvent);
wxDEFINE_EVENT(EVT_LOAD_URL, wxCommandEvent);
+wxDEFINE_EVENT(EVT_LOAD_PRINTER_URL, wxCommandEvent);
enum class ERescaleTarget
{
@@ -925,16 +926,35 @@ void MainFrame::init_tabpanel()
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGING, [this](wxBookCtrlEvent& e) {
int old_sel = e.GetOldSelection();
int new_sel = e.GetSelection();
- if (new_sel == tpMonitor) {
+ if (wxGetApp().preset_bundle &&
+ wxGetApp().preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(wxGetApp().preset_bundle) &&
+ new_sel == tpMonitor) {
if (!wxGetApp().getAgent()) {
e.Veto();
- BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins")%old_sel %new_sel;
+ BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins") %
+ old_sel % new_sel;
if (m_plater) {
- wxCommandEvent *evt = new wxCommandEvent(EVT_INSTALL_PLUGIN_HINT);
+ wxCommandEvent* evt = new wxCommandEvent(EVT_INSTALL_PLUGIN_HINT);
wxQueueEvent(m_plater, evt);
}
}
}
+ else {
+ if (new_sel == tpMonitor && wxGetApp().preset_bundle != nullptr) {
+ auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
+ wxString url;
+ if (cfg.has("print_host_webui") && !cfg.opt_string("print_host_webui").empty()) {
+ url = cfg.opt_string("print_host_webui");
+ }
+ else {
+ url = cfg.opt_string("print_host");
+ }
+ if (url.empty()) {
+ wxString url = wxString::Format("file://%s/web/device/missing_connection.html", from_u8(resources_dir()));
+ m_printer_view->load_url(url);
+ }
+ }
+ }
});
#ifdef __WXMSW__
@@ -1017,6 +1037,14 @@ void MainFrame::init_tabpanel()
m_monitor->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"));
+ m_printer_view = new PrinterWebView(m_tabpanel);
+ Bind(EVT_LOAD_PRINTER_URL, [this](wxCommandEvent &evt) {
+ wxString url = evt.GetString();
+ //select_tab(MainFrame::tpMonitor);
+ m_printer_view->load_url(url);
+ });
+ m_printer_view->Hide();
+
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_project->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(m_project, _L("Project"), std::string("tab_auxiliary_avtice"), std::string("tab_auxiliary_avtice"));
@@ -1039,6 +1067,36 @@ void MainFrame::init_tabpanel()
}
}
+
+// SoftFever
+void MainFrame::show_device(bool bBBLPrinter) {
+ if (m_tabpanel->GetPage(tpMonitor) != m_monitor &&
+ m_tabpanel->GetPage(tpMonitor) != m_printer_view) {
+ BOOST_LOG_TRIVIAL(error) << "Failed to find device tab";
+ return;
+ }
+ if (bBBLPrinter) {
+ if (m_tabpanel->GetPage(tpMonitor) != m_monitor) {
+ m_printer_view->Hide();
+ m_tabpanel->RemovePage(tpMonitor);
+ m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"),
+ std::string("tab_monitor_active"),
+ std::string("tab_monitor_active"));
+ m_tabpanel->SetSelection(tp3DEditor);
+ }
+ } else {
+ if (m_tabpanel->GetPage(tpMonitor) != m_printer_view) {
+ m_printer_view->Show();
+ m_tabpanel->RemovePage(tpMonitor);
+ m_tabpanel->InsertPage(tpMonitor, m_printer_view, _L("Device"),
+ std::string("tab_monitor_active"),
+ std::string("tab_monitor_active"));
+ m_tabpanel->SetSelection(tp3DEditor);
+ }
+ }
+}
+
+
bool MainFrame::preview_only_hint()
{
if (m_plater && (m_plater->only_gcode_mode() || (m_plater->using_exported_file()))) {
@@ -3244,6 +3302,34 @@ void MainFrame::load_url(wxString url)
wxQueueEvent(this, evt);
}
+void MainFrame::load_printer_url(wxString url)
+{
+ BOOST_LOG_TRIVIAL(trace) << "load_printer_url:" << url;
+ auto evt = new wxCommandEvent(EVT_LOAD_PRINTER_URL, this->GetId());
+ evt->SetString(url);
+ wxQueueEvent(this, evt);
+}
+
+void MainFrame::load_printer_url()
+{
+ PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
+ if (preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle))
+ return;
+
+ auto cfg = preset_bundle.printers.get_edited_preset().config;
+ wxString url =
+ cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
+ if (!url.empty()) {
+ if (!url.Lower().starts_with("http"))
+ url = wxString::Format("http://%s", url);
+
+ load_printer_url(url);
+ }
+}
+
+bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelection() == TabPosition::tpMonitor; }
+
+
void MainFrame::refresh_plugin_tips()
{
if (m_webview != nullptr)
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index e96ec8523..065dda196 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -28,6 +28,7 @@
// BBS
#include "BBLTopbar.hpp"
+#include "PrinterWebView.hpp"
#define ENABEL_PRINT_ALL 0
@@ -328,8 +329,12 @@ public:
//BBS
void load_url(wxString url);
+ void load_printer_url(wxString url);
+ void load_printer_url();
+ bool is_printer_view() const;
void refresh_plugin_tips();
void RunScript(wxString js);
+ void show_device(bool bBBLPrinter);
// BBS. Replace title bar and menu bar with top bar.
BBLTopbar* m_topbar{ nullptr };
@@ -343,6 +348,7 @@ public:
CalibrationPanel* m_calibration{ nullptr };
WebViewPanel* m_webview { nullptr };
+ PrinterWebView* m_printer_view{nullptr};
wxLogWindow* m_log_window { nullptr };
// BBS
//wxBookCtrlBase* m_tabpanel { nullptr };
diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp
index 5304955bc..4d6d48a26 100644
--- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp
+++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp
@@ -219,6 +219,10 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
host_line.append_widget(print_host_test);
m_optgroup->append_line(host_line);
+ option = m_optgroup->get_option("print_host_webui");
+ option.opt.width = Field::def_width_wider();
+ m_optgroup->append_single_option_line(option);
+
m_optgroup->append_single_option_line("printhost_authorization_type");
option = m_optgroup->get_option("printhost_apikey");
@@ -233,7 +237,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.");
- /*if (Http::ca_file_supported()) {
+ if (Http::ca_file_supported()) {
option = m_optgroup->get_option("printhost_cafile");
option.opt.width = Field::def_width_wider();
Line cafile_line = m_optgroup->create_single_option_line(option);
@@ -253,37 +257,37 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
};
cafile_line.append_widget(printhost_cafile_browse);
- //m_optgroup->append_line(cafile_line);
+ m_optgroup->append_line(cafile_line);
- //Line cafile_hint{ "", "" };
- //cafile_hint.full_width = 1;
- //cafile_hint.widget = [ca_file_hint](wxWindow* parent) {
- // auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint);
- // auto sizer = new wxBoxSizer(wxHORIZONTAL);
- // sizer->Add(txt);
- // return sizer;
- //};
- //m_optgroup->append_line(cafile_hint);
+ Line cafile_hint{ "", "" };
+ cafile_hint.full_width = 1;
+ cafile_hint.widget = [ca_file_hint](wxWindow* parent) {
+ auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint);
+ auto sizer = new wxBoxSizer(wxHORIZONTAL);
+ sizer->Add(txt);
+ return sizer;
+ };
+ m_optgroup->append_line(cafile_hint);
}
else {
- //Line line{ "", "" };
- //line.full_width = 1;
+ Line line{ "", "" };
+ line.full_width = 1;
- //line.widget = [ca_file_hint](wxWindow* parent) {
- // std::string info = _u8L("HTTPS CA File") + ":\n\t" +
- // (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() +
- // "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain.");
+ line.widget = [ca_file_hint](wxWindow* parent) {
+ std::string info = _u8L("HTTPS CA File") + ":\n\t" +
+ (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() +
+ "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain.");
- // //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str()));
- // auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str()));
- // txt->SetFont(wxGetApp().normal_font());
- // auto sizer = new wxBoxSizer(wxHORIZONTAL);
- // sizer->Add(txt, 1, wxEXPAND);
- // return sizer;
- //};
- //m_optgroup->append_line(line);
- }*/
+ //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str()));
+ auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str()));
+ txt->SetFont(wxGetApp().normal_font());
+ auto sizer = new wxBoxSizer(wxHORIZONTAL);
+ sizer->Add(txt, 1, wxEXPAND|wxALIGN_LEFT);
+ return sizer;
+ };
+ m_optgroup->append_line(line);
+ }
for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) {
option = m_optgroup->get_option(opt_key);
@@ -292,9 +296,9 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
}
#ifdef WIN32
- //option = m_optgroup->get_option("printhost_ssl_ignore_revoke");
- //option.opt.width = Field::def_width_wider();
- //m_optgroup->append_single_option_line(option);
+ option = m_optgroup->get_option("printhost_ssl_ignore_revoke");
+ option.opt.width = Field::def_width_wider();
+ m_optgroup->append_single_option_line(option);
#endif
m_optgroup->activate();
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 260102c21..0ca2bd09e 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -1026,17 +1026,39 @@ void Sidebar::update_all_preset_comboboxes()
bool is_bbl_preset = preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle);
+ auto p_mainframe = wxGetApp().mainframe;
+
+ p_mainframe->show_device(is_bbl_preset);
if (is_bbl_preset) {
//only show connection button for not-BBL printer
connection_btn->Hide();
//only show sync-ams button for BBL printer
ams_btn->Show();
//update print button default value for bbl or third-party printer
- wxGetApp().mainframe->set_print_button_to_default(MainFrame::PrintSelectType::ePrintPlate);
+ p_mainframe->set_print_button_to_default(MainFrame::PrintSelectType::ePrintPlate);
+ m_bed_type_list->SelectAndNotify(btPC - 1);
+ m_bed_type_list->Enable();
} else {
connection_btn->Show();
ams_btn->Hide();
- wxGetApp().mainframe->set_print_button_to_default(MainFrame::PrintSelectType::eSendGcode);
+ p_mainframe->set_print_button_to_default(MainFrame::PrintSelectType::eSendGcode);
+ auto cfg = preset_bundle.printers.get_edited_preset().config;
+ wxString url;
+ if (cfg.has("print_host_webui") && !cfg.opt_string("print_host_webui").empty()) {
+ url = cfg.opt_string("print_host_webui");
+ } else {
+ url = cfg.opt_string("print_host");
+ }
+ if(!url.empty())
+ {
+ if(!url.Lower().starts_with("http"))
+ url = wxString::Format("http://%s",url);
+
+ p_mainframe->load_printer_url(url);
+ }
+
+ m_bed_type_list->SelectAndNotify(btPEI - 1);
+ m_bed_type_list->Disable();
}
// Update the print choosers to only contain the compatible presets, update the dirty flags.
diff --git a/src/slic3r/GUI/PrinterWebView.cpp b/src/slic3r/GUI/PrinterWebView.cpp
new file mode 100644
index 000000000..5830d1379
--- /dev/null
+++ b/src/slic3r/GUI/PrinterWebView.cpp
@@ -0,0 +1,123 @@
+#include "PrinterWebView.hpp"
+
+#include "I18N.hpp"
+#include "slic3r/GUI/wxExtensions.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/MainFrame.hpp"
+#include "libslic3r_version.h"
+
+#include
+#include
+#include
+
+#include
+
+namespace pt = boost::property_tree;
+
+namespace Slic3r {
+namespace GUI {
+
+PrinterWebView::PrinterWebView(wxWindow *parent)
+ : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
+ {
+
+ wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
+
+ // Create the webview
+ m_browser = WebView::CreateWebView(this, "");
+ if (m_browser == nullptr) {
+ wxLogError("Could not init m_browser");
+ return;
+ }
+
+ Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this);
+
+ SetSizer(topsizer);
+
+ topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
+
+ // Log backend information
+ if (wxGetApp().get_mode() == comDevelop) {
+ wxLogMessage(wxWebView::GetBackendVersionInfo().ToString());
+ wxLogMessage("Backend: %s Version: %s", m_browser->GetClassInfo()->GetClassName(),
+ wxWebView::GetBackendVersionInfo().ToString());
+ wxLogMessage("User Agent: %s", m_browser->GetUserAgent());
+ }
+
+ //Zoom
+ m_zoomFactor = 100;
+
+ //Connect the idle events
+ Bind(wxEVT_CLOSE_WINDOW, &PrinterWebView::OnClose, this);
+
+ }
+
+PrinterWebView::~PrinterWebView()
+{
+ BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Start";
+ SetEvtHandlerEnabled(false);
+
+ BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End";
+}
+
+
+void PrinterWebView::load_url(wxString& url)
+{
+// this->Show();
+// this->Raise();
+ if (m_browser == nullptr)
+ return;
+ m_browser->LoadURL(url);
+ //m_browser->SetFocus();
+ UpdateState();
+}
+/**
+ * Method that retrieves the current state from the web control and updates the
+ * GUI the reflect this current state.
+ */
+void PrinterWebView::UpdateState() {
+ // SetTitle(m_browser->GetCurrentTitle());
+
+}
+
+void PrinterWebView::OnClose(wxCloseEvent& evt)
+{
+ this->Hide();
+}
+
+void PrinterWebView::OnError(wxWebViewEvent &evt)
+{
+ auto e = "unknown error";
+ switch (evt.GetInt()) {
+ case wxWEBVIEW_NAV_ERR_CONNECTION:
+ e = "wxWEBVIEW_NAV_ERR_CONNECTION";
+ break;
+ case wxWEBVIEW_NAV_ERR_CERTIFICATE:
+ e = "wxWEBVIEW_NAV_ERR_CERTIFICATE";
+ break;
+ case wxWEBVIEW_NAV_ERR_AUTH:
+ e = "wxWEBVIEW_NAV_ERR_AUTH";
+ break;
+ case wxWEBVIEW_NAV_ERR_SECURITY:
+ e = "wxWEBVIEW_NAV_ERR_SECURITY";
+ break;
+ case wxWEBVIEW_NAV_ERR_NOT_FOUND:
+ e = "wxWEBVIEW_NAV_ERR_NOT_FOUND";
+ break;
+ case wxWEBVIEW_NAV_ERR_REQUEST:
+ e = "wxWEBVIEW_NAV_ERR_REQUEST";
+ break;
+ case wxWEBVIEW_NAV_ERR_USER_CANCELLED:
+ e = "wxWEBVIEW_NAV_ERR_USER_CANCELLED";
+ break;
+ case wxWEBVIEW_NAV_ERR_OTHER:
+ e = "wxWEBVIEW_NAV_ERR_OTHER";
+ break;
+ }
+ BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": error loading page %1% %2% %3% %4%") %evt.GetURL() %evt.GetTarget() %e %evt.GetString();
+}
+
+
+
+} // GUI
+} // Slic3r
diff --git a/src/slic3r/GUI/PrinterWebView.hpp b/src/slic3r/GUI/PrinterWebView.hpp
new file mode 100644
index 000000000..bdf5f4c68
--- /dev/null
+++ b/src/slic3r/GUI/PrinterWebView.hpp
@@ -0,0 +1,54 @@
+#ifndef slic3r_PrinterWebView_hpp_
+#define slic3r_PrinterWebView_hpp_
+
+
+#include "wx/artprov.h"
+#include "wx/cmdline.h"
+#include "wx/notifmsg.h"
+#include "wx/settings.h"
+#include "wx/webview.h"
+
+#if wxUSE_WEBVIEW_EDGE
+#include "wx/msw/webview_edge.h"
+#endif
+
+#include "wx/webviewarchivehandler.h"
+#include "wx/webviewfshandler.h"
+#include "wx/numdlg.h"
+#include "wx/infobar.h"
+#include "wx/filesys.h"
+#include "wx/fs_arc.h"
+#include "wx/fs_mem.h"
+#include "wx/stdpaths.h"
+#include
+#include
+#include "wx/textctrl.h"
+#include
+
+
+namespace Slic3r {
+namespace GUI {
+
+
+class PrinterWebView : public wxPanel {
+public:
+ PrinterWebView(wxWindow *parent);
+ virtual ~PrinterWebView();
+
+ void load_url(wxString& url);
+ void UpdateState();
+ void OnClose(wxCloseEvent& evt);
+ void OnError(wxWebViewEvent& evt);
+
+private:
+
+ wxWebView* m_browser;
+ long m_zoomFactor;
+
+ // DECLARE_EVENT_TABLE()
+};
+
+} // GUI
+} // Slic3r
+
+#endif /* slic3r_Tab_hpp_ */
diff --git a/src/slic3r/GUI/Widgets/ComboBox.cpp b/src/slic3r/GUI/Widgets/ComboBox.cpp
index 1ba91ddf7..cbc7cedd4 100644
--- a/src/slic3r/GUI/Widgets/ComboBox.cpp
+++ b/src/slic3r/GUI/Widgets/ComboBox.cpp
@@ -75,6 +75,10 @@ void ComboBox::SetSelection(int n)
if (drop.selection >= 0)
SetIcon(icons[drop.selection]);
}
+void ComboBox::SelectAndNotify(int n) {
+ SetSelection(n);
+ sendComboBoxEvent();
+}
void ComboBox::Rescale()
{
diff --git a/src/slic3r/GUI/Widgets/ComboBox.hpp b/src/slic3r/GUI/Widgets/ComboBox.hpp
index 7158cb2fb..bac352307 100644
--- a/src/slic3r/GUI/Widgets/ComboBox.hpp
+++ b/src/slic3r/GUI/Widgets/ComboBox.hpp
@@ -43,6 +43,8 @@ public:
void SetSelection(int n) override;
+ void SelectAndNotify(int n);
+
virtual void Rescale() override;
wxString GetValue() const;
diff --git a/src/slic3r/Utils/AstroBox.cpp b/src/slic3r/Utils/AstroBox.cpp
new file mode 100644
index 000000000..8781549a2
--- /dev/null
+++ b/src/slic3r/Utils/AstroBox.cpp
@@ -0,0 +1,173 @@
+#include "AstroBox.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "libslic3r/PrintConfig.hpp"
+#include "slic3r/GUI/I18N.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "Http.hpp"
+
+
+namespace fs = boost::filesystem;
+namespace pt = boost::property_tree;
+
+
+namespace Slic3r {
+
+AstroBox::AstroBox(DynamicPrintConfig *config) :
+ host(config->opt_string("print_host")),
+ apikey(config->opt_string("printhost_apikey")),
+ cafile(config->opt_string("printhost_cafile"))
+{}
+
+const char* AstroBox::get_name() const { return "AstroBox"; }
+
+bool AstroBox::test(wxString &msg) const
+{
+ // Since the request is performed synchronously here,
+ // it is ok to refer to `msg` from within the closure
+
+ const char *name = get_name();
+
+ bool res = true;
+ auto url = make_url("api/version");
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
+
+ auto http = Http::get(std::move(url));
+ set_auth(http);
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ res = false;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&, this](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
+
+ try {
+ std::stringstream ss(body);
+ pt::ptree ptree;
+ pt::read_json(ss, ptree);
+
+ if (! ptree.get_optional("api")) {
+ res = false;
+ return;
+ }
+
+ const auto text = ptree.get_optional("text");
+ res = validate_version_text(text);
+ if (! res) {
+ msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "AstroBox")).str());
+ }
+ }
+ catch (const std::exception &) {
+ res = false;
+ msg = "Could not parse server response";
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+wxString AstroBox::get_test_ok_msg () const
+{
+ return _(L("Connection to AstroBox works correctly."));
+}
+
+wxString AstroBox::get_test_failed_msg (wxString &msg) const
+{
+ return GUI::from_u8((boost::format("%s: %s\n\n%s")
+ % _utf8(L("Could not connect to AstroBox"))
+ % std::string(msg.ToUTF8())
+ % _utf8(L("Note: AstroBox version at least 1.1.0 is required."))).str());
+}
+
+bool AstroBox::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
+{
+ const char *name = get_name();
+
+ const auto upload_filename = upload_data.upload_path.filename();
+ const auto upload_parent_path = upload_data.upload_path.parent_path();
+
+ wxString test_msg;
+ if (! test(test_msg)) {
+ error_fn(std::move(test_msg));
+ return false;
+ }
+
+ bool res = true;
+
+ auto url = make_url("api/files/local");
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
+ % name
+ % upload_data.source_path
+ % url
+ % upload_filename.string()
+ % upload_parent_path.string()
+ % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
+
+ auto http = Http::post(std::move(url));
+ set_auth(http);
+ http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
+ .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
+ .form_add_file("file", upload_data.source_path.string(), upload_filename.string())
+ .on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
+ })
+ .on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ error_fn(format_error(body, error, status));
+ res = false;
+ })
+ .on_progress([&](Http::Progress progress, bool &cancel) {
+ prorgess_fn(std::move(progress), cancel);
+ if (cancel) {
+ // Upload was canceled
+ BOOST_LOG_TRIVIAL(info) << "AstroBox: Upload canceled";
+ res = false;
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+bool AstroBox::validate_version_text(const boost::optional &version_text) const
+{
+ return version_text ? boost::starts_with(*version_text, "AstroBox") : true;
+}
+
+void AstroBox::set_auth(Http &http) const
+{
+ http.header("X-Api-Key", apikey);
+
+ if (! cafile.empty()) {
+ http.ca_file(cafile);
+ }
+}
+
+std::string AstroBox::make_url(const std::string &path) const
+{
+ if (host.find("http://") == 0 || host.find("https://") == 0) {
+ if (host.back() == '/') {
+ return (boost::format("%1%%2%") % host % path).str();
+ } else {
+ return (boost::format("%1%/%2%") % host % path).str();
+ }
+ } else {
+ return (boost::format("http://%1%/%2%") % host % path).str();
+ }
+}
+
+}
diff --git a/src/slic3r/Utils/AstroBox.hpp b/src/slic3r/Utils/AstroBox.hpp
new file mode 100644
index 000000000..15a8863a9
--- /dev/null
+++ b/src/slic3r/Utils/AstroBox.hpp
@@ -0,0 +1,46 @@
+#ifndef slic3r_AstroBox_hpp_
+#define slic3r_AstroBox_hpp_
+
+#include
+#include
+#include
+
+#include "PrintHost.hpp"
+
+namespace Slic3r {
+
+class DynamicPrintConfig;
+class Http;
+
+class AstroBox : public PrintHost
+{
+public:
+ AstroBox(DynamicPrintConfig *config);
+ ~AstroBox() override = default;
+
+ const char* get_name() const override;
+
+ bool test(wxString &curl_msg) const override;
+ wxString get_test_ok_msg () const override;
+ wxString get_test_failed_msg (wxString &msg) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
+ bool has_auto_discovery() const override { return true; }
+ bool can_test() const override { return true; }
+ PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
+ std::string get_host() const override { return host; }
+
+protected:
+ bool validate_version_text(const boost::optional &version_text) const;
+
+private:
+ std::string host;
+ std::string apikey;
+ std::string cafile;
+
+ void set_auth(Http &http) const;
+ std::string make_url(const std::string &path) const;
+};
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp
new file mode 100644
index 000000000..3293a3ff2
--- /dev/null
+++ b/src/slic3r/Utils/Duet.cpp
@@ -0,0 +1,286 @@
+#include "Duet.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "libslic3r/PrintConfig.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/I18N.hpp"
+#include "slic3r/GUI/MsgDialog.hpp"
+#include "Http.hpp"
+
+namespace fs = boost::filesystem;
+namespace pt = boost::property_tree;
+
+namespace Slic3r {
+
+Duet::Duet(DynamicPrintConfig *config) :
+ host(config->opt_string("print_host")),
+ password(config->opt_string("printhost_apikey"))
+{}
+
+const char* Duet::get_name() const { return "Duet"; }
+
+bool Duet::test(wxString &msg) const
+{
+ auto connectionType = connect(msg);
+ disconnect(connectionType);
+
+ return connectionType != ConnectionType::error;
+}
+
+wxString Duet::get_test_ok_msg () const
+{
+ return _(L("Connection to Duet works correctly."));
+}
+
+wxString Duet::get_test_failed_msg (wxString &msg) const
+{
+ return GUI::from_u8((boost::format("%s: %s")
+ % _utf8(L("Could not connect to Duet"))
+ % std::string(msg.ToUTF8())).str());
+}
+
+bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
+{
+ wxString connect_msg;
+ auto connectionType = connect(connect_msg);
+ if (connectionType == ConnectionType::error) {
+ error_fn(std::move(connect_msg));
+ return false;
+ }
+
+ bool res = true;
+ bool dsf = (connectionType == ConnectionType::dsf);
+
+ auto upload_cmd = get_upload_url(upload_data.upload_path.string(), connectionType);
+ BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filepath: %2%, post_action: %3%, command: %4%")
+ % upload_data.source_path
+ % upload_data.upload_path
+ % int(upload_data.post_action)
+ % upload_cmd;
+
+ auto http = (dsf ? Http::put(std::move(upload_cmd)) : Http::post(std::move(upload_cmd)));
+ if (dsf) {
+ http.set_put_body(upload_data.source_path);
+ } else {
+ http.set_post_body(upload_data.source_path);
+ }
+ http.on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
+
+ int err_code = dsf ? (status == 201 ? 0 : 1) : get_err_code_from_body(body);
+ if (err_code != 0) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Request completed but error code was received: %1%") % err_code;
+ error_fn(format_error(body, L("Unknown error occured"), 0));
+ res = false;
+ } else if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
+ wxString errormsg;
+ res = start_print(errormsg, upload_data.upload_path.string(), connectionType, false);
+ if (! res) {
+ error_fn(std::move(errormsg));
+ }
+ } else if (upload_data.post_action == PrintHostPostUploadAction::StartSimulation) {
+ wxString errormsg;
+ res = start_print(errormsg, upload_data.upload_path.string(), connectionType, true);
+ if (! res) {
+ error_fn(std::move(errormsg));
+ }
+ }
+ })
+ .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;
+ error_fn(format_error(body, error, status));
+ res = false;
+ })
+ .on_progress([&](Http::Progress progress, bool &cancel) {
+ prorgess_fn(std::move(progress), cancel);
+ if (cancel) {
+ // Upload was canceled
+ BOOST_LOG_TRIVIAL(info) << "Duet: Upload canceled";
+ res = false;
+ }
+ })
+ .perform_sync();
+
+ disconnect(connectionType);
+
+ return res;
+}
+
+Duet::ConnectionType Duet::connect(wxString &msg) const
+{
+ auto res = ConnectionType::error;
+ auto url = get_connect_url(false);
+
+ auto http = Http::get(std::move(url));
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ auto dsfUrl = get_connect_url(true);
+ auto dsfHttp = Http::get(std::move(dsfUrl));
+ dsfHttp.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&](std::string body, unsigned) {
+ res = ConnectionType::dsf;
+ })
+ .perform_sync();
+ })
+ .on_complete([&](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
+
+ int err_code = get_err_code_from_body(body);
+ switch (err_code) {
+ case 0:
+ res = ConnectionType::rrf;
+ break;
+ case 1:
+ msg = format_error(body, L("Wrong password"), 0);
+ break;
+ case 2:
+ msg = format_error(body, L("Could not get resources to create a new connection"), 0);
+ break;
+ default:
+ msg = format_error(body, L("Unknown error occured"), 0);
+ break;
+ }
+
+ })
+ .perform_sync();
+
+ return res;
+}
+
+void Duet::disconnect(ConnectionType connectionType) const
+{
+ // we don't need to disconnect from DSF or if it failed anyway
+ if (connectionType != ConnectionType::rrf) {
+ return;
+ }
+ auto url = (boost::format("%1%rr_disconnect")
+ % get_base_url()).str();
+
+ auto http = Http::get(std::move(url));
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ // we don't care about it, if disconnect is not working Duet will disconnect automatically after some time
+ BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ })
+ .perform_sync();
+}
+
+std::string Duet::get_upload_url(const std::string &filename, ConnectionType connectionType) const
+{
+ assert(connectionType != ConnectionType::error);
+
+ if (connectionType == ConnectionType::dsf) {
+ return (boost::format("%1%machine/file/gcodes/%2%")
+ % get_base_url()
+ % Http::url_encode(filename)).str();
+ } else {
+ return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
+ % get_base_url()
+ % Http::url_encode(filename)
+ % timestamp_str()).str();
+ }
+}
+
+std::string Duet::get_connect_url(const bool dsfUrl) const
+{
+ if (dsfUrl) {
+ return (boost::format("%1%machine/status")
+ % get_base_url()).str();
+ } else {
+ return (boost::format("%1%rr_connect?password=%2%&%3%")
+ % get_base_url()
+ % (password.empty() ? "reprap" : password)
+ % timestamp_str()).str();
+ }
+}
+
+std::string Duet::get_base_url() const
+{
+ if (host.find("http://") == 0 || host.find("https://") == 0) {
+ if (host.back() == '/') {
+ return host;
+ } else {
+ return (boost::format("%1%/") % host).str();
+ }
+ } else {
+ return (boost::format("http://%1%/") % host).str();
+ }
+}
+
+std::string Duet::timestamp_str() const
+{
+ enum { BUFFER_SIZE = 32 };
+
+ auto t = std::time(nullptr);
+ auto tm = *std::localtime(&t);
+
+ char buffer[BUFFER_SIZE];
+ std::strftime(buffer, BUFFER_SIZE, "time=%Y-%m-%dT%H:%M:%S", &tm);
+
+ return std::string(buffer);
+}
+
+bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const
+{
+ assert(connectionType != ConnectionType::error);
+
+ bool res = false;
+ bool dsf = (connectionType == ConnectionType::dsf);
+
+ auto url = dsf
+ ? (boost::format("%1%machine/code")
+ % get_base_url()).str()
+ : (boost::format(simulationMode
+ ? "%1%rr_gcode?gcode=M37%%20P\"0:/gcodes/%2%\""
+ : "%1%rr_gcode?gcode=M32%%20\"0:/gcodes/%2%\"")
+ % get_base_url()
+ % Http::url_encode(filename)).str();
+
+ auto http = (dsf ? Http::post(std::move(url)) : Http::get(std::move(url)));
+ if (dsf) {
+ http.set_post_body(
+ (boost::format(simulationMode
+ ? "M37 P\"0:/gcodes/%1%\""
+ : "M32 \"0:/gcodes/%1%\"")
+ % filename).str()
+ );
+ }
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
+ res = true;
+ })
+ .perform_sync();
+
+ return res;
+}
+
+int Duet::get_err_code_from_body(const std::string &body) const
+{
+ pt::ptree root;
+ std::istringstream iss (body); // wrap returned json to istringstream
+ pt::read_json(iss, root);
+
+ return root.get("err", 0);
+}
+
+}
diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp
new file mode 100644
index 000000000..edca66ce0
--- /dev/null
+++ b/src/slic3r/Utils/Duet.hpp
@@ -0,0 +1,48 @@
+#ifndef slic3r_Duet_hpp_
+#define slic3r_Duet_hpp_
+
+#include
+#include
+
+#include "PrintHost.hpp"
+
+namespace Slic3r {
+
+class DynamicPrintConfig;
+class Http;
+
+class Duet : public PrintHost
+{
+public:
+ explicit Duet(DynamicPrintConfig *config);
+ ~Duet() override = default;
+
+ const char* get_name() const override;
+
+ bool test(wxString &curl_msg) const override;
+ wxString get_test_ok_msg() const override;
+ wxString get_test_failed_msg(wxString &msg) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
+ bool has_auto_discovery() const override { return false; }
+ bool can_test() const override { return true; }
+ PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::StartSimulation; }
+ std::string get_host() const override { return host; }
+
+private:
+ enum class ConnectionType { rrf, dsf, error };
+ std::string host;
+ std::string password;
+
+ std::string get_upload_url(const std::string &filename, ConnectionType connectionType) const;
+ std::string get_connect_url(const bool dsfUrl) const;
+ std::string get_base_url() const;
+ std::string timestamp_str() const;
+ ConnectionType connect(wxString &msg) const;
+ void disconnect(ConnectionType connectionType) const;
+ bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const;
+ int get_err_code_from_body(const std::string &body) const;
+};
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/FlashAir.cpp b/src/slic3r/Utils/FlashAir.cpp
new file mode 100644
index 000000000..2337ac290
--- /dev/null
+++ b/src/slic3r/Utils/FlashAir.cpp
@@ -0,0 +1,229 @@
+#include "FlashAir.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "libslic3r/PrintConfig.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/I18N.hpp"
+#include "slic3r/GUI/MsgDialog.hpp"
+#include "Http.hpp"
+
+namespace fs = boost::filesystem;
+namespace pt = boost::property_tree;
+
+namespace Slic3r {
+
+FlashAir::FlashAir(DynamicPrintConfig *config) :
+ host(config->opt_string("print_host"))
+{}
+
+const char* FlashAir::get_name() const { return "FlashAir"; }
+
+bool FlashAir::test(wxString &msg) const
+{
+ // Since the request is performed synchronously here,
+ // it is ok to refer to `msg` from within the closure
+
+ const char *name = get_name();
+
+ bool res = false;
+ auto url = make_url("command.cgi", "op", "118");
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url;
+
+ auto http = Http::get(std::move(url));
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ res = false;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body;
+
+ res = boost::starts_with(body, "1");
+ if (! res) {
+ msg = _(L("Upload not enabled on FlashAir card."));
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+wxString FlashAir::get_test_ok_msg () const
+{
+ return _(L("Connection to FlashAir works correctly and upload is enabled."));
+}
+
+wxString FlashAir::get_test_failed_msg (wxString &msg) const
+{
+ return GUI::from_u8((boost::format("%s: %s\n%s")
+ % _utf8(L("Could not connect to FlashAir"))
+ % std::string(msg.ToUTF8())
+ % _utf8(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."))).str());
+}
+
+bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
+{
+ const char *name = get_name();
+
+ const auto upload_filename = upload_data.upload_path.filename();
+ const auto upload_parent_path = upload_data.upload_path.parent_path();
+ wxString test_msg;
+ if (! test(test_msg)) {
+ error_fn(std::move(test_msg));
+ return false;
+ }
+
+ bool res = false;
+
+ std::string strDest = upload_parent_path.string();
+ if (strDest.front()!='/') // Needs a leading / else root uploads fail.
+ {
+ strDest.insert(0,"/");
+ }
+
+ auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str());
+ auto urlSetDir = make_url("upload.cgi","UPDIR",strDest);
+ auto urlUpload = make_url("upload.cgi");
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%")
+ % name
+ % upload_data.source_path
+ % urlPrepare
+ % urlUpload
+ % upload_filename.string();
+
+ // set filetime for upload and make card writeprotect to prevent filesystem damage
+ auto httpPrepare = Http::get(std::move(urlPrepare));
+ httpPrepare.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error preparing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ error_fn(format_error(body, error, status));
+ res = false;
+ })
+ .on_complete([&, this](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body;
+ res = boost::icontains(body, "SUCCESS");
+ if (! res) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
+ error_fn(format_error(body, L("Unknown error occured"), 0));
+ }
+ })
+ .perform_sync();
+
+ if(! res ) {
+ return res;
+ }
+
+ // start file upload
+ auto httpDir = Http::get(std::move(urlSetDir));
+ httpDir.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error setting upload dir: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ error_fn(format_error(body, error, status));
+ res = false;
+ })
+ .on_complete([&, this](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got dir select result: %2%") % name % body;
+ res = boost::icontains(body, "SUCCESS");
+ if (! res) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
+ error_fn(format_error(body, L("Unknown error occured"), 0));
+ }
+ })
+ .perform_sync();
+
+ if(! res ) {
+ return res;
+ }
+
+ auto http = Http::post(std::move(urlUpload));
+ http.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
+ .on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
+ res = boost::icontains(body, "SUCCESS");
+ if (! res) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
+ error_fn(format_error(body, L("Unknown error occured"), 0));
+ }
+ })
+ .on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ error_fn(format_error(body, error, status));
+ res = false;
+ })
+ .on_progress([&](Http::Progress progress, bool &cancel) {
+ prorgess_fn(std::move(progress), cancel);
+ if (cancel) {
+ // Upload was canceled
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Upload canceled") % name;
+ res = false;
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+std::string FlashAir::timestamp_str() const
+{
+ auto t = std::time(nullptr);
+ auto tm = *std::localtime(&t);
+
+ unsigned long fattime = ((tm.tm_year - 80) << 25) |
+ ((tm.tm_mon + 1) << 21) |
+ (tm.tm_mday << 16) |
+ (tm.tm_hour << 11) |
+ (tm.tm_min << 5) |
+ (tm.tm_sec >> 1);
+
+ return (boost::format("%1$#x") % fattime).str();
+}
+
+std::string FlashAir::make_url(const std::string &path) const
+{
+ if (host.find("http://") == 0 || host.find("https://") == 0) {
+ if (host.back() == '/') {
+ return (boost::format("%1%%2%") % host % path).str();
+ } else {
+ return (boost::format("%1%/%2%") % host % path).str();
+ }
+ } else {
+ if (host.back() == '/') {
+ return (boost::format("http://%1%%2%") % host % path).str();
+ } else {
+ return (boost::format("http://%1%/%2%") % host % path).str();
+ }
+ }
+}
+
+std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const
+{
+ if (host.find("http://") == 0 || host.find("https://") == 0) {
+ if (host.back() == '/') {
+ return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str();
+ } else {
+ return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str();
+ }
+ } else {
+ if (host.back() == '/') {
+ return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str();
+ } else {
+ return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str();
+ }
+ }
+}
+
+}
diff --git a/src/slic3r/Utils/FlashAir.hpp b/src/slic3r/Utils/FlashAir.hpp
new file mode 100644
index 000000000..14e3f0015
--- /dev/null
+++ b/src/slic3r/Utils/FlashAir.hpp
@@ -0,0 +1,42 @@
+#ifndef slic3r_FlashAir_hpp_
+#define slic3r_FlashAir_hpp_
+
+#include
+#include
+
+#include "PrintHost.hpp"
+
+
+namespace Slic3r {
+
+class DynamicPrintConfig;
+class Http;
+
+class FlashAir : public PrintHost
+{
+public:
+ FlashAir(DynamicPrintConfig *config);
+ ~FlashAir() override = default;
+
+ const char* get_name() const override;
+
+ bool test(wxString &curl_msg) const override;
+ wxString get_test_ok_msg() const override;
+ wxString get_test_failed_msg(wxString &msg) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
+ bool has_auto_discovery() const override { return false; }
+ bool can_test() const override { return true; }
+ PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
+ std::string get_host() const override { return host; }
+
+private:
+ std::string host;
+
+ std::string timestamp_str() const;
+ std::string make_url(const std::string &path) const;
+ std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const;
+};
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/MKS.cpp b/src/slic3r/Utils/MKS.cpp
new file mode 100644
index 000000000..80a79537d
--- /dev/null
+++ b/src/slic3r/Utils/MKS.cpp
@@ -0,0 +1,150 @@
+#include "MKS.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "libslic3r/PrintConfig.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/I18N.hpp"
+#include "slic3r/GUI/MsgDialog.hpp"
+#include "Http.hpp"
+
+namespace fs = boost::filesystem;
+namespace pt = boost::property_tree;
+
+namespace Slic3r {
+
+MKS::MKS(DynamicPrintConfig* config) :
+ m_host(config->opt_string("print_host")), m_console_port("8080")
+{}
+
+const char* MKS::get_name() const { return "MKS"; }
+
+bool MKS::test(wxString& msg) const
+{
+ Utils::TCPConsole console(m_host, m_console_port);
+
+ console.enqueue_cmd("M105");
+ bool ret = console.run_queue();
+
+ if (!ret)
+ msg = wxString::FromUTF8(console.error_message().c_str());
+
+ return ret;
+}
+
+wxString MKS::get_test_ok_msg() const
+{
+ return _(L("Connection to MKS works correctly."));
+}
+
+wxString MKS::get_test_failed_msg(wxString& msg) const
+{
+ return GUI::from_u8((boost::format("%s: %s")
+ % _utf8(L("Could not connect to MKS"))
+ % std::string(msg.ToUTF8())).str());
+}
+
+bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
+{
+ bool res = true;
+
+ auto upload_cmd = get_upload_url(upload_data.upload_path.string());
+ BOOST_LOG_TRIVIAL(info) << boost::format("MKS: Uploading file %1%, filepath: %2%, print: %3%, command: %4%")
+ % upload_data.source_path
+ % upload_data.upload_path
+ % (upload_data.post_action == PrintHostPostUploadAction::StartPrint)
+ % upload_cmd;
+
+ auto http = Http::post(std::move(upload_cmd));
+ http.set_post_body(upload_data.source_path);
+
+ http.on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("MKS: File uploaded: HTTP %1%: %2%") % status % body;
+
+ int err_code = get_err_code_from_body(body);
+ if (err_code != 0) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("MKS: Request completed but error code was received: %1%") % err_code;
+ error_fn(format_error(body, L("Unknown error occured"), 0));
+ res = false;
+ }
+ else if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
+ wxString errormsg;
+ res = start_print(errormsg, upload_data.upload_path.string());
+ if (!res) {
+ error_fn(std::move(errormsg));
+ }
+ }
+ })
+ .on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("MKS: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ error_fn(format_error(body, error, status));
+ res = false;
+ })
+ .on_progress([&](Http::Progress progress, bool& cancel) {
+ prorgess_fn(std::move(progress), cancel);
+ if (cancel) {
+ // Upload was canceled
+ BOOST_LOG_TRIVIAL(info) << "MKS: Upload canceled";
+ res = false;
+ }
+ }).perform_sync();
+
+
+ return res;
+}
+
+std::string MKS::get_upload_url(const std::string& filename) const
+{
+ return (boost::format("http://%1%/upload?X-Filename=%2%")
+ % m_host
+ % Http::url_encode(filename)).str();
+}
+
+bool MKS::start_print(wxString& msg, const std::string& filename) const
+{
+ // For some reason printer firmware does not want to respond on gcode commands immediately after file upload.
+ // So we just introduce artificial delay to workaround it.
+ // TODO: Inspect reasons
+ std::this_thread::sleep_for(std::chrono::milliseconds(1500));
+
+ Utils::TCPConsole console(m_host, m_console_port);
+
+ console.enqueue_cmd(std::string("M23 ") + filename);
+ console.enqueue_cmd("M24");
+
+ bool ret = console.run_queue();
+
+ if (!ret)
+ msg = wxString::FromUTF8(console.error_message().c_str());
+
+ return ret;
+}
+
+int MKS::get_err_code_from_body(const std::string& body) const
+{
+ pt::ptree root;
+ std::istringstream iss(body); // wrap returned json to istringstream
+ pt::read_json(iss, root);
+
+ return root.get("err", 0);
+}
+
+} // Slic3r
diff --git a/src/slic3r/Utils/MKS.hpp b/src/slic3r/Utils/MKS.hpp
new file mode 100644
index 000000000..22455436a
--- /dev/null
+++ b/src/slic3r/Utils/MKS.hpp
@@ -0,0 +1,42 @@
+#ifndef slic3r_MKS_hpp_
+#define slic3r_MKS_hpp_
+
+#include
+#include
+
+#include "PrintHost.hpp"
+#include "TCPConsole.hpp"
+
+namespace Slic3r {
+class DynamicPrintConfig;
+class Http;
+
+class MKS : public PrintHost
+{
+public:
+ explicit MKS(DynamicPrintConfig* config);
+ ~MKS() override = default;
+
+ const char* get_name() const override;
+
+ bool test(wxString& curl_msg) const override;
+ wxString get_test_ok_msg() const override;
+ wxString get_test_failed_msg(wxString& msg) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
+ bool has_auto_discovery() const override { return false; }
+ bool can_test() const override { return true; }
+ PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
+ std::string get_host() const override { return m_host; }
+
+private:
+ std::string m_host;
+ std::string m_console_port;
+
+ std::string get_upload_url(const std::string& filename) const;
+ bool start_print(wxString& msg, const std::string& filename) const;
+ int get_err_code_from_body(const std::string& body) const;
+};
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp
index aa50b1b1c..86f6101b6 100644
--- a/src/slic3r/Utils/PrintHost.cpp
+++ b/src/slic3r/Utils/PrintHost.cpp
@@ -14,11 +14,11 @@
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Channel.hpp"
#include "OctoPrint.hpp"
-//#include "Duet.hpp"
-//#include "FlashAir.hpp"
-//#include "AstroBox.hpp"
-//#include "Repetier.hpp"
-//#include "MKS.hpp"
+#include "Duet.hpp"
+#include "FlashAir.hpp"
+#include "AstroBox.hpp"
+#include "Repetier.hpp"
+#include "MKS.hpp"
#include "../GUI/PrintHostDialogs.hpp"
namespace fs = boost::filesystem;
@@ -47,12 +47,12 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
switch (host_type) {
case htOctoPrint: return new OctoPrint(config);
- //case htDuet: return new Duet(config);
- //case htFlashAir: return new FlashAir(config);
- //case htAstroBox: return new AstroBox(config);
- //case htRepetier: return new Repetier(config);
+ case htDuet: return new Duet(config);
+ case htFlashAir: return new FlashAir(config);
+ case htAstroBox: return new AstroBox(config);
+ case htRepetier: return new Repetier(config);
case htPrusaLink: return new PrusaLink(config);
- //case htMKS: return new MKS(config);
+ case htMKS: return new MKS(config);
default: return nullptr;
}
} else {
diff --git a/src/slic3r/Utils/Repetier.cpp b/src/slic3r/Utils/Repetier.cpp
new file mode 100644
index 000000000..63f438428
--- /dev/null
+++ b/src/slic3r/Utils/Repetier.cpp
@@ -0,0 +1,274 @@
+#include "Repetier.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+
+#include "libslic3r/PrintConfig.hpp"
+#include "slic3r/GUI/I18N.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/format.hpp"
+#include "Http.hpp"
+
+
+namespace fs = boost::filesystem;
+namespace pt = boost::property_tree;
+
+
+namespace Slic3r {
+
+Repetier::Repetier(DynamicPrintConfig *config) :
+ host(config->opt_string("print_host")),
+ apikey(config->opt_string("printhost_apikey")),
+ cafile(config->opt_string("printhost_cafile")),
+ port(config->opt_string("printhost_port"))
+{}
+
+const char* Repetier::get_name() const { return "Repetier"; }
+
+bool Repetier::test(wxString &msg) const
+{
+ // Since the request is performed synchronously here,
+ // it is ok to refer to `msg` from within the closure
+
+ const char *name = get_name();
+
+ bool res = true;
+ auto url = make_url("printer/info");
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: List version at: %2%") % name % url;
+
+ auto http = Http::get(std::move(url));
+ set_auth(http);
+
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ res = false;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&, this](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
+
+ try {
+ std::stringstream ss(body);
+ pt::ptree ptree;
+ pt::read_json(ss, ptree);
+
+ const auto text = ptree.get_optional("name");
+ res = validate_version_text(text);
+ if (! res) {
+ msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "Repetier")).str());
+ }
+ }
+ catch (const std::exception &) {
+ res = false;
+ msg = "Could not parse server response";
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+wxString Repetier::get_test_ok_msg () const
+{
+ return _(L("Connection to Repetier works correctly."));
+}
+
+wxString Repetier::get_test_failed_msg (wxString &msg) const
+{
+ return GUI::from_u8((boost::format("%s: %s\n\n%s")
+ % _utf8(L("Could not connect to Repetier"))
+ % std::string(msg.ToUTF8())
+ % _utf8(L("Note: Repetier version at least 0.90.0 is required."))).str());
+}
+
+bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
+{
+ const char *name = get_name();
+
+ const auto upload_filename = upload_data.upload_path.filename();
+ const auto upload_parent_path = upload_data.upload_path.parent_path();
+
+ wxString test_msg;
+ if (! test(test_msg)) {
+ error_fn(std::move(test_msg));
+ return false;
+ }
+
+ bool res = true;
+
+ auto url = upload_data.post_action == PrintHostPostUploadAction::StartPrint
+ ? make_url((boost::format("printer/job/%1%") % port).str())
+ : make_url((boost::format("printer/model/%1%") % port).str());
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, group: %7%")
+ % name
+ % upload_data.source_path
+ % url
+ % upload_filename.string()
+ % upload_parent_path.string()
+ % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
+ % upload_data.group;
+
+ auto http = Http::post(std::move(url));
+ set_auth(http);
+
+ if (! upload_data.group.empty() && upload_data.group != _utf8(L("Default"))) {
+ http.form_add("group", upload_data.group);
+ }
+
+ if(upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
+ http.form_add("name", upload_filename.string());
+ }
+
+ http.form_add("a", "upload")
+ .form_add_file("filename", upload_data.source_path.string(), upload_filename.string())
+ .on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
+ })
+ .on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ error_fn(format_error(body, error, status));
+ res = false;
+ })
+ .on_progress([&](Http::Progress progress, bool &cancel) {
+ prorgess_fn(std::move(progress), cancel);
+ if (cancel) {
+ // Upload was canceled
+ BOOST_LOG_TRIVIAL(info) << "Repetier: Upload canceled";
+ res = false;
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+bool Repetier::validate_version_text(const boost::optional &version_text) const
+{
+ return version_text ? (!version_text->empty()) : true;
+}
+
+void Repetier::set_auth(Http &http) const
+{
+ http.header("X-Api-Key", apikey);
+
+ if (! cafile.empty()) {
+ http.ca_file(cafile);
+ }
+}
+
+std::string Repetier::make_url(const std::string &path) const
+{
+ if (host.find("http://") == 0 || host.find("https://") == 0) {
+ if (host.back() == '/') {
+ return (boost::format("%1%%2%") % host % path).str();
+ } else {
+ return (boost::format("%1%/%2%") % host % path).str();
+ }
+ } else {
+ return (boost::format("http://%1%/%2%") % host % path).str();
+ }
+}
+
+bool Repetier::get_groups(wxArrayString& groups) const
+{
+ bool res = true;
+
+ const char *name = get_name();
+ auto url = make_url((boost::format("printer/api/%1%") % port).str());
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get groups at: %2%") % name % url;
+
+ auto http = Http::get(std::move(url));
+ set_auth(http);
+ http.form_add("a", "listModelGroups");
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ })
+ .on_complete([&](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got groups: %2%") % name % body;
+
+ try {
+ std::stringstream ss(body);
+ pt::ptree ptree;
+ pt::read_json(ss, ptree);
+
+ BOOST_FOREACH(boost::property_tree::ptree::value_type &v, ptree.get_child("groupNames.")) {
+ if (v.second.data() == "#") {
+ groups.push_back(_utf8(L("Default")));
+ } else {
+ // Is it safe to assume that the data are utf-8 encoded?
+ groups.push_back(GUI::from_u8(v.second.data()));
+ }
+ }
+ }
+ catch (const std::exception &) {
+ //msg = "Could not parse server response";
+ res = false;
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+bool Repetier::get_printers(wxArrayString& printers) const
+{
+ const char *name = get_name();
+
+ bool res = true;
+ auto url = make_url("printer/list");
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: List printers at: %2%") % name % url;
+
+ auto http = Http::get(std::move(url));
+ set_auth(http);
+
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error listing printers: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ res = false;
+ })
+ .on_complete([&](std::string body, unsigned http_status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got printers: %2%, HTTP status: %3%") % name % body % http_status;
+
+ if (http_status != 200)
+ throw HostNetworkError(GUI::format(_L("HTTP status: %1%\nMessage body: \"%2%\""), http_status, body));
+
+ std::stringstream ss(body);
+ pt::ptree ptree;
+ try {
+ pt::read_json(ss, ptree);
+ } catch (const pt::ptree_error &err) {
+ throw HostNetworkError(GUI::format(_L("Parsing of host response failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what()));
+ }
+
+ const auto error = ptree.get_optional("error");
+ if (error)
+ throw HostNetworkError(*error);
+
+ try {
+ BOOST_FOREACH(boost::property_tree::ptree::value_type &v, ptree.get_child("data.")) {
+ const auto port = v.second.get("slug");
+ printers.push_back(Slic3r::GUI::from_u8(port));
+ }
+ } catch (const std::exception &err) {
+ throw HostNetworkError(GUI::format(_L("Enumeration of host printers failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what()));
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+}
diff --git a/src/slic3r/Utils/Repetier.hpp b/src/slic3r/Utils/Repetier.hpp
new file mode 100644
index 000000000..6f3310260
--- /dev/null
+++ b/src/slic3r/Utils/Repetier.hpp
@@ -0,0 +1,51 @@
+#ifndef slic3r_Repetier_hpp_
+#define slic3r_Repetier_hpp_
+
+#include
+#include
+#include
+
+#include "PrintHost.hpp"
+
+namespace Slic3r {
+
+class DynamicPrintConfig;
+class Http;
+
+class Repetier : public PrintHost
+{
+public:
+ Repetier(DynamicPrintConfig *config);
+ ~Repetier() override = default;
+
+ const char* get_name() const override;
+
+ bool test(wxString &curl_msg) const override;
+ wxString get_test_ok_msg () const override;
+ wxString get_test_failed_msg (wxString &msg) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
+ bool has_auto_discovery() const override { return false; }
+ bool can_test() const override { return true; }
+ PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
+ bool supports_multiple_printers() const override { return true; }
+ std::string get_host() const override { return host; }
+
+ bool get_groups(wxArrayString &groups) const override;
+ bool get_printers(wxArrayString &printers) const override;
+
+protected:
+ virtual bool validate_version_text(const boost::optional &version_text) const;
+
+private:
+ std::string host;
+ std::string apikey;
+ std::string cafile;
+ std::string port;
+
+ void set_auth(Http &http) const;
+ std::string make_url(const std::string &path) const;
+};
+
+}
+
+#endif