Merge of pull request Add support for RepetierServer #4384 by @docbobo

with the following refactorings:

1) Removed the "printhost_slug" config from the Printer config
   and from all the Printer config related spots.
2) "printhost_slug" renamed to "printhost_port". Slug sounds nasty.
3) Improved error reporting of RepetierHost class.
4) Refactored for the new "Physical Printers"

Following refactorings were done independently of the Repetier pull request:
1) Removed PrintHost static print config.
2) Clean-up after conversion of print host configuration
   from Printer config to Physical Printer config.
3) Fixed some issues, where the Printer config was still queried for
   host configuration. Vojtech believes that this should not happen
   after the host configuration is converted to physical printers.

Vojtech still feels that more refactoring is needed in regard to porting
the host configuration from Printer profile to the new Physical Printer
profile.
This commit is contained in:
Vojtech Bubnik 2020-10-28 09:51:05 +01:00
parent 0798fa8185
commit 7c571c1d9d
24 changed files with 556 additions and 200 deletions

View file

@ -193,6 +193,8 @@ set(SLIC3R_GUI_SOURCES
Utils/FlashAir.hpp
Utils/AstroBox.cpp
Utils/AstroBox.hpp
Utils/Repetier.cpp
Utils/Repetier.hpp
Utils/PrintHost.cpp
Utils/PrintHost.hpp
Utils/Bonjour.cpp

View file

@ -1048,13 +1048,33 @@ void Choice::set_values(const std::vector<std::string>& values)
auto value = ww->GetValue();
ww->Clear();
ww->Append("");
for (auto el : values)
for (const auto &el : values)
ww->Append(wxString(el));
ww->SetValue(value);
m_disable_change_event = false;
}
void Choice::set_values(const wxArrayString &values)
{
if (values.empty())
return;
m_disable_change_event = true;
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
// # but we want to preserve it
auto ww = dynamic_cast<wxBitmapComboBox*>(window);
auto value = ww->GetValue();
ww->Clear();
ww->Append("");
for (const auto &el : values)
ww->Append(el);
ww->SetValue(value);
m_disable_change_event = false;
}
boost::any& Choice::get_value()
{
wxBitmapComboBox* field = dynamic_cast<wxBitmapComboBox*>(window);

View file

@ -405,6 +405,7 @@ public:
void set_value(const std::string& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false);
void set_values(const std::vector<std::string> &values);
void set_values(const wxArrayString &values);
boost::any& get_value() override;
void msw_rescale(bool rescale_sidetext = false) override;

View file

@ -750,14 +750,11 @@ bool MainFrame::can_export_gcode() const
bool MainFrame::can_send_gcode() const
{
if (m_plater == nullptr)
return false;
if (m_plater->model().objects.empty())
return false;
const auto print_host_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionString>("print_host");
return print_host_opt != nullptr && !print_host_opt->value.empty();
if (m_plater && ! m_plater->model().objects.empty())
if (const DynamicPrintConfig *cfg = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); cfg)
if (const auto *print_host_opt = cfg->option<ConfigOptionString>("print_host"); print_host_opt)
return ! print_host_opt->value.empty();
return false;
}
bool MainFrame::can_export_gcode_sd() const

View file

@ -32,10 +32,6 @@
#include "BitmapCache.hpp"
#include "BonjourDialog.hpp"
using Slic3r::GUI::format_wxstr;
//static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
namespace Slic3r {
namespace GUI {
@ -248,10 +244,30 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog()
}
}
void PhysicalPrinterDialog::update_printers()
{
wxBusyCursor wait;
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
wxArrayString printers;
Field *rs = m_optgroup->get_field("printhost_port");
try {
if (! host->get_printers(printers))
printers.clear();
} catch (const HostNetworkError &err) {
printers.clear();
show_error(this, _L("Querying printers connected to a print host failed.") + "\n\n" + from_u8(err.what()));
}
Choice *choice = dynamic_cast<Choice*>(rs);
choice->set_values(printers);
printers.empty() ? rs->disable() : rs->enable();
}
void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup)
{
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
if (opt_key == "printhost_authorization_type")
if (opt_key == "host_type" || opt_key == "printhost_authorization_type")
this->update();
};
@ -291,17 +307,30 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
return;
}
wxString msg;
if (host->test(msg)) {
bool result;
{
// Show a wait cursor during the connection test, as it is blocking UI.
wxBusyCursor wait;
result = host->test(msg);
}
if (result)
show_info(this, host->get_test_ok_msg(), _L("Success!"));
}
else {
else
show_error(this, host->get_test_failed_msg(msg));
}
});
return sizer;
};
auto print_host_printers = [this, create_sizer_with_btn](wxWindow* parent) {
//add_scaled_button(parent, &m_printhost_port_browse_btn, "browse", _(L("Refresh Printers")), wxBU_LEFT | wxBU_EXACTFIT);
auto sizer = create_sizer_with_btn(parent, &m_printhost_port_browse_btn, "browse", _(L("Refresh Printers")));
ScalableButton* btn = m_printhost_port_browse_btn;
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) { update_printers(); });
return sizer;
};
// Set a wider width for a better alignment
Option option = m_optgroup->get_option("print_host");
option.opt.width = Field::def_width_wider();
@ -316,6 +345,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
option.opt.width = Field::def_width_wider();
m_optgroup->append_single_option_line(option);
option = m_optgroup->get_option("printhost_port");
option.opt.width = Field::def_width_wider();
Line port_line = m_optgroup->create_single_option_line(option);
port_line.append_widget(print_host_printers);
m_optgroup->append_line(port_line);
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()) {
@ -377,6 +412,14 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
}
m_optgroup->activate();
// Always fill in the "printhost_port" combo box from the config and select it.
{
Choice* choice = dynamic_cast<Choice*>(m_optgroup->get_field("printhost_port"));
choice->set_values({ m_config->opt_string("printhost_port") });
choice->set_selection();
}
update();
}
@ -386,11 +429,14 @@ void PhysicalPrinterDialog::update()
const PrinterTechnology tech = Preset::printer_technology(*m_config);
// Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment)
bool supports_multiple_printers = false;
if (tech == ptFFF) {
m_optgroup->show_field("host_type");
m_optgroup->hide_field("printhost_authorization_type");
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
m_optgroup->hide_field(opt_key);
const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
supports_multiple_printers = opt && opt->value == htRepetier;
}
else {
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
@ -401,10 +447,12 @@ void PhysicalPrinterDialog::update()
AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("printhost_authorization_type")->value;
m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword);
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
for (const char *opt_key : { "printhost_user", "printhost_password" })
m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword);
}
m_optgroup->show_field("printhost_port", supports_multiple_printers);
m_printhost_port_browse_btn->Show(supports_multiple_printers);
this->Layout();
}

View file

@ -73,6 +73,7 @@ class PhysicalPrinterDialog : public DPIDialog
ScalableButton* m_printhost_browse_btn {nullptr};
ScalableButton* m_printhost_test_btn {nullptr};
ScalableButton* m_printhost_cafile_browse_btn {nullptr};
ScalableButton* m_printhost_port_browse_btn {nullptr};
wxBoxSizer* m_presets_sizer {nullptr};
@ -85,6 +86,7 @@ public:
~PhysicalPrinterDialog();
void update();
void update_printers();
wxString get_printer_name();
void update_full_printer_names();
PhysicalPrinter* get_printer() {return &m_printer; }

View file

@ -1819,8 +1819,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
, main_frame(main_frame)
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
"brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host",
"printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material",
"brim_width", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle",
"extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology",
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
@ -4292,11 +4291,8 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
wxWindowUpdateLocker noUpdater(sidebar);
DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
if (!selected_printer_config)
selected_printer_config = config;
const auto prin_host_opt = selected_printer_config->option<ConfigOptionString>("print_host");
const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty();
const auto print_host_opt = selected_printer_config ? selected_printer_config->option<ConfigOptionString>("print_host") : nullptr;
const bool send_gcode_shown = print_host_opt != nullptr && !print_host_opt->value.empty();
// when a background processing is ON, export_btn and/or send_btn are showing
if (wxGetApp().app_config->get("background_processing") == "1")
@ -5304,12 +5300,14 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &
void Plater::send_gcode()
{
if (p->model.objects.empty()) { return; }
// if physical_printer is selected, send gcode for this printer
DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
PrintHostJob upload_job(physical_printer_config ? physical_printer_config : p->config);
if (upload_job.empty()) { return; }
if (! physical_printer_config || p->model.objects.empty())
return;
PrintHostJob upload_job(physical_printer_config);
if (upload_job.empty())
return;
// Obtain default output path
fs::path default_output_file;
@ -5327,11 +5325,18 @@ void Plater::send_gcode()
}
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->can_start_print());
// Repetier specific: Query the server for the list of file groups.
wxArrayString groups;
{
wxBusyCursor wait;
upload_job.printhost->get_groups(groups);
}
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->can_start_print(), groups);
if (dlg.ShowModal() == wxID_OK) {
upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.start_print = dlg.start_print();
upload_job.upload_data.group = dlg.group();
p->export_gcode(fs::path(), false, std::move(upload_job));
}
}

View file

@ -28,18 +28,20 @@ namespace GUI {
static const char *CONFIG_KEY_PATH = "printhost_path";
static const char *CONFIG_KEY_PRINT = "printhost_print";
static const char *CONFIG_KEY_GROUP = "printhost_group";
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print)
: MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE)
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print, const wxArrayString &groups)
: MsgDialog(nullptr, _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), wxID_NONE)
, txt_filename(new wxTextCtrl(this, wxID_ANY))
, box_print(can_start_print ? new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))) : nullptr)
, box_print(can_start_print ? new wxCheckBox(this, wxID_ANY, _L("Start printing after upload")) : nullptr)
, combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
{
#ifdef __APPLE__
txt_filename->OSXDisableAllSmartSubstitutions();
#endif
const AppConfig *app_config = wxGetApp().app_config;
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Use forward slashes ( / ) as a directory separator if needed."));
label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
content_sizer->Add(txt_filename, 0, wxEXPAND);
@ -49,10 +51,19 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1");
}
if (combo_groups != nullptr) {
// Repetier specific: Show a selection of file groups.
auto *label_group = new wxStaticText(this, wxID_ANY, _L("Group"));
content_sizer->Add(label_group);
content_sizer->Add(combo_groups, 0, wxBOTTOM, 2*VERT_SPACING);
wxString recent_group = from_u8(app_config->get("recent", CONFIG_KEY_GROUP));
if (! recent_group.empty())
combo_groups->SetValue(recent_group);
}
btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
recent_path += '/';
@ -64,7 +75,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
txt_filename->SetValue(recent_path);
txt_filename->SetFocus();
Fit();
Bind(wxEVT_SHOW, [=](const wxShowEvent &) {
@ -86,6 +97,16 @@ bool PrintHostSendDialog::start_print() const
return box_print != nullptr ? box_print->GetValue() : false;
}
std::string PrintHostSendDialog::group() const
{
if (combo_groups == nullptr) {
return "";
} else {
wxString group = combo_groups->GetValue();
return into_u8(group);
}
}
void PrintHostSendDialog::EndModal(int ret)
{
if (ret == wxID_OK) {
@ -96,9 +117,15 @@ void PrintHostSendDialog::EndModal(int ret)
path.clear();
else
path = path.SubString(0, last_slash);
AppConfig *app_config = wxGetApp().app_config;
app_config->set("recent", CONFIG_KEY_PATH, into_u8(path));
app_config->set("recent", CONFIG_KEY_PRINT, start_print() ? "1" : "0");
if (combo_groups != nullptr) {
wxString group = combo_groups->GetValue();
app_config->set("recent", CONFIG_KEY_GROUP, into_u8(group));
}
}
MsgDialog::EndModal(ret);
@ -133,7 +160,7 @@ wxEvent *PrintHostQueueDialog::Event::Clone() const
}
PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
: DPIDialog(parent, wxID_ANY, _(L("Print host upload queue")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
: DPIDialog(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)
, on_cancel_evt(this, EVT_PRINTHOST_CANCEL, &PrintHostQueueDialog::on_cancel, this)
@ -144,19 +171,20 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
job_list = new wxDataViewListCtrl(this, wxID_ANY);
// Note: Keep these in sync with Column
job_list->AppendTextColumn(_(L("ID")), wxDATAVIEW_CELL_INERT);
job_list->AppendProgressColumn(_(L("Progress")), wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn(_(L("Status")), wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn(_(L("Host")), wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn(_(L("Filename")), wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn(_(L("Error Message")), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT);
job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn(_L("Error Message"), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
btn_cancel = new wxButton(this, wxID_DELETE, _(L("Cancel selected")));
btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected"));
btn_cancel->Disable();
btn_error = new wxButton(this, wxID_ANY, _(L("Show error message")));
btn_error = new wxButton(this, wxID_ANY, _L("Show error message"));
btn_error->Disable();
auto *btn_close = new wxButton(this, wxID_CANCEL, _(L("Close"))); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
// Note: The label needs to be present, otherwise we get accelerator bugs on Mac
auto *btn_close = new wxButton(this, wxID_CANCEL, _L("Close"));
btnsizer->Add(btn_cancel, 0, wxRIGHT, SPACING);
btnsizer->Add(btn_error, 0);
btnsizer->AddStretchSpacer();
@ -195,7 +223,7 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &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(_L("Enqueued")));
fields.push_back(wxVariant(job.printhost->get_host()));
fields.push_back(wxVariant(job.upload_data.upload_path.string()));
fields.push_back(wxVariant(""));
@ -226,12 +254,12 @@ void PrintHostQueueDialog::set_state(int idx, JobState state)
job_list->SetItemData(job_list->RowToItem(idx), static_cast<wxUIntPtr>(state));
switch (state) {
case ST_NEW: job_list->SetValue(_(L("Enqueued")), idx, COL_STATUS); break;
case ST_PROGRESS: job_list->SetValue(_(L("Uploading")), idx, COL_STATUS); break;
case ST_ERROR: job_list->SetValue(_(L("Error")), idx, COL_STATUS); break;
case ST_CANCELLING: job_list->SetValue(_(L("Cancelling")), idx, COL_STATUS); break;
case ST_CANCELLED: job_list->SetValue(_(L("Cancelled")), idx, COL_STATUS); break;
case ST_COMPLETED: job_list->SetValue(_(L("Completed")), idx, COL_STATUS); break;
case ST_NEW: job_list->SetValue(_L("Enqueued"), idx, COL_STATUS); break;
case ST_PROGRESS: job_list->SetValue(_L("Uploading"), idx, COL_STATUS); break;
case ST_ERROR: job_list->SetValue(_L("Error"), idx, COL_STATUS); break;
case ST_CANCELLING: job_list->SetValue(_L("Cancelling"), idx, COL_STATUS); break;
case ST_CANCELLED: job_list->SetValue(_L("Cancelled"), idx, COL_STATUS); break;
case ST_COMPLETED: job_list->SetValue(_L("Completed"), idx, COL_STATUS); break;
}
}

View file

@ -15,6 +15,7 @@
class wxButton;
class wxTextCtrl;
class wxComboBox;
class wxCheckBox;
class wxDataViewListCtrl;
@ -29,14 +30,16 @@ namespace GUI {
class PrintHostSendDialog : public GUI::MsgDialog
{
public:
PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print, const wxArrayString& groups);
boost::filesystem::path filename() const;
bool start_print() const;
std::string group() const;
virtual void EndModal(int ret) override;
private:
wxTextCtrl *txt_filename;
wxCheckBox *box_print;
wxComboBox *combo_groups;
};

View file

@ -435,9 +435,6 @@ private:
std::vector<PageShp> m_pages_sla;
public:
wxButton* m_serial_test_btn = nullptr;
ScalableButton* m_print_host_test_btn = nullptr;
ScalableButton* m_printhost_browse_btn = nullptr;
ScalableButton* m_reset_to_filament_color = nullptr;
size_t m_extruders_count;

View file

@ -28,7 +28,7 @@ public:
bool can_test() const override { return true; }
bool can_start_print() const override { return true; }
std::string get_host() const override { return host; }
protected:
bool validate_version_text(const boost::optional<std::string> &version_text) const;

View file

@ -27,7 +27,7 @@ public:
bool can_test() const override { return true; }
bool can_start_print() const override { return true; }
std::string get_host() const override { return host; }
private:
enum class ConnectionType { rrf, dsf, error };
std::string host;

View file

@ -28,7 +28,7 @@ public:
bool can_test() const override { return true; }
bool can_start_print() const override { return false; }
std::string get_host() const override { return host; }
private:
std::string host;

View file

@ -9,6 +9,7 @@
#include <wx/string.h>
#include <wx/app.h>
#include <wx/arrstr.h>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Channel.hpp"
@ -16,6 +17,7 @@
#include "Duet.hpp"
#include "FlashAir.hpp"
#include "AstroBox.hpp"
#include "Repetier.hpp"
#include "../GUI/PrintHostDialogs.hpp"
namespace fs = boost::filesystem;
@ -47,6 +49,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *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);
default: return nullptr;
}
} else {

View file

@ -10,6 +10,7 @@
#include "Http.hpp"
class wxArrayString;
namespace Slic3r {
@ -20,6 +21,9 @@ struct PrintHostUpload
{
boost::filesystem::path source_path;
boost::filesystem::path upload_path;
std::string group;
bool start_print = false;
};
@ -41,8 +45,15 @@ public:
virtual bool has_auto_discovery() const = 0;
virtual bool can_test() const = 0;
virtual bool can_start_print() const = 0;
// A print host usually does not support multiple printers, with the exception of Repetier server.
virtual bool supports_multiple_printers() const { return false; }
virtual std::string get_host() const = 0;
// Support for Repetier server multiple groups & printers. Not supported by other print hosts.
// Returns false if not supported. May throw HostNetworkError.
virtual bool get_groups(wxArrayString & /* groups */) const { return false; }
virtual bool get_printers(wxArrayString & /* printers */) const { return false; }
static PrintHost* get_print_host(DynamicPrintConfig *config);
protected:

View file

@ -0,0 +1,268 @@
#include "Repetier.hpp"
#include <algorithm>
#include <sstream>
#include <exception>
#include <boost/foreach.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <wx/progdlg.h>
#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<std::string>("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 = 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.start_print
% 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);
}
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<std::string> &version_text) const
{
return version_text ? boost::starts_with(*version_text, "Repetier") : 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([&, this](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(wxString::FromUTF8(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([&, this](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<std::string>("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<std::string>("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;
}
}

View file

@ -0,0 +1,52 @@
#ifndef slic3r_Repetier_hpp_
#define slic3r_Repetier_hpp_
#include <string>
#include <wx/string.h>
#include <boost/optional.hpp>
#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;
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; }
bool can_start_print() const override { return false; }
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<std::string> &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