From 7c571c1d9d33150cdc60780876b275421dbdd9b8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik <bubnikv@gmail.com> Date: Wed, 28 Oct 2020 09:51:05 +0100 Subject: [PATCH] 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. --- src/libslic3r/Exception.hpp | 1 + src/libslic3r/Format/SL1.cpp | 1 + src/libslic3r/GCode.cpp | 3 +- src/libslic3r/Preset.cpp | 102 +++------ src/libslic3r/PresetBundle.cpp | 18 +- src/libslic3r/PrintConfig.cpp | 35 +-- src/libslic3r/PrintConfig.hpp | 35 +-- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Field.cpp | 22 +- src/slic3r/GUI/Field.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 13 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 68 +++++- src/slic3r/GUI/PhysicalPrinterDialog.hpp | 2 + src/slic3r/GUI/Plater.cpp | 31 +-- src/slic3r/GUI/PrintHostDialogs.cpp | 74 +++++-- src/slic3r/GUI/PrintHostDialogs.hpp | 5 +- src/slic3r/GUI/Tab.hpp | 3 - src/slic3r/Utils/AstroBox.hpp | 2 +- src/slic3r/Utils/Duet.hpp | 2 +- src/slic3r/Utils/FlashAir.hpp | 2 +- src/slic3r/Utils/PrintHost.cpp | 3 + src/slic3r/Utils/PrintHost.hpp | 11 + src/slic3r/Utils/Repetier.cpp | 268 +++++++++++++++++++++++ src/slic3r/Utils/Repetier.hpp | 52 +++++ 24 files changed, 556 insertions(+), 200 deletions(-) create mode 100644 src/slic3r/Utils/Repetier.cpp create mode 100644 src/slic3r/Utils/Repetier.hpp 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