diff --git a/src/libslic3r/Exception.hpp b/src/libslic3r/Exception.hpp index 8ec9f20c8..2bef204ad 100644 --- a/src/libslic3r/Exception.hpp +++ b/src/libslic3r/Exception.hpp @@ -19,6 +19,7 @@ SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError); SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError); SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException); SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError); +SLIC3R_DERIVE_EXCEPTION(HostNetworkError, IOError); // Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications. SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception); #undef SLIC3R_DERIVE_EXCEPTION diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 274f84f00..c4a9f5864 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -383,6 +383,7 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print) static constexpr auto banned_keys = { "compatible_printers"sv, "compatible_prints"sv, + //FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed. "print_host"sv, "printhost_apikey"sv, "printhost_cafile"sv diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7de07cd73..c5b363bd3 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1619,7 +1619,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Append full config. _write(file, "\n"); { - std::string full_config = ""; + std::string full_config; append_full_config(print, full_config); if (!full_config.empty()) _write(file, full_config); @@ -2475,6 +2475,7 @@ void GCode::append_full_config(const Print &print, std::string &str) static constexpr auto banned_keys = { "compatible_printers"sv, "compatible_prints"sv, + //FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed. "print_host"sv, "printhost_apikey"sv, "printhost_cafile"sv diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 421a1f39e..1982a91fb 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -475,8 +475,9 @@ const std::vector<std::string>& Preset::printer_options() if (s_opts.empty()) { s_opts = { "printer_technology", - "bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", + "bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", + //FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset. "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "color_change_gcode", "pause_print_gcode", "template_custom_gcode", @@ -595,6 +596,7 @@ const std::vector<std::string>& Preset::sla_printer_options() "gamma_correction", "min_exposure_time", "max_exposure_time", "min_initial_exposure_time", "max_initial_exposure_time", + //FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset. "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", "inherits" @@ -711,38 +713,6 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string return this->load_preset(path, name, std::move(cfg), select); } -enum class ProfileHostParams -{ - Same, - Different, - Anonymized, -}; - -static ProfileHostParams profile_host_params_same_or_anonymized(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new) -{ - auto opt_print_host_old = cfg_old.option<ConfigOptionString>("print_host"); - auto opt_printhost_apikey_old = cfg_old.option<ConfigOptionString>("printhost_apikey"); - auto opt_printhost_cafile_old = cfg_old.option<ConfigOptionString>("printhost_cafile"); - - auto opt_print_host_new = cfg_new.option<ConfigOptionString>("print_host"); - auto opt_printhost_apikey_new = cfg_new.option<ConfigOptionString>("printhost_apikey"); - auto opt_printhost_cafile_new = cfg_new.option<ConfigOptionString>("printhost_cafile"); - - // If the new print host data is undefined, use the old data. - bool new_print_host_undefined = (opt_print_host_new == nullptr || opt_print_host_new ->empty()) && - (opt_printhost_apikey_new == nullptr || opt_printhost_apikey_new ->empty()) && - (opt_printhost_cafile_new == nullptr || opt_printhost_cafile_new ->empty()); - if (new_print_host_undefined) - return ProfileHostParams::Anonymized; - - auto opt_same = [](const ConfigOptionString *l, const ConfigOptionString *r) { - return ((l == nullptr || l->empty()) && (r == nullptr || r->empty())) || - (l != nullptr && r != nullptr && l->value == r->value); - }; - return (opt_same(opt_print_host_old, opt_print_host_new) && opt_same(opt_printhost_apikey_old, opt_printhost_apikey_new) && - opt_same(opt_printhost_cafile_old, opt_printhost_cafile_new)) ? ProfileHostParams::Same : ProfileHostParams::Different; -} - static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new) { t_config_option_keys diff = cfg_old.diff(cfg_new); @@ -752,10 +722,11 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const D "compatible_printers", "compatible_printers_condition", "inherits", "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile", + //FIXME remove the print host keys? "print_host", "printhost_apikey", "printhost_cafile" }) diff.erase(std::remove(diff.begin(), diff.end(), key), diff.end()); // Preset with the same name as stored inside the config exists. - return diff.empty() && profile_host_params_same_or_anonymized(cfg_old, cfg_new) != ProfileHostParams::Different; + return diff.empty(); } // Load a preset from an already parsed config file, insert it into the sorted sequence of presets @@ -784,25 +755,11 @@ Preset& PresetCollection::load_external_preset( it = this->find_preset_renamed(original_name); found = it != m_presets.end(); } - if (found) { - if (profile_print_params_same(it->config, cfg)) { - // The preset exists and it matches the values stored inside config. - if (select) - this->select_preset(it - m_presets.begin()); - return *it; - } - if (profile_host_params_same_or_anonymized(it->config, cfg) == ProfileHostParams::Anonymized) { - // The project being loaded is anonymized. Replace the empty host keys of the loaded profile with the data from the original profile. - // See "Octoprint Settings when Opening a .3MF file" GH issue #3244 - auto opt_update = [it, &cfg](const std::string &opt_key) { - auto opt = it->config.option<ConfigOptionString>(opt_key); - if (opt != nullptr) - cfg.set_key_value(opt_key, opt->clone()); - }; - opt_update("print_host"); - opt_update("printhost_apikey"); - opt_update("printhost_cafile"); - } + if (found && profile_print_params_same(it->config, cfg)) { + // The preset exists and it matches the values stored inside config. + if (select) + this->select_preset(it - m_presets.begin()); + return *it; } // Update the "inherits" field. std::string &inherits = Preset::inherits(cfg); @@ -1374,31 +1331,25 @@ const std::vector<std::string>& PhysicalPrinter::printer_options() "preset_name", "printer_technology", // "printer_model", - "host_type", - "print_host", - "printhost_apikey", + "host_type", + "print_host", + "printhost_apikey", "printhost_cafile", + "printhost_port", "printhost_authorization_type", // HTTP digest authentization (RFC 2617) - "printhost_user", + "printhost_user", "printhost_password" }; } return s_opts; } -const std::vector<std::string>& PhysicalPrinter::print_host_options() -{ - static std::vector<std::string> s_opts; - if (s_opts.empty()) { - s_opts = { - "print_host", - "printhost_apikey", - "printhost_cafile" - }; - } - return s_opts; -} +static constexpr auto legacy_print_host_options = { + "print_host", + "printhost_apikey", + "printhost_cafile", +}; std::vector<std::string> PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection& printer_presets) { @@ -1412,7 +1363,7 @@ std::vector<std::string> PhysicalPrinter::presets_with_print_host_information(co bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config) { - for (const std::string& opt : print_host_options()) + for (const char *opt : legacy_print_host_options) if (!config.opt_string(opt).empty()) return true; @@ -1429,6 +1380,7 @@ bool PhysicalPrinter::has_empty_config() const return config.opt_string("print_host" ).empty() && config.opt_string("printhost_apikey" ).empty() && config.opt_string("printhost_cafile" ).empty() && + config.opt_string("printhost_port" ).empty() && config.opt_string("printhost_user" ).empty() && config.opt_string("printhost_password").empty(); } @@ -1614,9 +1566,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti int cnt=0; for (Preset& preset: printer_presets) { DynamicPrintConfig& config = preset.config; - const std::vector<std::string>& options = PhysicalPrinter::print_host_options(); - - for(const std::string& option : options) { + for(const char* option : legacy_print_host_options) { if (!config.opt_string(option).empty()) { // check if printer with those "Print Host upload" options already exist PhysicalPrinter* existed_printer = find_printer_with_same_config(config); @@ -1635,7 +1585,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti } // erase "Print Host upload" information from the preset - for (const std::string& opt : options) + for (const char *opt : legacy_print_host_options) config.opt_string(opt).clear(); // save changes for preset preset.save(); @@ -1643,7 +1593,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti // update those changes for edited preset if it's equal to the preset Preset& edited = printer_presets.get_edited_preset(); if (preset.name == edited.name) { - for (const std::string& opt : options) + for (const char *opt : legacy_print_host_options) edited.config.opt_string(opt).clear(); } @@ -1692,7 +1642,7 @@ PhysicalPrinter* PhysicalPrinterCollection::find_printer_with_same_config(const { for (const PhysicalPrinter& printer :*this) { bool is_equal = true; - for (const std::string& opt : PhysicalPrinter::print_host_options()) + for (const char *opt : legacy_print_host_options) if (is_equal && printer.config.opt_string(opt) != config.opt_string(opt)) is_equal = false; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index b1166d2a4..44df000c3 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -37,11 +37,11 @@ static std::vector<std::string> s_project_options { const char *PresetBundle::PRUSA_BUNDLE = "PrusaResearch"; PresetBundle::PresetBundle() : - prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults())), - filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults())), + prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())), + filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())), sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast<const SLAMaterialConfig&>(SLAFullPrintConfig::defaults())), sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast<const SLAPrintObjectConfig&>(SLAFullPrintConfig::defaults())), - printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults()), "- default FFF -"), + printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults()), "- default FFF -"), physical_printers(PhysicalPrinter::printer_options()) { // The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes, @@ -77,11 +77,12 @@ PresetBundle::PresetBundle() : for (size_t i = 0; i < 2; ++ i) { // The following ugly switch is to avoid printers.preset(0) to return the edited instance, as the 0th default is the current one. Preset &preset = this->printers.default_preset(i); - preset.config.optptr("printer_settings_id", true); - preset.config.optptr("printer_vendor", true); - preset.config.optptr("printer_model", true); - preset.config.optptr("printer_variant", true); - preset.config.optptr("thumbnails", true); + for (const char *key : { + "printer_settings_id", "printer_vendor", "printer_model", "printer_variant", "thumbnails", + //FIXME the following keys are only created here for compatibility to be able to parse legacy Printer profiles. + // These keys are converted to Physical Printer profile. After the conversion, they shall be removed. + "host_type", "print_host", "printhost_apikey", "printhost_cafile"}) + preset.config.optptr(key, true); if (i == 0) { preset.config.optptr("default_print_profile", true); preset.config.option<ConfigOptionStrings>("default_filament_profile", true)->values = { "" }; @@ -495,6 +496,7 @@ DynamicPrintConfig PresetBundle::full_config() const DynamicPrintConfig PresetBundle::full_config_secure() const { DynamicPrintConfig config = this->full_config(); + //FIXME legacy, the keys should not be there after conversion to a Physical Printer profile. config.erase("print_host"); config.erase("printhost_apikey"); config.erase("printhost_cafile"); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 095a1b390..ee4a0945e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -108,7 +108,14 @@ void PrintConfigDef::init_common_params() "the API Key or the password required for authentication."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - + + def = this->add("printhost_port", coString); + def->label = L("Printer"); + def->tooltip = L("Name of the printer"); + def->gui_type = "select_open"; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + def = this->add("printhost_cafile", coString); def->label = L("HTTPS CA File"); def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " @@ -1447,10 +1454,12 @@ void PrintConfigDef::init_fff_params() 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_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->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint)); @@ -1765,26 +1774,6 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(30)); #endif - def = this->add("serial_port", coString); - def->gui_type = "select_open"; - def->label = ""; - def->full_label = L("Serial port"); - def->tooltip = L("USB/serial port for printer connection."); - def->width = 20; - def->set_default_value(new ConfigOptionString("")); - - def = this->add("serial_speed", coInt); - def->gui_type = "i_enum_open"; - def->label = L("Speed"); - def->full_label = L("Serial port speed"); - def->tooltip = L("Speed (baud) of USB/serial port for printer connection."); - def->min = 1; - def->max = 300000; - def->enum_values.push_back("115200"); - def->enum_values.push_back("250000"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(250000)); - def = this->add("skirt_distance", coFloat); def->label = L("Distance from object"); def->tooltip = L("Distance between skirt and object(s). Set this to zero to attach the skirt " @@ -3176,8 +3165,9 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "seal_position", "vibration_limit", "bed_size", "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe" #ifndef HAS_PRESSURE_EQUALIZER - , "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative" + , "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ + "serial_port", "serial_speed" }; // In PrusaSlicer 2.3.0-alpha0 the "monotonous" infill was introduced, which was later renamed to "monotonic". @@ -3498,7 +3488,6 @@ StaticPrintConfig::StaticCache<class Slic3r::PrintRegionConfig> PrintRegionConfi StaticPrintConfig::StaticCache<class Slic3r::MachineEnvelopeConfig> MachineEnvelopeConfig::s_cache_MachineEnvelopeConfig; StaticPrintConfig::StaticCache<class Slic3r::GCodeConfig> GCodeConfig::s_cache_GCodeConfig; StaticPrintConfig::StaticCache<class Slic3r::PrintConfig> PrintConfig::s_cache_PrintConfig; -StaticPrintConfig::StaticCache<class Slic3r::HostConfig> HostConfig::s_cache_HostConfig; StaticPrintConfig::StaticCache<class Slic3r::FullPrintConfig> FullPrintConfig::s_cache_FullPrintConfig; StaticPrintConfig::StaticCache<class Slic3r::SLAMaterialConfig> SLAMaterialConfig::s_cache_SLAMaterialConfig; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 99b5feb95..89c9c7a97 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -11,7 +11,6 @@ // PrintRegionConfig // PrintConfig // GCodeConfig -// HostConfig // #ifndef slic3r_PrintConfig_hpp_ @@ -37,7 +36,7 @@ enum class MachineLimitsUsage { }; enum PrintHostType { - htOctoPrint, htDuet, htFlashAir, htAstroBox + htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier }; enum AuthorizationType { @@ -127,6 +126,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::g keys_map["duet"] = htDuet; keys_map["flashair"] = htFlashAir; keys_map["astrobox"] = htAstroBox; + keys_map["repetier"] = htRepetier; } return keys_map; } @@ -962,38 +962,14 @@ protected: } }; -class HostConfig : public StaticPrintConfig -{ - STATIC_PRINT_CONFIG_CACHE(HostConfig) -public: - ConfigOptionEnum<PrintHostType> host_type; - ConfigOptionString print_host; - ConfigOptionString printhost_apikey; - ConfigOptionString printhost_cafile; - ConfigOptionString serial_port; - ConfigOptionInt serial_speed; - -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(host_type); - OPT_PTR(print_host); - OPT_PTR(printhost_apikey); - OPT_PTR(printhost_cafile); - OPT_PTR(serial_port); - OPT_PTR(serial_speed); - } -}; - // This object is mapped to Perl as Slic3r::Config::Full. class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, - public PrintConfig, - public HostConfig + public PrintConfig { STATIC_PRINT_CONFIG_CACHE_DERIVED(FullPrintConfig) - FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); } + FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); } public: // Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. @@ -1001,13 +977,12 @@ public: protected: // Protected constructor to be called to initialize ConfigCache::m_default. - FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) {} + FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0) {} void initialize(StaticCacheBase &cache, const char *base_ptr) { this->PrintObjectConfig::initialize(cache, base_ptr); this->PrintRegionConfig::initialize(cache, base_ptr); this->PrintConfig ::initialize(cache, base_ptr); - this->HostConfig ::initialize(cache, base_ptr); } }; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 3cb8dbfd9..105d02613 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -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 diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 95fd13577..0bb3f0068 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -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); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index d919304a9..aa047d030 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -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; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 2d1f87a2f..27d288a8e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -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 diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 7456ee800..5a440b4f2 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -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(); } diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index 3d0cf2d9f..6d089415f 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -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; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 24f8e5ed6..6ed3e1c1e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -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)); } } diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 96299c381..dcf7115ca 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -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; } } diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index e5f96f2b6..71973b64e 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -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; }; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 54c137b2c..33c5adca4 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -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; diff --git a/src/slic3r/Utils/AstroBox.hpp b/src/slic3r/Utils/AstroBox.hpp index 38542275c..f24018b1b 100644 --- a/src/slic3r/Utils/AstroBox.hpp +++ b/src/slic3r/Utils/AstroBox.hpp @@ -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; diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index 7fdd8ea10..e5aec548b 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -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; diff --git a/src/slic3r/Utils/FlashAir.hpp b/src/slic3r/Utils/FlashAir.hpp index 40af48da1..181405d46 100644 --- a/src/slic3r/Utils/FlashAir.hpp +++ b/src/slic3r/Utils/FlashAir.hpp @@ -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; diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 0a49b7815..ace7e2fe4 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -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 { diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index e0aeb463c..35a870b29 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -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: diff --git a/src/slic3r/Utils/Repetier.cpp b/src/slic3r/Utils/Repetier.cpp new file mode 100644 index 000000000..ddf0a06e0 --- /dev/null +++ b/src/slic3r/Utils/Repetier.cpp @@ -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; +} + +} diff --git a/src/slic3r/Utils/Repetier.hpp b/src/slic3r/Utils/Repetier.hpp new file mode 100644 index 000000000..d94d7ec56 --- /dev/null +++ b/src/slic3r/Utils/Repetier.hpp @@ -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