mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Revert "Support upload g-code to third-party printers (#2)"
This reverts commit dc5e0eed34.
			
			
This commit is contained in:
		
							parent
							
								
									dc5e0eed34
								
							
						
					
					
						commit
						82127a92c9
					
				
					 44 changed files with 19 additions and 5543 deletions
				
			
		|  | @ -1,22 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> | ||||
| <g id="ADD"> | ||||
| 	<path fill="#FFFFFF" d="M72.3,117.5H10.5v-75h75v23.27c1.61-0.56,3.28-0.99,5-1.29V41.04l27-27V72.3c1.89,1.71,3.57,3.65,5,5.76V8 | ||||
| 		c0-0.05-0.01-0.1-0.02-0.15c0-0.06-0.01-0.11-0.02-0.17c-0.03-0.22-0.08-0.43-0.15-0.62c0,0,0-0.01,0-0.01c0,0,0,0,0,0 | ||||
| 		c-0.01-0.03-0.03-0.05-0.04-0.08c-0.05-0.11-0.11-0.21-0.17-0.31c-0.03-0.04-0.05-0.08-0.08-0.11c-0.06-0.08-0.13-0.16-0.2-0.24 | ||||
| 		c-0.03-0.03-0.06-0.07-0.09-0.1c-0.09-0.09-0.19-0.17-0.3-0.25c-0.01-0.01-0.02-0.02-0.04-0.03c-0.12-0.08-0.24-0.15-0.38-0.2 | ||||
| 		c-0.04-0.02-0.09-0.03-0.13-0.05c-0.1-0.04-0.2-0.07-0.3-0.09c-0.05-0.01-0.09-0.02-0.14-0.03c-0.15-0.03-0.3-0.05-0.45-0.05H48 | ||||
| 		c-0.57,0-1.12,0.19-1.56,0.55l-40,32c-0.03,0.03-0.06,0.06-0.09,0.09c-0.07,0.06-0.13,0.12-0.19,0.19 | ||||
| 		c-0.05,0.06-0.1,0.12-0.15,0.18c-0.05,0.07-0.09,0.13-0.14,0.2c-0.04,0.07-0.08,0.14-0.12,0.21c-0.03,0.07-0.07,0.15-0.09,0.22 | ||||
| 		c-0.03,0.08-0.05,0.16-0.07,0.24c-0.02,0.08-0.04,0.15-0.05,0.23c-0.01,0.09-0.02,0.18-0.03,0.27c0,0.04-0.01,0.08-0.01,0.13v80 | ||||
| 		c0,1.38,1.12,2.5,2.5,2.5h70.06C75.95,121.07,74.01,119.39,72.3,117.5z M48.88,10.5h65.09l-27,27H15.13L48.88,10.5z"/> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M96,69.5c-14.61,0-26.5,11.89-26.5,26.5s11.89,26.5,26.5,26.5s26.5-11.89,26.5-26.5S110.61,69.5,96,69.5z | ||||
| 			 M96,117.5c-11.86,0-21.5-9.64-21.5-21.5S84.14,74.5,96,74.5s21.5,9.64,21.5,21.5S107.86,117.5,96,117.5z"/> | ||||
| 		<path fill="#ED6B21" d="M112,93.5H98.5V80c0-1.38-1.12-2.5-2.5-2.5s-2.5,1.12-2.5,2.5v13.5H80c-1.38,0-2.5,1.12-2.5,2.5 | ||||
| 			s1.12,2.5,2.5,2.5h13.5V112c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V98.5H112c1.38,0,2.5-1.12,2.5-2.5S113.38,93.5,112,93.5z"/> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 1.9 KiB | 
|  | @ -1,19 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> | ||||
| <g id="add_x5F_copies"> | ||||
| 	<g> | ||||
| 		<path fill="#808080" d="M8,2c3.31,0,6,2.69,6,6s-2.69,6-6,6s-6-2.69-6-6S4.69,2,8,2 M8,1C4.13,1,1,4.13,1,8s3.13,7,7,7s7-3.13,7-7 | ||||
| 			S11.87,1,8,1L8,1z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M12,8.75H4C3.59,8.75,3.25,8.41,3.25,8S3.59,7.25,4,7.25h8c0.41,0,0.75,0.34,0.75,0.75S12.41,8.75,12,8.75 | ||||
| 			z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M8,12.75c-0.41,0-0.75-0.34-0.75-0.75V4c0-0.41,0.34-0.75,0.75-0.75S8.75,3.59,8.75,4v8 | ||||
| 			C8.75,12.41,8.41,12.75,8,12.75z"/> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 846 B | 
|  | @ -1,11 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> | ||||
| <g id="browse"> | ||||
| 	<path fill="#ED6B21" d="M8.49,2.43c-1.71-1.71-4.49-1.71-6.2,0s-1.71,4.49,0,6.2c1.59,1.59,4.1,1.7,5.82,0.34l1.48,1.48 | ||||
| 		c0,0-0.36,0.36,0,0.73s3.65,3.65,3.65,3.65s0.36,0.36,0.73,0c0.36-0.36,0.73-0.73,0.73-0.73s0.36-0.36,0-0.73s-3.65-3.65-3.65-3.65 | ||||
| 		c-0.36-0.36-0.73,0-0.73,0L8.83,8.25C10.19,6.52,10.08,4.02,8.49,2.43z M8.1,8.25c-1.5,1.5-3.93,1.5-5.43,0s-1.5-3.93,0-5.43 | ||||
| 		s3.93-1.5,5.43,0S9.6,6.75,8.1,8.25z"/> | ||||
| </g> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 777 B | 
|  | @ -724,13 +724,7 @@ static std::vector<std::string> s_Preset_printer_options { | |||
|     "silent_mode", | ||||
|     // BBS
 | ||||
|     "scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", | ||||
|     "nozzle_type", "auxiliary_fan", "nozzle_volume", | ||||
|     //SoftFever
 | ||||
|     "connection_moonraker_url","connection_port", "host_type", "print_host", "printhost_apikey",  | ||||
|     "printhost_cafile","printhost_port","printhost_authorization_type", | ||||
|         "printhost_user", | ||||
|     "printhost_password", | ||||
|     "printhost_ssl_ignore_revoke" | ||||
|     "nozzle_type", "auxiliary_fan", "nozzle_volume" | ||||
| }; | ||||
| 
 | ||||
| static std::vector<std::string> s_Preset_sla_print_options { | ||||
|  | @ -2525,16 +2519,6 @@ static std::vector<std::string> s_PhysicalPrinter_opts { | |||
|     "preset_name", // temporary option to compatibility with older Slicer
 | ||||
|     "preset_names", | ||||
|     "printer_technology", | ||||
|     "host_type", | ||||
|     "print_host", | ||||
|     "printhost_apikey", | ||||
|     "printhost_cafile", | ||||
|     "printhost_port", | ||||
|     "printhost_authorization_type", | ||||
|     // HTTP digest authentization (RFC 2617)
 | ||||
|     "printhost_user", | ||||
|     "printhost_password", | ||||
|     "printhost_ssl_ignore_revoke" | ||||
| }; | ||||
| 
 | ||||
| const std::vector<std::string>& PhysicalPrinter::printer_options() | ||||
|  | @ -2698,8 +2682,6 @@ void PhysicalPrinterCollection::load_printers( | |||
|     // see https://github.com/prusa3d/PrusaSlicer/issues/732
 | ||||
|     boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred(); | ||||
|     m_dir_path = dir.string(); | ||||
|     if(!boost::filesystem::exists(dir)) | ||||
|         return; | ||||
|     std::string errors_cummulative; | ||||
|     // Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken.
 | ||||
|     std::deque<PhysicalPrinter> printers_loaded; | ||||
|  |  | |||
|  | @ -22,7 +22,6 @@ | |||
| #define PRESET_SLA_MATERIALS_NAME "sla_materials" | ||||
| 
 | ||||
| //BBS: iot preset type strings
 | ||||
| #define PRESET_IOT_PHYSICAL_PRINTER_TYPE     "physical_printer" | ||||
| #define PRESET_IOT_PRINTER_TYPE     "printer" | ||||
| #define PRESET_IOT_FILAMENT_TYPE    "filament" | ||||
| #define PRESET_IOT_PRINT_TYPE       "print" | ||||
|  |  | |||
|  | @ -90,7 +90,7 @@ PresetBundle::PresetBundle() | |||
|     for (size_t i = 0; i < 1; ++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); | ||||
|         for (const char *key : {"printer_settings_id", "printer_model", "printer_variant","physical_printer_settings_id"}) preset.config.optptr(key, true); | ||||
|         for (const char *key : {"printer_settings_id", "printer_model", "printer_variant"}) preset.config.optptr(key, true); | ||||
|         //if (i == 0) {
 | ||||
|             preset.config.optptr("default_print_profile", true); | ||||
|             preset.config.option<ConfigOptionStrings>("default_filament_profile", true); | ||||
|  | @ -268,11 +268,6 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward | |||
|     } catch (const std::runtime_error &err) { | ||||
|         errors_cummulative += err.what(); | ||||
|     } | ||||
|     try { | ||||
|         this->physical_printers.load_printers(dir_user_presets, "physical_printer", substitutions, substitution_rule); | ||||
|     } catch (const std::runtime_error &err) { | ||||
|         errors_cummulative += err.what(); | ||||
|     } | ||||
|     this->update_multi_material_filament_presets(); | ||||
|     this->update_compatible(PresetSelectCompatibleType::Never); | ||||
|     if (! errors_cummulative.empty()) | ||||
|  | @ -537,7 +532,7 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &config, st | |||
|     // First load the vendor specific system presets.
 | ||||
|     PresetsConfigSubstitutions substitutions; | ||||
|     std::string errors_cummulative; | ||||
|     bool process_added = false, filament_added = false, machine_added = false, physical_printer_added = false; | ||||
|     bool process_added = false, filament_added = false, machine_added = false; | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, substitution_rule %1%, preset toltal count %2%")%substitution_rule%my_presets.size(); | ||||
|     BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" print's selected_idx %1%, selected_name %2%") %prints.get_selected_idx() %prints.get_selected_preset_name(); | ||||
|  | @ -567,11 +562,6 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &config, st | |||
|                 preset_collection = &(this->printers); | ||||
|                 machine_added |= preset_collection->load_user_preset(name, value_map, substitutions, substitution_rule); | ||||
|             } | ||||
|             else if(type_iter->second == PRESET_IOT_PHYSICAL_PRINTER_TYPE){ | ||||
|                 preset_collection = &(this->printers); | ||||
|                 physical_printer_added |= preset_collection->load_user_preset(name, value_map, substitutions, substitution_rule); | ||||
| 
 | ||||
|             } | ||||
|             else { | ||||
|                 BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid type %1% for setting %2%") %type_iter->second %name; | ||||
|                 continue; | ||||
|  | @ -655,6 +645,9 @@ void PresetBundle::update_system_preset_setting_ids(std::map<std::string, std::m | |||
|         else if (type_iter->second == PRESET_IOT_PRINTER_TYPE) { | ||||
|             preset_collection = &(this->printers); | ||||
|         } | ||||
|         else if (type_iter->second == PRESET_IOT_PRINTER_TYPE) { | ||||
|             preset_collection = &(this->printers); | ||||
|         } | ||||
|         else { | ||||
|             BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid type %1% for setting %2%") %type_iter->second %name; | ||||
|             continue; | ||||
|  | @ -687,7 +680,7 @@ void PresetBundle::update_system_preset_setting_ids(std::map<std::string, std::m | |||
| bool PresetBundle::validate_printers(const std::string &name, DynamicPrintConfig& config) | ||||
| { | ||||
|     // BBS TODO:
 | ||||
| #if 1 | ||||
| #if 0 | ||||
|     std::vector<std::string> inherits_values; | ||||
|     PrinterTechnology printer_technology = Preset::printer_technology(config); | ||||
|     size_t num_extruders = (printer_technology == ptFFF) ? | ||||
|  | @ -1184,7 +1177,7 @@ void PresetBundle::export_selections(AppConfig &config) | |||
|     // BBS
 | ||||
|     //config.set("presets", "sla_print",    sla_prints.get_selected_preset_name());
 | ||||
|     //config.set("presets", "sla_material", sla_materials.get_selected_preset_name());
 | ||||
|     config.set("presets", "physical_printer", physical_printers.get_selected_full_printer_name()); | ||||
|     //config.set("presets", "physical_printer", physical_printers.get_selected_full_printer_name());
 | ||||
|     //BBS: add config related log
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": printer %1%, print %2%, filaments[0] %3% ")%printers.get_selected_preset_name() % prints.get_selected_preset_name() %filament_presets[0]; | ||||
| } | ||||
|  | @ -1252,15 +1245,13 @@ 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");    return config; | ||||
|     //BBS example: config.erase("print_host");
 | ||||
|     return config; | ||||
| } | ||||
| 
 | ||||
| const std::set<std::string> ignore_settings_list ={ | ||||
|     "inherits", | ||||
|     "print_settings_id", "filament_settings_id", "printer_settings_id","physical_printer_settings_id" | ||||
|     "print_settings_id", "filament_settings_id", "printer_settings_id" | ||||
| }; | ||||
| 
 | ||||
| DynamicPrintConfig PresetBundle::full_fff_config() const | ||||
|  | @ -1441,7 +1432,6 @@ DynamicPrintConfig PresetBundle::full_fff_config() const | |||
|     out.option<ConfigOptionStrings>("filament_settings_id", true)->values = this->filament_presets; | ||||
|     out.option<ConfigOptionString >("printer_settings_id",  true)->value  = this->printers.get_selected_preset_name(); | ||||
|     out.option<ConfigOptionStrings>("filament_ids", true)->values = filament_ids; | ||||
|     // out.option<ConfigOptionString >("physical_printer_settings_id", true)->value = this->physical_printers.get_selected_printer_preset_name();
 | ||||
|     // Serialize the collected "compatible_printers_condition" and "inherits" fields.
 | ||||
|     // There will be 1 + num_exturders fields for "inherits" and 2 + num_extruders for "compatible_printers_condition" stored.
 | ||||
|     // The vector will not be stored if all fields are empty strings.
 | ||||
|  | @ -1820,7 +1810,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool | |||
| 	this->update_compatible(PresetSelectCompatibleType::Never); | ||||
| 
 | ||||
|     //BBS
 | ||||
|     const std::string &physical_printer = config.option<ConfigOptionString>("physical_printer_settings_id", true)->value; | ||||
|     //const std::string &physical_printer = config.option<ConfigOptionString>("physical_printer_settings_id", true)->value;
 | ||||
|     const std::string physical_printer; | ||||
|     if (this->printers.get_edited_preset().is_external || physical_printer.empty()) { | ||||
|         this->physical_printers.unselect_printer(); | ||||
|     } else { | ||||
|  |  | |||
|  | @ -43,23 +43,6 @@ static t_config_enum_values s_keys_map_PrinterTechnology { | |||
| }; | ||||
| CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology) | ||||
| 
 | ||||
| static t_config_enum_values s_keys_map_PrintHostType { | ||||
|     { "prusalink",      htPrusaLink }, | ||||
|     { "octoprint",      htOctoPrint }, | ||||
|     { "duet",           htDuet }, | ||||
|     { "flashair",       htFlashAir }, | ||||
|     { "astrobox",       htAstroBox }, | ||||
|     { "repetier",       htRepetier }, | ||||
|     { "mks",            htMKS } | ||||
| }; | ||||
| CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) | ||||
| 
 | ||||
| static t_config_enum_values s_keys_map_AuthorizationType { | ||||
|     { "key",            atKeyPassword }, | ||||
|     { "user",           atUserPassword } | ||||
| }; | ||||
| CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType) | ||||
| 
 | ||||
| static t_config_enum_values s_keys_map_GCodeFlavor { | ||||
|     { "marlin",         gcfMarlinLegacy }, | ||||
|     { "reprap",         gcfRepRapSprinter }, | ||||
|  | @ -274,7 +257,6 @@ void PrintConfigDef::init_common_params() | |||
| 
 | ||||
|     def = this->add("printable_area", coPoints); | ||||
|     def->label = L("Printable area"); | ||||
| 
 | ||||
|     //BBS
 | ||||
|     def->mode = comDevelop; | ||||
|     def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); | ||||
|  | @ -322,98 +304,6 @@ void PrintConfigDef::init_common_params() | |||
|     def->mode = comDevelop; | ||||
|     def->set_default_value(new ConfigOptionStrings()); | ||||
| 
 | ||||
|     //SoftFever
 | ||||
|     def = this->add("connection_moonraker_url", coString); | ||||
|     def->label = L("Moonraker URL"); | ||||
|     //def->tooltip = L("Names of presets related to the physical printer");
 | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionString("http://")); | ||||
| 
 | ||||
|     def = this->add("connection_port", coString); | ||||
|     def->label = L("Connection port"); | ||||
|     //def->tooltip = L("Names of presets related to the physical printer");
 | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionString("7125")); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     def = this->add("print_host", coString); | ||||
|     def->label = L("Hostname, IP or URL"); | ||||
|     def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " | ||||
|                    "the hostname, IP address or URL of the printer host instance. " | ||||
|                    "Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL " | ||||
|                    "in the following format: https://username:password@your-octopi-address/"); | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     def->set_default_value(new ConfigOptionString("")); | ||||
| 
 | ||||
|     def = this->add("printhost_apikey", coString); | ||||
|     def->label = L("API Key / Password"); | ||||
|     def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " | ||||
|                    "the API Key or the password required for authentication."); | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     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 = ConfigOptionDef::GUIType::select_open; | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     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. " | ||||
|                    "If left blank, the default OS CA certificate repository is used."); | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     def->set_default_value(new ConfigOptionString("")); | ||||
|      | ||||
|     // Options used by physical printers
 | ||||
|      | ||||
|     def = this->add("printhost_user", coString); | ||||
|     def->label = L("User"); | ||||
| //    def->tooltip = L("");
 | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     def->set_default_value(new ConfigOptionString("")); | ||||
|      | ||||
|     def = this->add("printhost_password", coString); | ||||
|     def->label = L("Password"); | ||||
| //    def->tooltip = L("");
 | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     def->set_default_value(new ConfigOptionString("")); | ||||
| 
 | ||||
|     // Only available on Windows.
 | ||||
|     def = this->add("printhost_ssl_ignore_revoke", coBool); | ||||
|     def->label = L("Ignore HTTPS certificate revocation checks"); | ||||
|     def->tooltip = L("Ignore HTTPS certificate revocation checks in case of missing or offline distribution points. " | ||||
|                      "One may want to enable this option for self signed certificates if connection fails."); | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     def->set_default_value(new ConfigOptionBool(false)); | ||||
|      | ||||
|     def = this->add("preset_names", coStrings); | ||||
|     def->label = L("Printer preset names"); | ||||
|     def->tooltip = L("Names of presets related to the physical printer"); | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionStrings()); | ||||
| 
 | ||||
|     def = this->add("printhost_authorization_type", coEnum); | ||||
|     def->label = L("Authorization Type"); | ||||
| //    def->tooltip = L("");
 | ||||
|     def->enum_keys_map = &ConfigOptionEnum<AuthorizationType>::get_enum_values(); | ||||
|     def->enum_values.push_back("key"); | ||||
|     def->enum_values.push_back("user"); | ||||
|     def->enum_labels.push_back(L("API key")); | ||||
|     def->enum_labels.push_back(L("HTTP digest")); | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     def->set_default_value(new ConfigOptionEnum<AuthorizationType>(atKeyPassword)); | ||||
| 
 | ||||
|     // temporary workaround for compatibility with older Slicer
 | ||||
|     { | ||||
|         def = this->add("preset_name", coString); | ||||
|  | @ -1734,30 +1624,6 @@ void PrintConfigDef::init_fff_params() | |||
|     def->mode = comDevelop; | ||||
|     def->set_default_value(new ConfigOptionFloats { 0.4 }); | ||||
| 
 | ||||
|     def = this->add("host_type", coEnum); | ||||
|     def->label = L("Host Type"); | ||||
|     def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain " | ||||
|                    "the kind of the host."); | ||||
|     def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values(); | ||||
|     def->enum_values.push_back("prusalink"); | ||||
|     def->enum_values.push_back("octoprint"); | ||||
|     def->enum_values.push_back("duet"); | ||||
|     def->enum_values.push_back("flashair"); | ||||
|     def->enum_values.push_back("astrobox"); | ||||
|     def->enum_values.push_back("repetier"); | ||||
|     def->enum_values.push_back("mks"); | ||||
|     def->enum_labels.push_back("PrusaLink"); | ||||
|     def->enum_labels.push_back("OctoPrint"); | ||||
|     def->enum_labels.push_back("Duet"); | ||||
|     def->enum_labels.push_back("FlashAir"); | ||||
|     def->enum_labels.push_back("AstroBox"); | ||||
|     def->enum_labels.push_back("Repetier"); | ||||
|     def->enum_labels.push_back("MKS"); | ||||
|     def->mode = comAdvanced; | ||||
|     def->cli = ConfigOptionDef::nocli; | ||||
|     def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint)); | ||||
|      | ||||
| 
 | ||||
|     def = this->add("nozzle_volume", coFloat); | ||||
|     def->label = L("Nozzle volume"); | ||||
|     def->tooltip = L("Volume of nozzle between the cutter and the end of nozzle"); | ||||
|  |  | |||
|  | @ -42,14 +42,6 @@ enum class FuzzySkinType { | |||
|     All, | ||||
| }; | ||||
| 
 | ||||
| enum PrintHostType { | ||||
|     htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS | ||||
| }; | ||||
| 
 | ||||
| enum AuthorizationType { | ||||
|     atKeyPassword, atUserPassword | ||||
| }; | ||||
| 
 | ||||
| #define HAS_LIGHTNING_INFILL 0 | ||||
| 
 | ||||
| enum InfillPattern : int { | ||||
|  | @ -244,9 +236,6 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BedType) | |||
| CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) | ||||
| CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) | ||||
| 
 | ||||
| CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType) | ||||
| CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType) | ||||
| 
 | ||||
| #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS | ||||
| 
 | ||||
| // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
 | ||||
|  | @ -766,10 +755,6 @@ PRINT_CONFIG_CLASS_DEFINE( | |||
|     //BBS
 | ||||
|     ((ConfigOptionEnum<NozzleType>,    nozzle_type)) | ||||
|     ((ConfigOptionBool,                auxiliary_fan)) | ||||
|     //SoftFever
 | ||||
|     ((ConfigOptionString,             connection_moonraker_url)) | ||||
|     ((ConfigOptionString,             connection_port)) | ||||
| 
 | ||||
| ) | ||||
| 
 | ||||
| // This object is mapped to Perl as Slic3r::Config::Print.
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| project(minilzo) | ||||
| cmake_minimum_required(VERSION 3.0) | ||||
| cmake_minimum_required(VERSION 2.6) | ||||
| 
 | ||||
| add_library(minilzo INTERFACE) | ||||
| 
 | ||||
|  |  | |||
|  | @ -177,10 +177,6 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/SavePresetDialog.cpp | ||||
|     GUI/GUI_Colors.hpp | ||||
|     GUI/GUI_Colors.cpp | ||||
|     GUI/PhysicalPrinterDialog.hpp | ||||
|     GUI/PhysicalPrinterDialog.cpp | ||||
|     GUI/PrintHostDialogs.cpp | ||||
|     GUI/PrintHostDialogs.hpp | ||||
|     GUI/GUI_Factories.cpp | ||||
|     GUI/GUI_Factories.hpp | ||||
|     GUI/GUI_ObjectList.cpp | ||||
|  | @ -352,8 +348,6 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/Calibration.cpp | ||||
|     GUI/PrintOptionsDialog.hpp | ||||
|     GUI/PrintOptionsDialog.cpp | ||||
|     GUI/BonjourDialog.hpp | ||||
|     GUI/BonjourDialog.cpp | ||||
|     Utils/json_diff.hpp | ||||
|     Utils/json_diff.cpp | ||||
|     GUI/KBShortcutsDialog.hpp | ||||
|  | @ -381,22 +375,6 @@ set(SLIC3R_GUI_SOURCES | |||
|     Utils/ColorSpaceConvert.cpp | ||||
|     Utils/NetworkAgent.cpp | ||||
|     Utils/NetworkAgent.hpp | ||||
|     Utils/OctoPrint.cpp | ||||
|     Utils/OctoPrint.hpp | ||||
|     Utils/PrintHost.cpp | ||||
|     Utils/PrintHost.hpp | ||||
|     Utils/Serial.cpp | ||||
|     Utils/Serial.hpp | ||||
|     Utils/MKS.hpp | ||||
|     Utils/MKS.cpp | ||||
|     Utils/Duet.cpp | ||||
|     Utils/Duet.hpp | ||||
|     Utils/FlashAir.cpp | ||||
|     Utils/FlashAir.hpp | ||||
|     Utils/AstroBox.cpp | ||||
|     Utils/AstroBox.hpp | ||||
|     Utils/Repetier.cpp | ||||
|     Utils/Repetier.hpp | ||||
| ) | ||||
| 
 | ||||
| if (APPLE) | ||||
|  |  | |||
|  | @ -227,9 +227,6 @@ void BackgroundSlicingProcess::process_fff() | |||
| 	    if (! m_export_path.empty()) { | ||||
| 			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); | ||||
| 			finalize_gcode(); | ||||
| 	    } else if (! m_upload_job.empty()) { | ||||
| 			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); | ||||
| 			prepare_upload(); | ||||
| 	    } else { | ||||
| 			m_print->set_status(100, _utf8(L("Slicing complete"))); | ||||
| 	    } | ||||
|  | @ -687,19 +684,6 @@ void BackgroundSlicingProcess::schedule_export(const std::string &path, bool exp | |||
| 	m_export_path_on_removable_media = export_path_on_removable_media; | ||||
| } | ||||
| 
 | ||||
| void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job) | ||||
| { | ||||
| 	assert(m_export_path.empty()); | ||||
| 	if (! m_export_path.empty()) | ||||
| 		return; | ||||
| 
 | ||||
| 	// Guard against entering the export step before changing the export path.
 | ||||
| 	std::scoped_lock<std::mutex> lock(m_print->state_mutex()); | ||||
| 	this->invalidate_step(bspsGCodeFinalize); | ||||
| 	m_export_path.clear(); | ||||
| 	m_upload_job = std::move(upload_job); | ||||
| } | ||||
| 
 | ||||
| void BackgroundSlicingProcess::reset_export() | ||||
| { | ||||
| 	assert(! this->running()); | ||||
|  | @ -816,45 +800,6 @@ void BackgroundSlicingProcess::finalize_gcode() | |||
| 	m_print->set_status(100, (boost::format(_utf8(L("Succeed to export G-code to %1%"))) % export_path).str()); | ||||
| } | ||||
| 
 | ||||
| // A print host upload job has been scheduled, enqueue it to the printhost job queue
 | ||||
| void BackgroundSlicingProcess::prepare_upload() | ||||
| { | ||||
| 	// Generate a unique temp path to which the gcode/zip file is copied/exported
 | ||||
| 	boost::filesystem::path source_path = boost::filesystem::temp_directory_path() | ||||
| 		/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%"); | ||||
| 
 | ||||
| 	if (m_print == m_fff_print) { | ||||
| 		m_print->set_status(95, _utf8(L("Running post-processing scripts"))); | ||||
| 		std::string error_message; | ||||
| 		if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) | ||||
| 			throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); | ||||
|         m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); | ||||
|         // Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file
 | ||||
|         // (not here, but when the final target is a file). 
 | ||||
|         std::string source_path_str = source_path.string(); | ||||
|         std::string output_name_str = m_upload_job.upload_data.upload_path.string(); | ||||
| 		if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config())) | ||||
| 			m_upload_job.upload_data.upload_path = output_name_str; | ||||
|     } else { | ||||
|         m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); | ||||
|          | ||||
|         ThumbnailsList thumbnails = this->render_thumbnails( | ||||
|         	ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true}); | ||||
| 																												 // true, false, true, true); // renders also supports and pad
 | ||||
|         Zipper zipper{source_path.string()}; | ||||
|         m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string()); | ||||
|         for (const ThumbnailData& data : thumbnails) | ||||
| 	        if (data.is_valid()) | ||||
| 	            write_thumbnail(zipper, data); | ||||
|         zipper.finalize(); | ||||
|     } | ||||
| 
 | ||||
|     m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str()); | ||||
| 
 | ||||
| 	m_upload_job.upload_data.source_path = std::move(source_path); | ||||
| 
 | ||||
| 	GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job)); | ||||
| } | ||||
| // Executed by the background thread, to start a task on the UI thread.
 | ||||
| ThumbnailsList BackgroundSlicingProcess::render_thumbnails(const ThumbnailsParams ¶ms) | ||||
| { | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ | |||
| #include "libslic3r/PrintBase.hpp" | ||||
| #include "libslic3r/GCode/ThumbnailData.hpp" | ||||
| #include "libslic3r/Format/SL1.hpp" | ||||
| #include "slic3r/Utils/PrintHost.hpp" | ||||
| #include "libslic3r/GCode/GCodeProcessor.hpp" | ||||
| #include "PartPlate.hpp" | ||||
| 
 | ||||
|  | @ -149,14 +148,10 @@ public: | |||
| 	// Set the export path of the G-code.
 | ||||
| 	// Once the path is set, the G-code
 | ||||
| 	void schedule_export(const std::string &path, bool export_path_on_removable_media); | ||||
| 	// Set print host upload job data to be enqueued to the PrintHostJobQueue
 | ||||
| 	// after current print slicing is complete
 | ||||
| 	void schedule_upload(Slic3r::PrintHostJob upload_job); | ||||
| 	// Clear m_export_path.
 | ||||
| 	void reset_export(); | ||||
| 	// Once the G-code export is scheduled, the apply() methods will do nothing.
 | ||||
| 	bool is_export_scheduled() const { return ! m_export_path.empty(); } | ||||
| 	bool is_upload_scheduled() const { return ! m_upload_job.empty(); } | ||||
| 
 | ||||
| 	enum State { | ||||
| 		// m_thread  is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
 | ||||
|  | @ -243,9 +238,6 @@ private: | |||
| 	// but once set, it cannot be re-set.
 | ||||
| 	std::string 				m_export_path; | ||||
| 	bool 						m_export_path_on_removable_media = false; | ||||
| 	// Print host upload job to schedule after slicing is complete, used by schedule_upload(),
 | ||||
| 	// empty by default (ie. no upload to schedule)
 | ||||
| 	PrintHostJob                m_upload_job; | ||||
| 	// Thread, on which the background processing is executed. The thread will always be present
 | ||||
| 	// and ready to execute the slicing process.
 | ||||
| 	boost::thread		 		m_thread; | ||||
|  | @ -284,7 +276,6 @@ private: | |||
|     // If the background processing stop was requested, throw CanceledException.
 | ||||
|     void                throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); } | ||||
| 	void				finalize_gcode(); | ||||
|     void                prepare_upload(); | ||||
|     // To be executed at the background thread.
 | ||||
| 	ThumbnailsList		render_thumbnails(const ThumbnailsParams ¶ms); | ||||
| 	// Execute task from background thread on the UI thread synchronously. Returns true if processed, false if cancelled before executing the task.
 | ||||
|  |  | |||
|  | @ -1,239 +0,0 @@ | |||
| #include "slic3r/Utils/Bonjour.hpp"   // On Windows, boost needs to be included before wxWidgets headers
 | ||||
| 
 | ||||
| #include "BonjourDialog.hpp" | ||||
| 
 | ||||
| #include <set> | ||||
| #include <mutex> | ||||
| 
 | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/button.h> | ||||
| #include <wx/listctrl.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/timer.h> | ||||
| #include <wx/wupdlock.h> | ||||
| 
 | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| #include "slic3r/GUI/I18N.hpp" | ||||
| #include "slic3r/Utils/Bonjour.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| 
 | ||||
| class BonjourReplyEvent : public wxEvent | ||||
| { | ||||
| public: | ||||
| 	BonjourReply reply; | ||||
| 
 | ||||
| 	BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) : | ||||
| 		wxEvent(winid, eventType), | ||||
| 		reply(std::move(reply)) | ||||
| 	{} | ||||
| 
 | ||||
| 	virtual wxEvent *Clone() const | ||||
| 	{ | ||||
| 		return new BonjourReplyEvent(*this); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent); | ||||
| 
 | ||||
| wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent); | ||||
| wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent); | ||||
| 
 | ||||
| class ReplySet: public std::set<BonjourReply> {}; | ||||
| 
 | ||||
| struct LifetimeGuard | ||||
| { | ||||
| 	std::mutex mutex; | ||||
| 	BonjourDialog *dialog; | ||||
| 
 | ||||
| 	LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {} | ||||
| }; | ||||
| 
 | ||||
| BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) | ||||
| 	: wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) | ||||
| 	, list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSIMPLE_BORDER)) | ||||
| 	, replies(new ReplySet) | ||||
| 	, label(new wxStaticText(this, wxID_ANY, "")) | ||||
| 	, timer(new wxTimer()) | ||||
| 	, timer_state(0) | ||||
| 	, tech(tech) | ||||
| { | ||||
| 	const int em = GUI::wxGetApp().em_unit(); | ||||
| 	list->SetMinSize(wxSize(80 * em, 30 * em)); | ||||
| 
 | ||||
| 	wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
| 	vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em); | ||||
| 
 | ||||
| 	list->SetSingleStyle(wxLC_SINGLE_SEL); | ||||
| 	list->SetSingleStyle(wxLC_SORT_DESCENDING); | ||||
| 	list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 5 * em); | ||||
| 	list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 10 * em); | ||||
| 	list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 20 * em); | ||||
| 	if (tech == ptFFF) { | ||||
| 		list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 5 * em); | ||||
| 	} | ||||
| 
 | ||||
| 	vsizer->Add(list, 1, wxEXPAND | wxALL, em); | ||||
| 
 | ||||
| 	wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
| 	button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em); | ||||
| 	button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em); | ||||
| 	// ^ Note: The Ok/Cancel labels are translated by wxWidgets
 | ||||
| 
 | ||||
| 	vsizer->Add(button_sizer, 0, wxALIGN_CENTER); | ||||
| 	SetSizerAndFit(vsizer); | ||||
| 
 | ||||
| 	Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this); | ||||
| 
 | ||||
| 	Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) { | ||||
| 		this->timer_state = 0; | ||||
| 	}); | ||||
| 
 | ||||
| 	Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this); | ||||
| 	GUI::wxGetApp().UpdateDlgDarkUI(this); | ||||
| } | ||||
| 
 | ||||
| BonjourDialog::~BonjourDialog() | ||||
| { | ||||
| 	// Needed bacuse of forward defs
 | ||||
| } | ||||
| 
 | ||||
| bool BonjourDialog::show_and_lookup() | ||||
| { | ||||
| 	Show();   // Because we need GetId() to work before ShowModal()
 | ||||
| 
 | ||||
| 	timer->Stop(); | ||||
| 	timer->SetOwner(this); | ||||
| 	timer_state = 1; | ||||
| 	timer->Start(1000); | ||||
|     on_timer_process(); | ||||
| 
 | ||||
| 	// The background thread needs to queue messages for this dialog
 | ||||
| 	// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
 | ||||
| 	// Here we put the pointer under a shared_ptr and protect it by a mutex,
 | ||||
| 	// so that both threads can access it safely.
 | ||||
| 	auto dguard = std::make_shared<LifetimeGuard>(this); | ||||
| 
 | ||||
| 	// Note: More can be done here when we support discovery of hosts other than Octoprint and SL1
 | ||||
| 	Bonjour::TxtKeys txt_keys { "version", "model" }; | ||||
| 
 | ||||
|     bonjour = Bonjour("octoprint") | ||||
| 		.set_txt_keys(std::move(txt_keys)) | ||||
| 		.set_retries(3) | ||||
| 		.set_timeout(4) | ||||
| 		.on_reply([dguard](BonjourReply &&reply) { | ||||
| 			std::lock_guard<std::mutex> lock_guard(dguard->mutex); | ||||
| 			auto dialog = dguard->dialog; | ||||
| 			if (dialog != nullptr) { | ||||
| 				auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply)); | ||||
| 				wxQueueEvent(dialog, evt); | ||||
| 			} | ||||
| 		}) | ||||
| 		.on_complete([dguard]() { | ||||
| 			std::lock_guard<std::mutex> lock_guard(dguard->mutex); | ||||
| 			auto dialog = dguard->dialog; | ||||
| 			if (dialog != nullptr) { | ||||
| 				auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId()); | ||||
| 				wxQueueEvent(dialog, evt); | ||||
| 			} | ||||
| 		}) | ||||
| 		.lookup(); | ||||
| 
 | ||||
| 	bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0; | ||||
| 	{ | ||||
| 		// Tell the background thread the dialog is going away...
 | ||||
| 		std::lock_guard<std::mutex> lock_guard(dguard->mutex); | ||||
| 		dguard->dialog = nullptr; | ||||
| 	} | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| wxString BonjourDialog::get_selected() const | ||||
| { | ||||
| 	auto sel = list->GetFirstSelected(); | ||||
| 	return sel >= 0 ? list->GetItemText(sel) : wxString(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // Private
 | ||||
| 
 | ||||
| void BonjourDialog::on_reply(BonjourReplyEvent &e) | ||||
| { | ||||
| 	if (replies->find(e.reply) != replies->end()) { | ||||
| 		// We already have this reply
 | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	// Filter replies based on selected technology
 | ||||
| 	const auto model = e.reply.txt_data.find("model"); | ||||
| 	const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; | ||||
| 	if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	replies->insert(std::move(e.reply)); | ||||
| 
 | ||||
| 	auto selected = get_selected(); | ||||
| 
 | ||||
| 	wxWindowUpdateLocker freeze_guard(this); | ||||
| 	(void)freeze_guard; | ||||
| 
 | ||||
| 	list->DeleteAllItems(); | ||||
| 
 | ||||
| 	// The whole list is recreated so that we benefit from it already being sorted in the set.
 | ||||
| 	// (And also because wxListView's sorting API is bananas.)
 | ||||
| 	for (const auto &reply : *replies) { | ||||
| 		auto item = list->InsertItem(0, reply.full_address); | ||||
| 		list->SetItem(item, 1, reply.hostname); | ||||
| 		list->SetItem(item, 2, reply.service_name); | ||||
| 
 | ||||
| 		if (tech == ptFFF) { | ||||
| 			const auto it = reply.txt_data.find("version"); | ||||
| 			if (it != reply.txt_data.end()) { | ||||
| 				list->SetItem(item, 3, GUI::from_u8(it->second)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	const int em = GUI::wxGetApp().em_unit(); | ||||
| 
 | ||||
| 	for (int i = 0; i < list->GetColumnCount(); i++) { | ||||
| 		list->SetColumnWidth(i, wxLIST_AUTOSIZE); | ||||
| 		if (list->GetColumnWidth(i) < 10 * em) { list->SetColumnWidth(i, 10 * em); } | ||||
| 	} | ||||
| 
 | ||||
| 	if (!selected.IsEmpty()) { | ||||
| 		// Attempt to preserve selection
 | ||||
| 		auto hit = list->FindItem(-1, selected); | ||||
| 		if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void BonjourDialog::on_timer(wxTimerEvent &) | ||||
| { | ||||
|     on_timer_process(); | ||||
| } | ||||
| 
 | ||||
| // This is here so the function can be bound to wxEVT_TIMER and also called
 | ||||
| // explicitly (wxTimerEvent should not be created by user code).
 | ||||
| void BonjourDialog::on_timer_process() | ||||
| { | ||||
|     const auto search_str = _utf8(L("Searching for devices")); | ||||
| 
 | ||||
|     if (timer_state > 0) { | ||||
|         const std::string dots(timer_state, '.'); | ||||
|         label->SetLabel(GUI::from_u8((boost::format("%1% %2%") % search_str % dots).str())); | ||||
|         timer_state = (timer_state) % 3 + 1; | ||||
|     } else { | ||||
|         label->SetLabel(GUI::from_u8((boost::format("%1%: %2%") % search_str % (_utf8(L("Finished"))+".")).str())); | ||||
|         timer->Stop(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -1,53 +0,0 @@ | |||
| #ifndef slic3r_BonjourDialog_hpp_ | ||||
| #define slic3r_BonjourDialog_hpp_ | ||||
| 
 | ||||
| #include <memory> | ||||
| 
 | ||||
| #include <wx/dialog.h> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| 
 | ||||
| class wxListView; | ||||
| class wxStaticText; | ||||
| class wxTimer; | ||||
| class wxTimerEvent; | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class Bonjour; | ||||
| class BonjourReplyEvent; | ||||
| class ReplySet; | ||||
| 
 | ||||
| 
 | ||||
| class BonjourDialog: public wxDialog | ||||
| { | ||||
| public: | ||||
| 	BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology); | ||||
| 	BonjourDialog(BonjourDialog &&) = delete; | ||||
| 	BonjourDialog(const BonjourDialog &) = delete; | ||||
| 	BonjourDialog &operator=(BonjourDialog &&) = delete; | ||||
| 	BonjourDialog &operator=(const BonjourDialog &) = delete; | ||||
| 	~BonjourDialog(); | ||||
| 
 | ||||
| 	bool show_and_lookup(); | ||||
| 	wxString get_selected() const; | ||||
| private: | ||||
| 	wxListView *list; | ||||
| 	std::unique_ptr<ReplySet> replies; | ||||
| 	wxStaticText *label; | ||||
| 	std::shared_ptr<Bonjour> bonjour; | ||||
| 	std::unique_ptr<wxTimer> timer; | ||||
| 	unsigned timer_state; | ||||
| 	Slic3r::PrinterTechnology tech; | ||||
| 
 | ||||
| 	void on_reply(BonjourReplyEvent &); | ||||
| 	void on_timer(wxTimerEvent &); | ||||
|     void on_timer_process(); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1225,9 +1225,7 @@ void Choice::set_value(const boost::any& value, bool change_event) | |||
|     // BBS
 | ||||
|     case coEnums: { | ||||
| 		int val = boost::any_cast<int>(value); | ||||
|         if (m_opt_id.compare("host_type") == 0 && val != 0 &&  | ||||
| 			m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType
 | ||||
| 			val--; | ||||
| 
 | ||||
| 		if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern") | ||||
| 		{ | ||||
| 			std::string key; | ||||
|  | @ -1307,11 +1305,7 @@ boost::any& Choice::get_value() | |||
|     // BBS
 | ||||
| 	if (m_opt.type == coEnum || m_opt.type == coEnums) | ||||
| 	{ | ||||
|         if (m_opt_id.compare("host_type") == 0 && m_opt.enum_values.size() > field->GetCount()) { | ||||
| 			// for case, when PrusaLink isn't used as a HostType
 | ||||
| 			m_value = field->GetSelection()+1; | ||||
| 		}  | ||||
| 		else if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern") { | ||||
| 		if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern") { | ||||
| 			const std::string& key = m_opt.enum_values[field->GetSelection()]; | ||||
| 			m_value = int(ConfigOptionEnum<InfillPattern>::get_enum_values().at(key)); | ||||
| 		} | ||||
|  |  | |||
|  | @ -56,7 +56,6 @@ | |||
| #include "GLCanvas3D.hpp" | ||||
| 
 | ||||
| #include "../Utils/PresetUpdater.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| #include "../Utils/Process.hpp" | ||||
| #include "../Utils/MacDarkMode.hpp" | ||||
| #include "../Utils/Http.hpp" | ||||
|  | @ -71,7 +70,6 @@ | |||
| #include "NotificationManager.hpp" | ||||
| #include "UnsavedChangesDialog.hpp" | ||||
| #include "SavePresetDialog.hpp" | ||||
| #include "PrintHostDialogs.hpp" | ||||
| #include "DesktopIntegrationDialog.hpp" | ||||
| #include "SendSystemInfoDialog.hpp" | ||||
| #include "ParamsDialog.hpp" | ||||
|  | @ -2083,8 +2081,6 @@ bool GUI_App::on_init_inner() | |||
| 
 | ||||
|     plater_->init_notification_manager(); | ||||
| 
 | ||||
|     m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); | ||||
| 
 | ||||
|     if (is_gcode_viewer()) { | ||||
|         mainframe->update_layout(); | ||||
|         if (plater_ != nullptr) | ||||
|  | @ -2600,7 +2596,6 @@ void GUI_App::recreate_GUI(const wxString& msg_name) | |||
|     old_main_frame->Destroy(); | ||||
| 
 | ||||
|     dlg.Update(80, _L("Loading current presets") + dots); | ||||
|     m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); | ||||
|     load_current_presets(); | ||||
|     mainframe->Show(true); | ||||
|     //mainframe->refresh_plugin_tips();
 | ||||
|  | @ -4306,34 +4301,6 @@ bool GUI_App::can_load_project() | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool GUI_App::check_print_host_queue() | ||||
| { | ||||
|     wxString dirty; | ||||
|     std::vector<std::pair<std::string, std::string>> jobs; | ||||
|     // Get ongoing jobs from dialog
 | ||||
|     mainframe->m_printhost_queue_dlg->get_active_jobs(jobs); | ||||
|     if (jobs.empty()) | ||||
|         return true; | ||||
|     // Show dialog
 | ||||
|     wxString job_string = wxString(); | ||||
|     for (const auto& job : jobs) { | ||||
|         job_string += format_wxstr("   %1% : %2% \n", job.first, job.second); | ||||
|     } | ||||
|     wxString message; | ||||
|     message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?")); | ||||
|     //wxMessageDialog dialog(mainframe,
 | ||||
|     MessageDialog dialog(mainframe, | ||||
|         message, | ||||
|         wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")), | ||||
|         wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); | ||||
|     if (dialog.ShowModal() == wxID_YES) | ||||
|         return true; | ||||
| 
 | ||||
|     // TODO: If already shown, bring forward
 | ||||
|     mainframe->m_printhost_queue_dlg->Show(); | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool GUI_App::checked_tab(Tab* tab) | ||||
| { | ||||
|     bool ret = true; | ||||
|  |  | |||
|  | @ -7,13 +7,11 @@ | |||
| #include "ConfigWizard.hpp" | ||||
| #include "OpenGLManager.hpp" | ||||
| #include "libslic3r/Preset.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| #include "libslic3r/PresetBundle.hpp" | ||||
| #include "slic3r/GUI/DeviceManager.hpp" | ||||
| #include "slic3r/Utils/NetworkAgent.hpp" | ||||
| #include "slic3r/GUI/WebViewDialog.hpp" | ||||
| #include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| 
 | ||||
| #include <wx/app.h> | ||||
| #include <wx/colour.h> | ||||
|  | @ -44,7 +42,6 @@ class AppConfig; | |||
| class PresetBundle; | ||||
| class PresetUpdater; | ||||
| class ModelObject; | ||||
| // class PrintHostJobQueue;
 | ||||
| class Model; | ||||
| class DeviceManager; | ||||
| class NetworkAgent; | ||||
|  | @ -247,7 +244,6 @@ private: | |||
|     //std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
 | ||||
| 
 | ||||
|     std::unique_ptr<ImGuiWrapper> m_imgui; | ||||
|     std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue; | ||||
| 	//std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
 | ||||
|     //std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
 | ||||
|     //std::string m_instance_hash_string;
 | ||||
|  | @ -420,7 +416,6 @@ public: | |||
|     void            apply_keeped_preset_modifications(); | ||||
|     bool            check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes = nullptr); | ||||
|     bool            can_load_project(); | ||||
|     bool            check_print_host_queue(); | ||||
|     bool            checked_tab(Tab* tab); | ||||
|     //BBS: add preset combox re-active logic
 | ||||
|     void            load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true); | ||||
|  | @ -488,8 +483,6 @@ public: | |||
| 
 | ||||
|     ImGuiWrapper* imgui() { return m_imgui.get(); } | ||||
| 
 | ||||
|     PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } | ||||
| 
 | ||||
|     void            open_web_page_localized(const std::string &http_address); | ||||
|     bool            may_switch_to_SLA_preset(const wxString& caption); | ||||
|     bool            run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME); | ||||
|  |  | |||
|  | @ -25,7 +25,6 @@ | |||
| #include "ProgressStatusBar.hpp" | ||||
| #include "3DScene.hpp" | ||||
| #include "ParamsDialog.hpp" | ||||
| #include "PrintHostDialogs.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| #include "Mouse3DController.hpp" | ||||
|  | @ -150,7 +149,6 @@ wxDEFINE_EVENT(EVT_SYNC_CLOUD_PRESET,     SimpleEvent); | |||
| 
 | ||||
| MainFrame::MainFrame() : | ||||
| DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_STYLE, "mainframe") | ||||
|     , m_printhost_queue_dlg(new PrintHostQueueDialog(this)) | ||||
|     // BBS
 | ||||
|     , m_recent_projects(9) | ||||
|     , m_settings_dialog(this) | ||||
|  | @ -401,10 +399,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ | |||
|             BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< "cancelled by close_with_confirm selection"; | ||||
|             return; | ||||
|         } | ||||
|         if (event.CanVeto() && !wxGetApp().check_print_host_queue()) { | ||||
|             event.Veto(); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|     #if 0 // BBS
 | ||||
|         //if (m_plater != nullptr) {
 | ||||
|  | @ -1111,15 +1105,6 @@ bool MainFrame::can_export_gcode() const | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool MainFrame::can_send_gcode() const | ||||
| { | ||||
|     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
 | ||||
| { | ||||
|     if (m_plater == nullptr) | ||||
|  |  | |||
|  | @ -40,7 +40,6 @@ namespace GUI | |||
| { | ||||
| 
 | ||||
| class Tab; | ||||
| class PrintHostQueueDialog; | ||||
| class Plater; | ||||
| class MainFrame; | ||||
| class ParamsDialog; | ||||
|  | @ -111,7 +110,6 @@ class MainFrame : public DPIFrame | |||
|     bool can_export_toolpaths() const; | ||||
|     bool can_export_supports() const; | ||||
|     bool can_export_gcode() const; | ||||
|     bool can_send_gcode() const; | ||||
|     //bool can_export_gcode_sd() const;
 | ||||
|     //bool can_eject() const;
 | ||||
|     bool can_slice() const; | ||||
|  | @ -307,7 +305,6 @@ public: | |||
| 
 | ||||
|     // BBS. Replace title bar and menu bar with top bar.
 | ||||
|     BBLTopbar*            m_topbar{ nullptr }; | ||||
|     PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } | ||||
|     Plater*               m_plater { nullptr }; | ||||
|     //BBS: GUI refactor
 | ||||
|     MonitorPanel*         m_monitor{ nullptr }; | ||||
|  | @ -324,7 +321,6 @@ public: | |||
|     SettingsDialog        m_settings_dialog; | ||||
|     DiffPresetDialog      diff_dialog; | ||||
|     wxWindow*             m_plater_page{ nullptr }; | ||||
|     PrintHostQueueDialog* m_printhost_queue_dlg; | ||||
| 
 | ||||
|     // BBS
 | ||||
|     mutable int m_print_select{ ePrintAll }; | ||||
|  |  | |||
|  | @ -983,149 +983,6 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty | |||
| 	update(data); | ||||
| } | ||||
| 
 | ||||
| //------PrintHostUploadNotification----------------
 | ||||
| void NotificationManager::PrintHostUploadNotification::init() | ||||
| { | ||||
| 	ProgressBarNotification::init(); | ||||
| 	if (m_state == EState::NotFading && m_uj_state == UploadJobState::PB_COMPLETED) | ||||
| 		m_state = EState::Shown; | ||||
| } | ||||
| void NotificationManager::PrintHostUploadNotification::count_spaces() | ||||
| { | ||||
| 	//determine line width 
 | ||||
| 	m_line_height = ImGui::CalcTextSize("A").y; | ||||
| 
 | ||||
| 	m_left_indentation = m_line_height; | ||||
| 	if (m_uj_state == UploadJobState::PB_ERROR) { | ||||
| 		std::string text; | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		float picture_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
| 		m_left_indentation = picture_width + m_line_height / 2; | ||||
| 	} | ||||
| 	m_window_width_offset = m_line_height * 6; //(m_has_cancel_button ? 6 : 4);
 | ||||
| 	m_window_width = m_line_height * 25; | ||||
| } | ||||
| bool NotificationManager::PrintHostUploadNotification::push_background_color() | ||||
| { | ||||
| 
 | ||||
| 	if (m_uj_state == UploadJobState::PB_ERROR) { | ||||
| 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); | ||||
| 		backcolor.x += 0.3f; | ||||
| 		push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) | ||||
| { | ||||
| 	m_percentage = percent; | ||||
| 	if (percent >= 1.0f) { | ||||
| 		m_uj_state = UploadJobState::PB_COMPLETED; | ||||
| 		m_has_cancel_button = false; | ||||
| 		init(); | ||||
| 	} else if (percent < 0.0f) { | ||||
| 		error(); | ||||
| 	} else { | ||||
| 		m_uj_state = UploadJobState::PB_PROGRESS; | ||||
| 		m_has_cancel_button = true; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	std::string text; | ||||
| 	switch (m_uj_state) { | ||||
| 	case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_PROGRESS: | ||||
| 	{ | ||||
| 		ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 		float uploaded = m_file_size * m_percentage; | ||||
| 		std::stringstream stream; | ||||
| 		stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; | ||||
| 		text = stream.str(); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); | ||||
| 		break; | ||||
| 	} | ||||
| 	case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_ERROR: | ||||
| 		text = _u8L("ERROR"); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_CANCELLED: | ||||
| 		text = _u8L("CANCELED"); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED: | ||||
| 		text = _u8L("COMPLETED"); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); | ||||
| 		break; | ||||
| 	} | ||||
| 	 | ||||
| 	imgui.text(text.c_str()); | ||||
| 
 | ||||
| } | ||||
| void NotificationManager::PrintHostUploadNotification::render_left_sign(ImGuiWrapper& imgui) | ||||
| { | ||||
| 	if (m_uj_state == UploadJobState::PB_ERROR) { | ||||
| 		std::string text; | ||||
| 		text = ImGui::ErrorMarker; | ||||
| 		ImGui::SetCursorPosX(m_line_height / 3); | ||||
| 		ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); | ||||
| 		imgui.text(text.c_str()); | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	ImVec2 win_size(win_size_x, win_size_y); | ||||
| 	ImVec2 win_pos(win_pos_x, win_pos_y); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 
 | ||||
| 	std::string button_text; | ||||
| 	button_text = ImGui::CancelButton; | ||||
| 
 | ||||
| 	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y), | ||||
| 		ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y), | ||||
| 		true)) | ||||
| 	{ | ||||
| 		button_text = ImGui::CancelHoverButton; | ||||
| 		// tooltip
 | ||||
| 		long time_now = wxGetLocalTime(); | ||||
| 		if (m_hover_time > 0 && m_hover_time < time_now) { | ||||
| 			ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); | ||||
| 			ImGui::BeginTooltip(); | ||||
| 			imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); | ||||
| 			ImGui::EndTooltip(); | ||||
| 			ImGui::PopStyleColor(); | ||||
| 		} | ||||
| 		if (m_hover_time == 0) | ||||
| 			m_hover_time = time_now; | ||||
| 	} | ||||
| 	else | ||||
| 		m_hover_time = 0; | ||||
| 
 | ||||
| 	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); | ||||
| 	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); | ||||
| 	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); | ||||
| 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) | ||||
| 	{ | ||||
| 		wxGetApp().printhost_job_queue().cancel(m_job_id - 1); | ||||
| 	} | ||||
| 
 | ||||
| 	//invisible large button
 | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); | ||||
| 	ImGui::SetCursorPosY(0); | ||||
| 	if (imgui.button("  ", m_line_height * 2.f, win_size.y)) | ||||
| 	{ | ||||
| 		wxGetApp().printhost_job_queue().cancel(m_job_id - 1); | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(5); | ||||
| } | ||||
| //------SlicingProgressNotification
 | ||||
| void NotificationManager::SlicingProgressNotification::init() | ||||
| { | ||||
|  | @ -1739,59 +1596,6 @@ void NotificationManager::push_exporting_finished_notification(const std::string | |||
| 	set_slicing_progress_hidden(); | ||||
| } | ||||
| 
 | ||||
| void  NotificationManager::push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage) | ||||
| { | ||||
| 	// find if upload with same id was not already in notification
 | ||||
| 	// done by compare_jon_id not compare_text thus has to be performed here
 | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PrintHostUpload && dynamic_cast<PrintHostUploadNotification*>(notification.get())->compare_job_id(id)) { | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); | ||||
| 	NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, text }; | ||||
| 	push_notification_data(std::make_unique<NotificationManager::PrintHostUploadNotification>(data, m_id_provider, m_evt_handler, 0, id, filesize), 0); | ||||
| } | ||||
| void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PrintHostUpload) { | ||||
| 			PrintHostUploadNotification* phun = dynamic_cast<PrintHostUploadNotification*>(notification.get()); | ||||
| 			if (phun->compare_job_id(id)) { | ||||
| 				phun->set_percentage(percentage); | ||||
| 				wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PrintHostUpload) { | ||||
| 			PrintHostUploadNotification* phun = dynamic_cast<PrintHostUploadNotification*>(notification.get()); | ||||
| 			if (phun->compare_job_id(id)) { | ||||
| 				phun->cancel(); | ||||
| 				wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::upload_job_notification_show_error(int id, const std::string& filename, const std::string& host) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PrintHostUpload) { | ||||
| 			PrintHostUploadNotification* phun = dynamic_cast<PrintHostUploadNotification*>(notification.get()); | ||||
| 			if(phun->compare_job_id(id)) { | ||||
| 				phun->error(); | ||||
| 				wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::init_slicing_progress_notification(std::function<bool()> cancel_callback) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
|  |  | |||
|  | @ -182,11 +182,6 @@ public: | |||
| 	void stop_delayed_notifications_of_type(const NotificationType type); | ||||
| 	// Creates Validate Error notification with a custom text and no fade out.
 | ||||
| 	void push_validate_error_notification(StringObjectException const & error); | ||||
| 		// print host upload
 | ||||
| 	void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); | ||||
| 	void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); | ||||
| 	void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); | ||||
| 	void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); | ||||
| 	// Creates Slicing Error notification with a custom text and no fade out.
 | ||||
| 	void push_slicing_error_notification(const std::string& text); | ||||
| 	// Creates Slicing Warning notification with a custom text and no fade out.
 | ||||
|  | @ -562,48 +557,6 @@ private: | |||
| 
 | ||||
| 	}; | ||||
| 
 | ||||
| 	class PrintHostUploadNotification : public ProgressBarNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		enum class UploadJobState | ||||
| 		{ | ||||
| 			PB_PROGRESS, | ||||
| 			PB_ERROR, | ||||
| 			PB_CANCELLED, | ||||
| 			PB_COMPLETED | ||||
| 		}; | ||||
| 		PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize) | ||||
| 			:ProgressBarNotification(n, id_provider, evt_handler) | ||||
| 			, m_job_id(job_id) | ||||
| 			, m_file_size(filesize) | ||||
| 		{ | ||||
| 			m_has_cancel_button = true; | ||||
| 			set_percentage(percentage); | ||||
| 		} | ||||
| 		static std::string	get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; } | ||||
| 		void				set_percentage(float percent) override; | ||||
| 		void				cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } | ||||
| 		void				error()  { m_uj_state = UploadJobState::PB_ERROR;     m_has_cancel_button = false; init(); } | ||||
| 		bool				compare_job_id(const int other_id) const { return m_job_id == other_id; } | ||||
| 		bool				compare_text(const std::string& text) const override { return false; } | ||||
| 	protected: | ||||
| 		void        init() override; | ||||
| 		void		count_spaces() override; | ||||
| 		bool		push_background_color() override; | ||||
| 		void		render_bar(ImGuiWrapper& imgui, | ||||
| 								const float win_size_x, const float win_size_y, | ||||
| 								const float win_pos_x, const float win_pos_y) override; | ||||
| 		void		render_cancel_button(ImGuiWrapper& imgui, | ||||
| 											const float win_size_x, const float win_size_y, | ||||
| 											const float win_pos_x, const float win_pos_y) override; | ||||
| 		void		render_left_sign(ImGuiWrapper& imgui) override; | ||||
| 		// Identifies job in cancel callback
 | ||||
| 		int					m_job_id; | ||||
| 		// Size of uploaded size to be displayed in MB
 | ||||
| 		float			    m_file_size; | ||||
| 		long				m_hover_time{ 0 }; | ||||
| 		UploadJobState		m_uj_state{ UploadJobState::PB_PROGRESS }; | ||||
| 	}; | ||||
| 	class SlicingProgressNotification : public ProgressBarNotification | ||||
| 	{ | ||||
| 	public: | ||||
|  |  | |||
|  | @ -1,746 +0,0 @@ | |||
| #include "PhysicalPrinterDialog.hpp" | ||||
| #include "PresetComboBoxes.hpp" | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include <boost/algorithm/string.hpp> | ||||
| 
 | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/textctrl.h> | ||||
| #include <wx/button.h> | ||||
| #include <wx/statbox.h> | ||||
| #include <wx/wupdlock.h> | ||||
| 
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "libslic3r/PresetBundle.hpp" | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "MainFrame.hpp" | ||||
| #include "format.hpp" | ||||
| #include "Tab.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| #include "PrintHostDialogs.hpp" | ||||
| #include "../Utils/ASCIIFolding.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| #include "../Utils/FixModelByWin10.hpp" | ||||
| #include "../Utils/UndoRedo.hpp" | ||||
| #include "RemovableDriveManager.hpp" | ||||
| #include "BitmapCache.hpp" | ||||
| #include "BonjourDialog.hpp" | ||||
| #include "MsgDialog.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| #define BORDER_W 10 | ||||
| 
 | ||||
| //------------------------------------------
 | ||||
| //          PresetForPrinter
 | ||||
| //------------------------------------------
 | ||||
| 
 | ||||
| PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name) : | ||||
|     m_parent(parent) | ||||
| { | ||||
|     m_sizer = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     m_delete_preset_btn = new ScalableButton(parent, wxID_ANY, "cross"); | ||||
|     m_delete_preset_btn->SetFont(wxGetApp().normal_font()); | ||||
|     m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); | ||||
|     m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); | ||||
| 
 | ||||
|     m_presets_list = new PresetComboBox(parent, Preset::TYPE_PRINTER); | ||||
|     m_presets_list->set_printer_technology(parent->get_printer_technology()); | ||||
| 
 | ||||
|     m_presets_list->set_selection_changed_function([this](int selection) { | ||||
|         std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); | ||||
|         Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); | ||||
|         assert(preset); | ||||
|         Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); | ||||
|         if (preset->name == edited_preset.name) | ||||
|             preset = &edited_preset; | ||||
| 
 | ||||
|         // // if created physical printer doesn't have any settings, use the settings from the selected preset
 | ||||
|         // if (m_parent->get_printer()->has_empty_config()) {
 | ||||
|         //     // update Print Host upload from the selected preset
 | ||||
|         //     m_parent->get_printer()->update_from_preset(*preset);
 | ||||
|         //     // update values in parent (PhysicalPrinterDialog)
 | ||||
|         //     m_parent->update(true);
 | ||||
|              | ||||
|         // }
 | ||||
| 
 | ||||
|         // update PrinterTechnology if it was changed
 | ||||
|         if (m_presets_list->set_printer_technology(preset->printer_technology())) | ||||
|             m_parent->set_printer_technology(preset->printer_technology()); | ||||
| 
 | ||||
|         update_full_printer_name(); | ||||
|     }); | ||||
|     m_presets_list->update(preset_name); | ||||
| 
 | ||||
|     m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); | ||||
| 
 | ||||
|     m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); | ||||
|     m_full_printer_name->SetFont(wxGetApp().bold_font()); | ||||
| 
 | ||||
|     wxBoxSizer* preset_sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     preset_sizer->Add(m_presets_list        , 1, wxEXPAND); | ||||
|     preset_sizer->Add(m_delete_preset_btn   , 0, wxEXPAND | wxLEFT, BORDER_W); | ||||
| 
 | ||||
|     wxBoxSizer* name_sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     name_sizer->Add(m_info_line, 0, wxEXPAND); | ||||
|     name_sizer->Add(m_full_printer_name, 0, wxEXPAND | wxLEFT, BORDER_W); | ||||
| 
 | ||||
|     m_sizer->Add(preset_sizer   , 0, wxEXPAND); | ||||
|     m_sizer->Add(name_sizer, 0, wxEXPAND); | ||||
| } | ||||
| 
 | ||||
| PresetForPrinter::~PresetForPrinter() | ||||
| { | ||||
|     m_presets_list->Destroy(); | ||||
|     m_delete_preset_btn->Destroy(); | ||||
|     m_info_line->Destroy(); | ||||
|     m_full_printer_name->Destroy(); | ||||
| } | ||||
| 
 | ||||
| void PresetForPrinter::DeletePreset(wxEvent& event) | ||||
| { | ||||
|     m_parent->DeletePreset(this); | ||||
| } | ||||
| 
 | ||||
| void PresetForPrinter::update_full_printer_name() | ||||
| { | ||||
|     wxString printer_name   = m_parent->get_printer_name(); | ||||
|     wxString preset_name    = m_presets_list->GetString(m_presets_list->GetSelection()); | ||||
| 
 | ||||
|     m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); | ||||
| } | ||||
| 
 | ||||
| std::string PresetForPrinter::get_preset_name() | ||||
| { | ||||
|     return into_u8(m_presets_list->GetString(m_presets_list->GetSelection())); | ||||
| } | ||||
| 
 | ||||
| void PresetForPrinter::SuppressDelete() | ||||
| { | ||||
|     m_delete_preset_btn->Enable(false); | ||||
|      | ||||
|     // this case means that now we have only one related preset for the printer
 | ||||
|     // So, allow any selection
 | ||||
|     m_presets_list->set_printer_technology(ptAny); | ||||
|     m_presets_list->update(); | ||||
| } | ||||
| 
 | ||||
| void PresetForPrinter::AllowDelete() | ||||
| { | ||||
|     if (!m_delete_preset_btn->IsEnabled()) | ||||
|         m_delete_preset_btn->Enable(); | ||||
| 
 | ||||
|     m_presets_list->set_printer_technology(m_parent->get_printer_technology()); | ||||
|     m_presets_list->update(); | ||||
| } | ||||
| 
 | ||||
| void PresetForPrinter::msw_rescale() | ||||
| { | ||||
|     m_presets_list->msw_rescale(); | ||||
|     m_delete_preset_btn->msw_rescale(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| //------------------------------------------
 | ||||
| //          PhysicalPrinterDialog
 | ||||
| //------------------------------------------
 | ||||
| 
 | ||||
| PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_name) : | ||||
|     DPIDialog(parent, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), | ||||
|     m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()), | ||||
|     had_all_mk3(!printer_name.empty()) | ||||
| { | ||||
|     SetFont(wxGetApp().normal_font()); | ||||
| #ifndef _WIN32 | ||||
|     SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); | ||||
| #endif | ||||
|     m_default_name = _L("Type here the name of your printer device"); | ||||
|     bool new_printer = true; | ||||
| 
 | ||||
|     if (printer_name.IsEmpty()) | ||||
|         printer_name = m_default_name; | ||||
|     else { | ||||
|         std::string full_name = into_u8(printer_name); | ||||
|         printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); | ||||
|         new_printer = false; | ||||
|     } | ||||
| 
 | ||||
|     wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer") + ":"); | ||||
| 
 | ||||
|     m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies"); | ||||
|     m_add_preset_btn->SetFont(wxGetApp().normal_font()); | ||||
|     m_add_preset_btn->SetToolTip(_L("Add preset for this printer device"));  | ||||
|     m_add_preset_btn->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::AddPreset, this); | ||||
| 
 | ||||
|     m_printer_name    = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); | ||||
|     wxGetApp().UpdateDarkUI(m_printer_name); | ||||
|     m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); | ||||
| 
 | ||||
|     PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; | ||||
|     PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); | ||||
|     if (!printer) { | ||||
|         const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); | ||||
|         m_printer = PhysicalPrinter(into_u8(printer_name), m_printer.config, preset); | ||||
|         // if printer_name is empty it means that new printer is created, so enable all items in the preset list
 | ||||
|         m_presets.emplace_back(new PresetForPrinter(this, preset.name)); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         m_printer = *printer; | ||||
|         const std::set<std::string>& preset_names = printer->get_preset_names(); | ||||
|         for (const std::string& preset_name : preset_names) | ||||
|             m_presets.emplace_back(new PresetForPrinter(this, preset_name)); | ||||
|     } | ||||
| 
 | ||||
|     if (m_presets.size() == 1) | ||||
|         m_presets.front()->SuppressDelete(); | ||||
| 
 | ||||
|     update_full_printer_names(); | ||||
| 
 | ||||
|     m_config = &m_printer.config; | ||||
|     m_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; | ||||
|     m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); | ||||
|     build_printhost_settings(m_optgroup); | ||||
| 
 | ||||
|     wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); | ||||
|     wxButton* btnOK = static_cast<wxButton*>(this->FindWindowById(wxID_OK, this)); | ||||
|     wxGetApp().UpdateDarkUI(btnOK); | ||||
|     btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); | ||||
| 
 | ||||
|     wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this))); | ||||
| 
 | ||||
|     wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     nameSizer->Add(m_printer_name, 1, wxEXPAND); | ||||
|     nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, BORDER_W); | ||||
| 
 | ||||
|     m_presets_sizer = new wxBoxSizer(wxVERTICAL); | ||||
|     for (PresetForPrinter* preset : m_presets) | ||||
|         m_presets_sizer->Add(preset->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); | ||||
| 
 | ||||
|     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     topSizer->Add(label_top           , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); | ||||
|     topSizer->Add(nameSizer           , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); | ||||
|     topSizer->Add(m_presets_sizer     , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); | ||||
|     topSizer->Add(m_optgroup->sizer   , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); | ||||
|     topSizer->Add(btns                , 0, wxEXPAND | wxALL, BORDER_W);  | ||||
| 
 | ||||
|     SetSizer(topSizer); | ||||
|     topSizer->SetSizeHints(this); | ||||
| 
 | ||||
|     if (new_printer) { | ||||
|         m_printer_name->SetFocus(); | ||||
|         m_printer_name->SelectAll(); | ||||
|     } | ||||
| 
 | ||||
|     this->CenterOnScreen(); | ||||
| } | ||||
| 
 | ||||
| PhysicalPrinterDialog::~PhysicalPrinterDialog() | ||||
| { | ||||
|     for (PresetForPrinter* preset : m_presets) { | ||||
|         delete preset; | ||||
|         preset = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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("Connection to printers connected via the 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 == "host_type" || opt_key == "printhost_authorization_type") | ||||
|             this->update(); | ||||
|         if (opt_key == "print_host") | ||||
|             this->update_printhost_buttons(); | ||||
|     }; | ||||
| 
 | ||||
|     m_optgroup->append_single_option_line("host_type"); | ||||
| 
 | ||||
|     auto create_sizer_with_btn = [](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { | ||||
|         *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); | ||||
|         (*btn)->SetFont(wxGetApp().normal_font()); | ||||
| 
 | ||||
|         auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         sizer->Add(*btn); | ||||
|         return sizer; | ||||
|     }; | ||||
| 
 | ||||
|     auto printhost_browse = [=](wxWindow* parent)  | ||||
|     { | ||||
|         auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots); | ||||
|         m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { | ||||
|             BonjourDialog dialog(this, Preset::printer_technology(m_printer.config)); | ||||
|             if (dialog.show_and_lookup()) { | ||||
|                 m_optgroup->set_value("print_host", dialog.get_selected(), true); | ||||
|                 m_optgroup->get_field("print_host")->field_changed(); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         return sizer; | ||||
|     }; | ||||
| 
 | ||||
|     auto print_host_test = [=](wxWindow* parent) { | ||||
|         auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test")); | ||||
| 
 | ||||
|         m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { | ||||
|             std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config)); | ||||
|             if (!host) { | ||||
|                 const wxString text = _L("Could not get a valid Printer Host reference"); | ||||
|                 show_error(this, text); | ||||
|                 return; | ||||
|             } | ||||
|             wxString 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 | ||||
|                 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(); | ||||
|     Line host_line = m_optgroup->create_single_option_line(option); | ||||
|     host_line.append_widget(printhost_browse); | ||||
|     host_line.append_widget(print_host_test); | ||||
|     m_optgroup->append_line(host_line); | ||||
| 
 | ||||
|     m_optgroup->append_single_option_line("printhost_authorization_type"); | ||||
| 
 | ||||
|     option = m_optgroup->get_option("printhost_apikey"); | ||||
|     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()) { | ||||
|         option = m_optgroup->get_option("printhost_cafile"); | ||||
|         option.opt.width = Field::def_width_wider(); | ||||
|         Line cafile_line = m_optgroup->create_single_option_line(option); | ||||
| 
 | ||||
|         auto printhost_cafile_browse = [=](wxWindow* parent) { | ||||
|             auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots); | ||||
|             m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) { | ||||
|                 static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); | ||||
|                 wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||||
|                 if (openFileDialog.ShowModal() != wxID_CANCEL) { | ||||
|                     m_optgroup->set_value("printhost_cafile", openFileDialog.GetPath(), true); | ||||
|                     m_optgroup->get_field("printhost_cafile")->field_changed(); | ||||
|                 } | ||||
|                 }); | ||||
| 
 | ||||
|             return sizer; | ||||
|         }; | ||||
| 
 | ||||
|         cafile_line.append_widget(printhost_cafile_browse); | ||||
|         m_optgroup->append_line(cafile_line); | ||||
| 
 | ||||
|         Line cafile_hint{ "", "" }; | ||||
|         cafile_hint.full_width = 1; | ||||
|         cafile_hint.widget = [ca_file_hint](wxWindow* parent) { | ||||
|             auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); | ||||
|             auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             sizer->Add(txt); | ||||
|             return sizer; | ||||
|         }; | ||||
|         m_optgroup->append_line(cafile_hint); | ||||
|     } | ||||
|     else { | ||||
|          | ||||
|         Line line{ "", "" }; | ||||
|         line.full_width = 1; | ||||
| 
 | ||||
|         line.widget = [ca_file_hint](wxWindow* parent) { | ||||
|             std::string info = _u8L("HTTPS CA File") + ":\n\t" + | ||||
|                 (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + | ||||
|                 "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); | ||||
| 
 | ||||
|             //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str()));
 | ||||
|             auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str())); | ||||
|             txt->SetFont(wxGetApp().normal_font()); | ||||
|             auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             sizer->Add(txt, 1, wxEXPAND); | ||||
|             return sizer; | ||||
|         }; | ||||
|         m_optgroup->append_line(line); | ||||
|     } | ||||
| 
 | ||||
|     for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" }) {         | ||||
|         option = m_optgroup->get_option(opt_key); | ||||
|         option.opt.width = Field::def_width_wider(); | ||||
|         m_optgroup->append_single_option_line(option); | ||||
|     } | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
|     option = m_optgroup->get_option("printhost_ssl_ignore_revoke"); | ||||
|     option.opt.width = Field::def_width_wider(); | ||||
|     m_optgroup->append_single_option_line(option); | ||||
| #endif | ||||
| 
 | ||||
|     m_optgroup->activate(); | ||||
| 
 | ||||
|     Field* printhost_field = m_optgroup->get_field("print_host"); | ||||
|     if (printhost_field) | ||||
|     { | ||||
|         wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(printhost_field->getWindow()); | ||||
|         if (temp) | ||||
|             temp->Bind(wxEVT_TEXT, ([printhost_field, temp](wxEvent& e) | ||||
|             { | ||||
| #ifndef __WXGTK__ | ||||
|                 e.Skip(); | ||||
|                 temp->GetToolTip()->Enable(true); | ||||
| #endif // __WXGTK__
 | ||||
|                 // Remove all leading and trailing spaces from the input
 | ||||
|                 std::string trimed_str, str = trimed_str = temp->GetValue().ToStdString(); | ||||
|                 boost::trim(trimed_str); | ||||
|                 if (trimed_str != str) | ||||
|                     temp->SetValue(trimed_str); | ||||
| 
 | ||||
|                 TextCtrl* field = dynamic_cast<TextCtrl*>(printhost_field); | ||||
|                 if (field) | ||||
|                     field->propagate_value(); | ||||
|             }), temp->GetId()); | ||||
|     } | ||||
| 
 | ||||
|     // 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(); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::update_printhost_buttons() | ||||
| { | ||||
|     std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config)); | ||||
|     m_printhost_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); | ||||
|     m_printhost_browse_btn->Enable(host->has_auto_discovery()); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::update(bool printer_change) | ||||
| { | ||||
|     m_optgroup->reload_config(); | ||||
| 
 | ||||
|     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) { | ||||
|         update_host_type(printer_change); | ||||
|         const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type"); | ||||
|         m_optgroup->show_field("host_type"); | ||||
|         if (opt->value == htPrusaLink) | ||||
|         { | ||||
|             m_optgroup->show_field("printhost_authorization_type"); | ||||
|             AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("printhost_authorization_type")->value; | ||||
|             m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); | ||||
|             for (const char* opt_key : { "printhost_user", "printhost_password" }) | ||||
|                 m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); | ||||
|         } else { | ||||
|             m_optgroup->hide_field("printhost_authorization_type"); | ||||
|             m_optgroup->show_field("printhost_apikey", true); | ||||
|             for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" }) | ||||
|                 m_optgroup->hide_field(opt_key); | ||||
|             supports_multiple_printers = opt && opt->value == htRepetier; | ||||
|         } | ||||
|          | ||||
|     } | ||||
|     else { | ||||
|         m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); | ||||
|         m_optgroup->hide_field("host_type"); | ||||
| 
 | ||||
|         m_optgroup->show_field("printhost_authorization_type"); | ||||
| 
 | ||||
|         AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("printhost_authorization_type")->value; | ||||
|         m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); | ||||
| 
 | ||||
|         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); | ||||
| 
 | ||||
|     update_printhost_buttons(); | ||||
| 
 | ||||
|     this->SetSize(this->GetBestSize()); | ||||
|     this->Layout(); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::update_host_type(bool printer_change) | ||||
| { | ||||
|     if (m_presets.empty()) | ||||
|         return; | ||||
|     bool all_presets_are_from_mk3_family = true; | ||||
| 
 | ||||
|     for (PresetForPrinter* prstft : m_presets) { | ||||
|         std::string preset_name = prstft->get_preset_name(); | ||||
|         if (Preset* preset = wxGetApp().preset_bundle->printers.find_preset(preset_name)) { | ||||
|             std::string model_id = preset->config.opt_string("printer_model"); | ||||
|             auto model_supports_prusalink = [](const std::string &model) { | ||||
|                 return model.size() >= 3 && | ||||
|                     ((boost::starts_with(model, "MK") && model[2] > '2' && model[2] <= '9') || | ||||
|                       boost::starts_with(model, "MINI")); | ||||
|             }; | ||||
|             if (preset->vendor) { | ||||
|                 if (preset->vendor->name == "Prusa Research") { | ||||
|                     const std::vector<VendorProfile::PrinterModel>& models = preset->vendor->models; | ||||
|                     auto it = std::find_if(models.begin(), models.end(), | ||||
|                         [model_id](const VendorProfile::PrinterModel& model) { return model.id == model_id; }); | ||||
|                     if (it != models.end() && model_supports_prusalink(it->family)) | ||||
|                         continue; | ||||
|                 } | ||||
|             } else if (model_supports_prusalink(model_id)) | ||||
|                 continue; | ||||
|         } | ||||
|         all_presets_are_from_mk3_family = false; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     Field* ht = m_optgroup->get_field("host_type"); | ||||
| 
 | ||||
|     wxArrayString types; | ||||
|     // Append localized enum_labels
 | ||||
|     assert(ht->m_opt.enum_labels.size() == ht->m_opt.enum_values.size()); | ||||
|     for (size_t i = 0; i < ht->m_opt.enum_labels.size(); i++) { | ||||
|         if (ht->m_opt.enum_values[i] == "prusalink" && !all_presets_are_from_mk3_family) | ||||
|             continue; | ||||
|         types.Add(_(ht->m_opt.enum_labels[i])); | ||||
|     } | ||||
| 
 | ||||
|     Choice* choice = dynamic_cast<Choice*>(ht); | ||||
|     choice->set_values(types); | ||||
|     auto set_to_choice_and_config = [this, choice](PrintHostType type) { | ||||
|         choice->set_value(static_cast<int>(type)); | ||||
|         m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(type)); | ||||
|     }; | ||||
|     if ((printer_change && all_presets_are_from_mk3_family) || (!had_all_mk3 && all_presets_are_from_mk3_family)) | ||||
|         set_to_choice_and_config(htPrusaLink);   | ||||
|     else if ((printer_change && !all_presets_are_from_mk3_family) || (!all_presets_are_from_mk3_family && m_config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value == htPrusaLink)) | ||||
|         set_to_choice_and_config(htOctoPrint); | ||||
|     else | ||||
|         choice->set_value(m_config->option("host_type")->getInt()); | ||||
|     had_all_mk3 = all_presets_are_from_mk3_family; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| wxString PhysicalPrinterDialog::get_printer_name() | ||||
| { | ||||
|     return m_printer_name->GetValue(); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::update_full_printer_names() | ||||
| { | ||||
|     for (PresetForPrinter* preset : m_presets) | ||||
|         preset->update_full_printer_name(); | ||||
| 
 | ||||
|     this->Layout(); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::set_printer_technology(PrinterTechnology pt) | ||||
| { | ||||
|     m_config->set_key_value("printer_technology", new ConfigOptionEnum<PrinterTechnology>(pt)); | ||||
|     update(); | ||||
| } | ||||
| 
 | ||||
| PrinterTechnology PhysicalPrinterDialog::get_printer_technology() | ||||
| { | ||||
|     return m_printer.printer_technology(); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) | ||||
| { | ||||
|     const int& em = em_unit(); | ||||
| 
 | ||||
|     m_add_preset_btn->msw_rescale(); | ||||
|     m_printhost_browse_btn->msw_rescale(); | ||||
|     m_printhost_test_btn->msw_rescale(); | ||||
|     if (m_printhost_cafile_browse_btn) | ||||
|         m_printhost_cafile_browse_btn->msw_rescale(); | ||||
| 
 | ||||
|     m_optgroup->msw_rescale(); | ||||
| 
 | ||||
|     msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); | ||||
| 
 | ||||
|     for (PresetForPrinter* preset : m_presets) | ||||
|         preset->msw_rescale(); | ||||
| 
 | ||||
|     const wxSize& size = wxSize(45 * em, 35 * em); | ||||
|     SetMinSize(size); | ||||
| 
 | ||||
|     Fit(); | ||||
|     Refresh(); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::OnOK(wxEvent& event) | ||||
| { | ||||
|     wxString printer_name = m_printer_name->GetValue(); | ||||
|     if (printer_name.IsEmpty()) { | ||||
|         warning_catcher(this, _L("The supplied name is empty. It can't be saved.")); | ||||
|         return; | ||||
|     } | ||||
|     if (printer_name == m_default_name) { | ||||
|         warning_catcher(this, _L("You should change the name of your printer device.")); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; | ||||
|     const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name), false); | ||||
|     if (existing && into_u8(printer_name) != printers.get_selected_printer_name()) | ||||
|     { | ||||
|         wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % existing->name/*printer_name*/).str()); | ||||
|         msg_text += "\n" + _L("Replace?"); | ||||
|         //wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO);
 | ||||
|         MessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); | ||||
| 
 | ||||
|         if (dialog.ShowModal() == wxID_NO) | ||||
|             return; | ||||
| 
 | ||||
|         m_printer.name = existing->name; | ||||
|     } | ||||
| 
 | ||||
|     std::set<std::string> repeat_presets; | ||||
|     m_printer.reset_presets(); | ||||
|     for (PresetForPrinter* preset : m_presets) { | ||||
|         if (!m_printer.add_preset(preset->get_preset_name())) | ||||
|             repeat_presets.emplace(preset->get_preset_name()); | ||||
|     } | ||||
| 
 | ||||
|     if (!repeat_presets.empty()) | ||||
|     { | ||||
|         wxString repeatable_presets = "\n"; | ||||
|         int repeat_cnt = 0; | ||||
|         for (const std::string& preset_name : repeat_presets) { | ||||
|             repeatable_presets += "    " + from_u8(preset_name) + "\n"; | ||||
|             repeat_cnt++; | ||||
|         } | ||||
|         repeatable_presets += "\n"; | ||||
| 
 | ||||
|         wxString msg_text = format_wxstr(_L_PLURAL("Following printer preset is duplicated:%1%" | ||||
|                                                    "The above preset for printer \"%2%\" will be used just once.", | ||||
|                                                    "Following printer presets are duplicated:%1%" | ||||
|                                                    "The above presets for printer \"%2%\" will be used just once.", repeat_cnt), repeatable_presets, printer_name); | ||||
|         //wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxOK | wxCANCEL);
 | ||||
|         MessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxOK | wxCANCEL); | ||||
|         if (dialog.ShowModal() == wxID_CANCEL) | ||||
|             return; | ||||
|     } | ||||
| 
 | ||||
|     std::string renamed_from; | ||||
|     // temporary save previous printer name if it was edited
 | ||||
|     if (m_printer.name != into_u8(m_default_name) && | ||||
|         m_printer.name != into_u8(printer_name)) | ||||
|         renamed_from = m_printer.name; | ||||
| 
 | ||||
|     //update printer name, if it was changed
 | ||||
|     m_printer.set_name(into_u8(printer_name)); | ||||
| 
 | ||||
|     // save new physical printer
 | ||||
|     printers.save_printer(m_printer, renamed_from); | ||||
| 
 | ||||
|     if (m_printer.preset_names.find(printers.get_selected_printer_preset_name()) == m_printer.preset_names.end()) { | ||||
|         // select first preset for this printer
 | ||||
|         printers.select_printer(m_printer); | ||||
|         // refresh preset list on Printer Settings Tab
 | ||||
|         wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); | ||||
|     } | ||||
|     else | ||||
|         wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_preset_choice(); | ||||
| 
 | ||||
|     event.Skip(); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::AddPreset(wxEvent& event) | ||||
| { | ||||
|     m_presets.emplace_back(new PresetForPrinter(this)); | ||||
|     // enable DELETE button for the first preset, if was disabled
 | ||||
|     m_presets.front()->AllowDelete(); | ||||
| 
 | ||||
|     m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); | ||||
|     update_full_printer_names(); | ||||
|     this->Fit(); | ||||
| 
 | ||||
|     update_host_type(true); | ||||
| } | ||||
| 
 | ||||
| void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) | ||||
| { | ||||
|     if (m_presets.size() == 1) { | ||||
|         wxString msg_text = _L("It's not possible to delete the last related preset for the printer."); | ||||
|         //wxMessageDialog dialog(nullptr, msg_text, _L("Information"), wxICON_INFORMATION | wxOK);
 | ||||
|         MessageDialog dialog(nullptr, msg_text, _L("Information"), wxICON_INFORMATION | wxOK); | ||||
|         dialog.ShowModal(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     assert(preset_for_printer); | ||||
|     auto it = std::find(m_presets.begin(), m_presets.end(), preset_for_printer); | ||||
|     if (it == m_presets.end()) | ||||
|         return; | ||||
| 
 | ||||
|     const int remove_id = it - m_presets.begin(); | ||||
|     m_presets_sizer->Remove(remove_id); | ||||
|     delete preset_for_printer; | ||||
|     m_presets.erase(it); | ||||
| 
 | ||||
|     if (m_presets.size() == 1) | ||||
|         m_presets.front()->SuppressDelete(); | ||||
| 
 | ||||
|     this->Layout(); | ||||
|     this->Fit(); | ||||
| 
 | ||||
|     update_host_type(true); | ||||
| } | ||||
| 
 | ||||
| }}    // namespace Slic3r::GUI
 | ||||
|  | @ -1,111 +0,0 @@ | |||
| #ifndef slic3r_PhysicalPrinterDialog_hpp_ | ||||
| #define slic3r_PhysicalPrinterDialog_hpp_ | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <wx/gdicmn.h> | ||||
| 
 | ||||
| #include "libslic3r/Preset.hpp" | ||||
| #include "GUI_Utils.hpp" | ||||
| 
 | ||||
| class wxString; | ||||
| class wxTextCtrl; | ||||
| class wxStaticText; | ||||
| class ScalableButton; | ||||
| class wxBoxSizer; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| class PresetComboBox; | ||||
| 
 | ||||
| //------------------------------------------
 | ||||
| //          PresetForPrinter
 | ||||
| //------------------------------------------
 | ||||
| //static std::string g_info_string = " (modified)";
 | ||||
| class PhysicalPrinterDialog; | ||||
| class PresetForPrinter | ||||
| { | ||||
|     PhysicalPrinterDialog* m_parent         { nullptr }; | ||||
| 
 | ||||
|     PresetComboBox*     m_presets_list      { nullptr }; | ||||
|     ScalableButton*     m_delete_preset_btn { nullptr }; | ||||
|     wxStaticText*       m_info_line         { nullptr }; | ||||
|     wxStaticText*       m_full_printer_name { nullptr }; | ||||
| 
 | ||||
|     wxBoxSizer*         m_sizer             { nullptr }; | ||||
| 
 | ||||
|     void DeletePreset(wxEvent& event); | ||||
| 
 | ||||
| public: | ||||
|     PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name = ""); | ||||
|     ~PresetForPrinter(); | ||||
| 
 | ||||
|     wxBoxSizer*         sizer() { return m_sizer; } | ||||
|     void                update_full_printer_name(); | ||||
|     std::string         get_preset_name(); | ||||
|     void                SuppressDelete(); | ||||
|     void                AllowDelete(); | ||||
| 
 | ||||
|     void                msw_rescale(); | ||||
|     void                on_sys_color_changed() {}; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| //------------------------------------------
 | ||||
| //          PhysicalPrinterDialog
 | ||||
| //------------------------------------------
 | ||||
| 
 | ||||
| class ConfigOptionsGroup; | ||||
| class PhysicalPrinterDialog : public DPIDialog | ||||
| { | ||||
|     PhysicalPrinter     m_printer; | ||||
|     wxString            m_default_name; | ||||
|     DynamicPrintConfig* m_config            { nullptr }; | ||||
| 
 | ||||
|     wxTextCtrl*         m_printer_name      { nullptr }; | ||||
|     std::vector<PresetForPrinter*> m_presets; | ||||
| 
 | ||||
|     ConfigOptionsGroup* m_optgroup          { nullptr }; | ||||
| 
 | ||||
|     ScalableButton*     m_add_preset_btn                    {nullptr}; | ||||
|     ScalableButton*     m_printhost_browse_btn              {nullptr}; | ||||
|     ScalableButton*     m_printhost_test_btn                {nullptr}; | ||||
|     ScalableButton*     m_printhost_cafile_browse_btn       {nullptr}; | ||||
|     ScalableButton*     m_printhost_client_cert_browse_btn  {nullptr}; | ||||
|     ScalableButton*     m_printhost_port_browse_btn         {nullptr}; | ||||
| 
 | ||||
|     wxBoxSizer*         m_presets_sizer                 {nullptr}; | ||||
| 
 | ||||
|     void build_printhost_settings(ConfigOptionsGroup* optgroup); | ||||
|     void OnOK(wxEvent& event); | ||||
|     void AddPreset(wxEvent& event); | ||||
| 
 | ||||
| public: | ||||
|     PhysicalPrinterDialog(wxWindow* parent, wxString printer_name); | ||||
|     ~PhysicalPrinterDialog(); | ||||
| 
 | ||||
|     void        update(bool printer_change = false); | ||||
|     void        update_host_type(bool printer_change); | ||||
|     void        update_printhost_buttons(); | ||||
|     void        update_printers(); | ||||
|     wxString    get_printer_name(); | ||||
|     void        update_full_printer_names(); | ||||
|     PhysicalPrinter*    get_printer() {return &m_printer; } | ||||
|     void                set_printer_technology(PrinterTechnology pt); | ||||
|     PrinterTechnology   get_printer_technology(); | ||||
| 
 | ||||
|     void        DeletePreset(PresetForPrinter* preset_for_printer); | ||||
| protected: | ||||
|     void on_dpi_changed(const wxRect& suggested_rect) override; | ||||
|     void on_sys_color_changed() override {}; | ||||
| 
 | ||||
|     bool had_all_mk3; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif | ||||
|  | @ -124,8 +124,6 @@ | |||
| #include "libslic3r/Platform.hpp" | ||||
| #include "nlohmann/json.hpp" | ||||
| 
 | ||||
| #include "PhysicalPrinterDialog.hpp" | ||||
| 
 | ||||
| using boost::optional; | ||||
| namespace fs = boost::filesystem; | ||||
| using Slic3r::_3DScene; | ||||
|  | @ -505,26 +503,12 @@ Sidebar::Sidebar(Plater *parent) | |||
|         combo_printer->edit_btn = edit_btn; | ||||
|         p->combo_printer = combo_printer; | ||||
| 
 | ||||
|         ScalableButton* connection_btn = new ScalableButton(p->m_panel_printer_content, wxID_ANY, "printer"); | ||||
|         connection_btn->SetBackgroundColour(wxColour(255, 255, 255)); | ||||
|         connection_btn->SetToolTip(_L("Print connection")); | ||||
|         connection_btn->Bind(wxEVT_BUTTON, [this, combo_printer](wxCommandEvent) | ||||
|                              { | ||||
|                                 auto preset_name = into_u8(combo_printer->GetString(combo_printer->GetSelection())); | ||||
|                                 auto printer_name =  from_u8(PhysicalPrinter::get_short_name(preset_name)+"-printer"); | ||||
|                                  PhysicalPrinterDialog dlg(this->GetParent(),printer_name); | ||||
|                                  dlg.ShowModal(); | ||||
|                                 //  wxMessageBox("Connection support WIP", "INFO", wxOK | wxICON_INFORMATION);
 | ||||
|                              }); | ||||
| 
 | ||||
|         wxBoxSizer *vsizer_printer = new wxBoxSizer(wxVERTICAL); | ||||
|         wxBoxSizer *hsizer_printer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         wxBoxSizer* vsizer_printer = new wxBoxSizer(wxVERTICAL); | ||||
|         wxBoxSizer* hsizer_printer = new wxBoxSizer(wxHORIZONTAL); | ||||
| 
 | ||||
|         hsizer_printer->Add(combo_printer, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3)); | ||||
|         hsizer_printer->Add(edit_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3)); | ||||
|         hsizer_printer->Add(FromDIP(8), 0, 0, 0, 0); | ||||
|         hsizer_printer->Add(connection_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3)); | ||||
|         hsizer_printer->Add(FromDIP(8), 0, 0, 0, 0); | ||||
|         vsizer_printer->Add(hsizer_printer, 0, wxEXPAND, 0); | ||||
| 
 | ||||
|         // Bed type selection
 | ||||
|  |  | |||
|  | @ -1,520 +0,0 @@ | |||
| #include "PrintHostDialogs.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <iomanip> | ||||
| 
 | ||||
| #include <wx/frame.h> | ||||
| #include <wx/progdlg.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/textctrl.h> | ||||
| #include <wx/checkbox.h> | ||||
| #include <wx/button.h> | ||||
| #include <wx/dataview.h> | ||||
| #include <wx/wupdlock.h> | ||||
| #include <wx/debug.h> | ||||
| #include <wx/msgdlg.h> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/filesystem.hpp> | ||||
| #include <boost/nowide/convert.hpp> | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "MsgDialog.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "MainFrame.hpp" | ||||
| #include "libslic3r/AppConfig.hpp" | ||||
| #include "NotificationManager.hpp" | ||||
| #include "ExtraRenderers.hpp" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| static const char *CONFIG_KEY_PATH  = "printhost_path"; | ||||
| static const char *CONFIG_KEY_GROUP = "printhost_group"; | ||||
| 
 | ||||
| PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups) | ||||
|     : MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:")) | ||||
|     , txt_filename(new wxTextCtrl(this, wxID_ANY)) | ||||
|     , combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr) | ||||
|     , post_upload_action(PrintHostPostUploadAction::None) | ||||
| { | ||||
| #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.")); | ||||
|     label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); | ||||
| 
 | ||||
|     content_sizer->Add(txt_filename, 0, wxEXPAND); | ||||
|     content_sizer->Add(label_dir_hint); | ||||
|     content_sizer->AddSpacer(VERT_SPACING); | ||||
|      | ||||
|     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); | ||||
|     } | ||||
| 
 | ||||
|     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 += '/'; | ||||
|     } | ||||
|     const auto recent_path_len = recent_path.Length(); | ||||
|     recent_path += path.filename().wstring(); | ||||
|     wxString stem(path.stem().wstring()); | ||||
|     const auto stem_len = stem.Length(); | ||||
| 
 | ||||
|     txt_filename->SetValue(recent_path); | ||||
|     txt_filename->SetFocus(); | ||||
|      | ||||
|     m_valid_suffix = recent_path.substr(recent_path.find_last_of('.')); | ||||
|     // .gcode suffix control
 | ||||
|     auto validate_path = [this](const wxString &path) -> bool { | ||||
|         if (! path.Lower().EndsWith(m_valid_suffix.Lower())) { | ||||
|             MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), m_valid_suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO); | ||||
|             if (msg_wingow.ShowModal() == wxID_NO) | ||||
|                 return false; | ||||
|         } | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     if (post_actions.has(PrintHostPostUploadAction::StartPrint)) { | ||||
|         auto* btn_print = add_button(wxID_YES, false, _L("Upload and Print")); | ||||
|         btn_print->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) { | ||||
|             if (validate_path(txt_filename->GetValue())) { | ||||
|                 post_upload_action = PrintHostPostUploadAction::StartPrint; | ||||
|                 EndDialog(wxID_OK); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     if (post_actions.has(PrintHostPostUploadAction::StartSimulation)) { | ||||
|         // Using wxID_MORE as a button identifier to be different from the other buttons, wxID_MORE has no other meaning here.
 | ||||
|         auto* btn_simulate = add_button(wxID_MORE, false, _L("Upload and Simulate")); | ||||
|         btn_simulate->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) { | ||||
|             if (validate_path(txt_filename->GetValue())) { | ||||
|                 post_upload_action = PrintHostPostUploadAction::StartSimulation; | ||||
|                 EndDialog(wxID_OK); | ||||
|             }         | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     add_button(wxID_CANCEL); | ||||
| 
 | ||||
|     if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) { | ||||
|         btn_ok->SetLabel(_L("Upload")); | ||||
|         btn_ok->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) { | ||||
|             if (validate_path(txt_filename->GetValue())) { | ||||
|                 post_upload_action = PrintHostPostUploadAction::None; | ||||
|                 EndDialog(wxID_OK); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     finalize(); | ||||
| 
 | ||||
| #ifdef __linux__ | ||||
|     // On Linux with GTK2 when text control lose the focus then selection (colored background) disappears but text color stay white
 | ||||
|     // and as a result the text is invisible with light mode
 | ||||
|     // see https://github.com/prusa3d/PrusaSlicer/issues/4532
 | ||||
|     // Workaround: Unselect text selection explicitly on kill focus
 | ||||
|     txt_filename->Bind(wxEVT_KILL_FOCUS, [this](wxEvent& e) { | ||||
|         e.Skip(); | ||||
|         txt_filename->SetInsertionPoint(txt_filename->GetLastPosition()); | ||||
|     }, txt_filename->GetId()); | ||||
| #endif /* __linux__ */ | ||||
| 
 | ||||
|     Bind(wxEVT_SHOW, [=](const wxShowEvent &) { | ||||
|         // Another similar case where the function only works with EVT_SHOW + CallAfter,
 | ||||
|         // this time on Mac.
 | ||||
|         CallAfter([=]() { | ||||
|             txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| fs::path PrintHostSendDialog::filename() const | ||||
| { | ||||
|     return into_path(txt_filename->GetValue()); | ||||
| } | ||||
| 
 | ||||
| PrintHostPostUploadAction PrintHostSendDialog::post_action() const | ||||
| { | ||||
|     return post_upload_action; | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
|         // Persist path and print settings
 | ||||
|         wxString path = txt_filename->GetValue(); | ||||
|         int last_slash = path.Find('/', true); | ||||
| 		if (last_slash == wxNOT_FOUND) | ||||
| 			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)); | ||||
| 
 | ||||
|         if (combo_groups != nullptr) { | ||||
|             wxString group = combo_groups->GetValue(); | ||||
|             app_config->set("recent", CONFIG_KEY_GROUP, into_u8(group)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     MsgDialog::EndModal(ret); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_ERROR,    PrintHostQueueDialog::Event); | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL,   PrintHostQueueDialog::Event); | ||||
| 
 | ||||
| PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id) | ||||
|     : wxEvent(winid, eventType) | ||||
|     , job_id(job_id) | ||||
| {} | ||||
| 
 | ||||
| PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, int progress) | ||||
|     : wxEvent(winid, eventType) | ||||
|     , job_id(job_id) | ||||
|     , progress(progress) | ||||
| {} | ||||
| 
 | ||||
| PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error) | ||||
|     : wxEvent(winid, eventType) | ||||
|     , job_id(job_id) | ||||
|     , error(std::move(error)) | ||||
| {} | ||||
| 
 | ||||
| wxEvent *PrintHostQueueDialog::Event::Clone() const | ||||
| { | ||||
|     return new Event(*this); | ||||
| } | ||||
| 
 | ||||
| PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) | ||||
|     : 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) | ||||
| { | ||||
|     const auto em = GetTextExtent("m").x; | ||||
| 
 | ||||
|     auto *topsizer = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     std::vector<int> widths; | ||||
|     widths.reserve(6); | ||||
|     if (!load_user_data(UDT_COLS, widths)) { | ||||
|         widths.clear(); | ||||
|         for (size_t i = 0; i < 6; i++) | ||||
|             widths.push_back(-1); | ||||
|     } | ||||
| 
 | ||||
|     job_list = new wxDataViewListCtrl(this, wxID_ANY); | ||||
| 
 | ||||
|     // MSW DarkMode: workaround for the selected item in the list
 | ||||
|     auto append_text_column = [this](const wxString& label, int width, wxAlignment align = wxALIGN_LEFT, | ||||
|                                      int flags = wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE) { | ||||
| #ifdef _WIN32 | ||||
|             job_list->AppendColumn(new wxDataViewColumn(label, new TextRenderer(), job_list->GetColumnCount(), width, align, flags)); | ||||
| #else | ||||
|             job_list->AppendTextColumn(label, wxDATAVIEW_CELL_INERT, width, align, flags); | ||||
| #endif | ||||
|     }; | ||||
| 
 | ||||
|     // Note: Keep these in sync with Column
 | ||||
|     append_text_column(_L("ID"), widths[0]); | ||||
|     job_list->AppendProgressColumn(_L("Progress"),      wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); | ||||
|     append_text_column(_L("Status"),widths[2]); | ||||
|     append_text_column(_L("Host"),  widths[3]); | ||||
|     append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]); | ||||
|     append_text_column(_L("Filename"),      widths[5]); | ||||
|     append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN); | ||||
|   | ||||
|     auto *btnsizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     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->Disable(); | ||||
|     // 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(); | ||||
|     btnsizer->Add(btn_close); | ||||
| 
 | ||||
|     topsizer->Add(job_list, 1, wxEXPAND | wxBOTTOM, SPACING); | ||||
|     topsizer->Add(btnsizer, 0, wxEXPAND); | ||||
|     SetSizer(topsizer); | ||||
| 
 | ||||
|     wxGetApp().UpdateDlgDarkUI(this); | ||||
|     wxGetApp().UpdateDVCDarkUI(job_list); | ||||
| 
 | ||||
|     std::vector<int> size; | ||||
|     SetSize(load_user_data(UDT_SIZE, size) ? wxSize(size[0] * em, size[1] * em) : wxSize(HEIGHT * em, WIDTH * em)); | ||||
| 
 | ||||
|     Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) { | ||||
|         OnSize(evt);  | ||||
|         save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
|      }); | ||||
|      | ||||
|     std::vector<int> pos; | ||||
|     if (load_user_data(UDT_POSITION, pos)) | ||||
|         SetPosition(wxPoint(pos[0], pos[1])); | ||||
| 
 | ||||
|     Bind(wxEVT_MOVE, [this](wxMoveEvent& evt) { | ||||
|         save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
|     }); | ||||
| 
 | ||||
|     job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); }); | ||||
| 
 | ||||
|     btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { | ||||
|         int selected = job_list->GetSelectedRow(); | ||||
|         if (selected == wxNOT_FOUND) { return; } | ||||
| 
 | ||||
|         const JobState state = get_state(selected); | ||||
|         if (state < ST_ERROR) { | ||||
|             GUI::wxGetApp().printhost_job_queue().cancel(selected); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     btn_error->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { | ||||
|         int selected = job_list->GetSelectedRow(); | ||||
|         if (selected == wxNOT_FOUND) { return; } | ||||
|         GUI::show_error(nullptr, job_list->GetTextValue(selected, COL_ERRORMSG)); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::append_job(const PrintHostJob &job) | ||||
| { | ||||
|     wxCHECK_RET(!job.empty(), "PrintHostQueueDialog: Attempt to append an empty job"); | ||||
| 
 | ||||
|     wxVector<wxVariant> fields; | ||||
|     fields.push_back(wxVariant(wxString::Format("%d", job_list->GetItemCount() + 1))); | ||||
|     fields.push_back(wxVariant(0)); | ||||
|     fields.push_back(wxVariant(_L("Enqueued"))); | ||||
|     fields.push_back(wxVariant(job.printhost->get_host())); | ||||
|     boost::system::error_code ec; | ||||
|     boost::uintmax_t size_i = boost::filesystem::file_size(job.upload_data.source_path, ec); | ||||
|     std::stringstream stream; | ||||
|     if (ec) { | ||||
|         stream << "unknown"; | ||||
|         size_i = 0; | ||||
|         BOOST_LOG_TRIVIAL(error) << ec.message(); | ||||
|     } else  | ||||
|         stream << std::fixed << std::setprecision(2) << ((float)size_i / 1024 / 1024) << "MB"; | ||||
|     fields.push_back(wxVariant(stream.str())); | ||||
|     fields.push_back(wxVariant(job.upload_data.upload_path.string())); | ||||
|     fields.push_back(wxVariant("")); | ||||
|     job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW)); | ||||
|     // Both strings are UTF-8 encoded.
 | ||||
|     upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string()); | ||||
| 
 | ||||
|     wxGetApp().notification_manager()->push_upload_job_notification(job_list->GetItemCount(), (float)size_i / 1024 / 1024, job.upload_data.upload_path.string(), job.printhost->get_host()); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect) | ||||
| { | ||||
|     const int& em = em_unit(); | ||||
| 
 | ||||
|     msw_buttons_rescale(this, em, { wxID_DELETE, wxID_CANCEL, btn_error->GetId() }); | ||||
| 
 | ||||
|     SetMinSize(wxSize(HEIGHT * em, WIDTH * em)); | ||||
| 
 | ||||
|     Fit(); | ||||
|     Refresh(); | ||||
| 
 | ||||
|     save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_sys_color_changed() | ||||
| { | ||||
| #ifdef _WIN32 | ||||
|     wxGetApp().UpdateDlgDarkUI(this); | ||||
|     wxGetApp().UpdateDVCDarkUI(job_list); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx) | ||||
| { | ||||
|     wxCHECK_MSG(idx >= 0 && idx < job_list->GetItemCount(), ST_ERROR, "Out of bounds access to job list"); | ||||
|     return static_cast<JobState>(job_list->GetItemData(job_list->RowToItem(idx))); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::set_state(int idx, JobState state) | ||||
| { | ||||
|     wxCHECK_RET(idx >= 0 && idx < job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
|     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; | ||||
|     } | ||||
|     // This might be ambigous call, but user data needs to be saved time to time
 | ||||
|     save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_list_select() | ||||
| { | ||||
|     int selected = job_list->GetSelectedRow(); | ||||
|     if (selected != wxNOT_FOUND) { | ||||
|         const JobState state = get_state(selected); | ||||
|         btn_cancel->Enable(state < ST_ERROR); | ||||
|         btn_error->Enable(state == ST_ERROR); | ||||
|         Layout(); | ||||
|     } else { | ||||
|         btn_cancel->Disable(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_progress(Event &evt) | ||||
| { | ||||
|     wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
| 
 | ||||
|     if (evt.progress < 100) { | ||||
|         set_state(evt.job_id, ST_PROGRESS); | ||||
|         job_list->SetValue(wxVariant(evt.progress), evt.job_id, COL_PROGRESS); | ||||
|     } else { | ||||
|         set_state(evt.job_id, ST_COMPLETED); | ||||
|         job_list->SetValue(wxVariant(100), evt.job_id, COL_PROGRESS); | ||||
|     } | ||||
| 
 | ||||
|     on_list_select(); | ||||
| 
 | ||||
|     if (evt.progress > 0) | ||||
|     { | ||||
|         wxVariant nm, hst; | ||||
|         job_list->GetValue(nm, evt.job_id, COL_FILENAME); | ||||
|         job_list->GetValue(hst, evt.job_id, COL_HOST); | ||||
|         wxGetApp().notification_manager()->set_upload_job_notification_percentage(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()), evt.progress / 100.f); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_error(Event &evt) | ||||
| { | ||||
|     wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
| 
 | ||||
|     set_state(evt.job_id, ST_ERROR); | ||||
| 
 | ||||
|     auto errormsg = from_u8((boost::format("%1%\n%2%") % _utf8(L("Error uploading to print host:")) % std::string(evt.error.ToUTF8())).str()); | ||||
|     job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS); | ||||
|     job_list->SetValue(wxVariant(errormsg), evt.job_id, COL_ERRORMSG);    // Stashes the error message into a hidden column for later
 | ||||
| 
 | ||||
|     on_list_select(); | ||||
| 
 | ||||
|     GUI::show_error(nullptr, errormsg); | ||||
| 
 | ||||
|     wxVariant nm, hst; | ||||
|     job_list->GetValue(nm, evt.job_id, COL_FILENAME); | ||||
|     job_list->GetValue(hst, evt.job_id, COL_HOST); | ||||
|     wxGetApp().notification_manager()->upload_job_notification_show_error(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString())); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_cancel(Event &evt) | ||||
| { | ||||
|     wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
| 
 | ||||
|     set_state(evt.job_id, ST_CANCELLED); | ||||
|     job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS); | ||||
| 
 | ||||
|     on_list_select(); | ||||
| 
 | ||||
|     wxVariant nm, hst; | ||||
|     job_list->GetValue(nm, evt.job_id, COL_FILENAME); | ||||
|     job_list->GetValue(hst, evt.job_id, COL_HOST); | ||||
|     wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString())); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::get_active_jobs(std::vector<std::pair<std::string, std::string>>& ret) | ||||
| { | ||||
|     int ic = job_list->GetItemCount(); | ||||
|     for (int i = 0; i < ic; i++) | ||||
|     { | ||||
|         auto item = job_list->RowToItem(i); | ||||
|         auto data = job_list->GetItemData(item); | ||||
|         JobState st = static_cast<JobState>(data); | ||||
|         if(st == JobState::ST_NEW || st == JobState::ST_PROGRESS) | ||||
|             ret.emplace_back(upload_names[i]);        | ||||
|     } | ||||
|     //job_list->data
 | ||||
| } | ||||
| void PrintHostQueueDialog::save_user_data(int udt) | ||||
| { | ||||
|     const auto em = GetTextExtent("m").x; | ||||
|     auto *app_config = wxGetApp().app_config; | ||||
|     if (udt & UserDataType::UDT_SIZE) { | ||||
|          | ||||
|         app_config->set("print_host_queue_dialog_height", std::to_string(this->GetSize().x / em)); | ||||
|         app_config->set("print_host_queue_dialog_width", std::to_string(this->GetSize().y / em)); | ||||
|     } | ||||
|     if (udt & UserDataType::UDT_POSITION) | ||||
|     { | ||||
|         app_config->set("print_host_queue_dialog_x", std::to_string(this->GetPosition().x)); | ||||
|         app_config->set("print_host_queue_dialog_y", std::to_string(this->GetPosition().y)); | ||||
|     } | ||||
|     if (udt & UserDataType::UDT_COLS) | ||||
|     { | ||||
|         for (size_t i = 0; i < job_list->GetColumnCount() - 1; i++) | ||||
|         { | ||||
|             app_config->set("print_host_queue_dialog_column_" + std::to_string(i), std::to_string(job_list->GetColumn(i)->GetWidth())); | ||||
|         } | ||||
|     }     | ||||
| } | ||||
| bool PrintHostQueueDialog::load_user_data(int udt, std::vector<int>& vector) | ||||
| { | ||||
|     auto* app_config = wxGetApp().app_config; | ||||
|     auto hasget = [app_config](const std::string& name, std::vector<int>& vector)->bool { | ||||
|         if (app_config->has(name)) { | ||||
|             vector.push_back(std::stoi(app_config->get(name))); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     }; | ||||
|     if (udt & UserDataType::UDT_SIZE) { | ||||
|         if (!hasget("print_host_queue_dialog_height",vector)) | ||||
|             return false; | ||||
|         if (!hasget("print_host_queue_dialog_width", vector)) | ||||
|             return false; | ||||
|     } | ||||
|     if (udt & UserDataType::UDT_POSITION) | ||||
|     { | ||||
|         if (!hasget("print_host_queue_dialog_x", vector)) | ||||
|             return false; | ||||
|         if (!hasget("print_host_queue_dialog_y", vector)) | ||||
|             return false; | ||||
|     } | ||||
|     if (udt & UserDataType::UDT_COLS) | ||||
|     { | ||||
|         for (size_t i = 0; i < 6; i++) | ||||
|         { | ||||
|             if (!hasget("print_host_queue_dialog_column_" + std::to_string(i), vector)) | ||||
|                 return false; | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| }} | ||||
|  | @ -1,130 +0,0 @@ | |||
| #ifndef slic3r_PrintHostSendDialog_hpp_ | ||||
| #define slic3r_PrintHostSendDialog_hpp_ | ||||
| 
 | ||||
| #include <set> | ||||
| #include <string> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| 
 | ||||
| #include <wx/string.h> | ||||
| #include <wx/event.h> | ||||
| #include <wx/dialog.h> | ||||
| 
 | ||||
| #include "GUI_Utils.hpp" | ||||
| #include "MsgDialog.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| 
 | ||||
| class wxButton; | ||||
| class wxTextCtrl; | ||||
| class wxChoice; | ||||
| class wxComboBox; | ||||
| class wxDataViewListCtrl; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| class PrintHostSendDialog : public GUI::MsgDialog | ||||
| { | ||||
| public: | ||||
|     PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups); | ||||
|     boost::filesystem::path filename() const; | ||||
|     PrintHostPostUploadAction post_action() const; | ||||
|     std::string group() const; | ||||
| 
 | ||||
|     virtual void EndModal(int ret) override; | ||||
| private: | ||||
|     wxTextCtrl *txt_filename; | ||||
|     wxComboBox *combo_groups; | ||||
|     PrintHostPostUploadAction post_upload_action; | ||||
|     wxString    m_valid_suffix; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| class PrintHostQueueDialog : public DPIDialog | ||||
| { | ||||
| public: | ||||
|     class Event : public wxEvent | ||||
|     { | ||||
|     public: | ||||
|         size_t job_id; | ||||
|         int progress = 0;    // in percent
 | ||||
|         wxString error; | ||||
| 
 | ||||
|         Event(wxEventType eventType, int winid, size_t job_id); | ||||
|         Event(wxEventType eventType, int winid, size_t job_id, int progress); | ||||
|         Event(wxEventType eventType, int winid, size_t job_id, wxString error); | ||||
| 
 | ||||
|         virtual wxEvent *Clone() const; | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     PrintHostQueueDialog(wxWindow *parent); | ||||
| 
 | ||||
|     void append_job(const PrintHostJob &job); | ||||
|     void get_active_jobs(std::vector<std::pair<std::string, std::string>>& ret); | ||||
| 
 | ||||
|     virtual bool Show(bool show = true) override | ||||
|     { | ||||
|         if(!show) | ||||
|             save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
|         return DPIDialog::Show(show); | ||||
|     } | ||||
| protected: | ||||
|     void on_dpi_changed(const wxRect &suggested_rect) override; | ||||
|     void on_sys_color_changed() override; | ||||
| 
 | ||||
| private: | ||||
|     enum Column { | ||||
|         COL_ID, | ||||
|         COL_PROGRESS, | ||||
|         COL_STATUS, | ||||
|         COL_HOST, | ||||
|         COL_SIZE, | ||||
|         COL_FILENAME, | ||||
|         COL_ERRORMSG | ||||
|     }; | ||||
| 
 | ||||
|     enum JobState { | ||||
|         ST_NEW, | ||||
|         ST_PROGRESS, | ||||
|         ST_ERROR, | ||||
|         ST_CANCELLING, | ||||
|         ST_CANCELLED, | ||||
|         ST_COMPLETED, | ||||
|     }; | ||||
| 
 | ||||
|     enum { HEIGHT = 60, WIDTH = 30, SPACING = 5 }; | ||||
| 
 | ||||
|     enum UserDataType{ | ||||
|         UDT_SIZE = 1, | ||||
|         UDT_POSITION = 2, | ||||
|         UDT_COLS = 4 | ||||
|     }; | ||||
| 
 | ||||
|     wxButton *btn_cancel; | ||||
|     wxButton *btn_error; | ||||
|     wxDataViewListCtrl *job_list; | ||||
|     // Note: EventGuard prevents delivery of progress evts to a freed PrintHostQueueDialog
 | ||||
|     EventGuard on_progress_evt; | ||||
|     EventGuard on_error_evt; | ||||
|     EventGuard on_cancel_evt; | ||||
| 
 | ||||
|     JobState get_state(int idx); | ||||
|     void set_state(int idx, JobState); | ||||
|     void on_list_select(); | ||||
|     void on_progress(Event&); | ||||
|     void on_error(Event&); | ||||
|     void on_cancel(Event&); | ||||
|     // This vector keep adress and filename of uploads. It is used when checking for running uploads during exit.
 | ||||
|     std::vector<std::pair<std::string, std::string>> upload_names; | ||||
|     void save_user_data(int); | ||||
|     bool load_user_data(int, std::vector<int>&); | ||||
| }; | ||||
| 
 | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event); | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event); | ||||
| 
 | ||||
| }} | ||||
| 
 | ||||
| #endif | ||||
|  | @ -43,7 +43,6 @@ | |||
| #include "MarkdownTip.hpp" | ||||
| #include "Search.hpp" | ||||
| 
 | ||||
| // #include "BonjourDialog.hpp"
 | ||||
| #ifdef WIN32 | ||||
| 	#include <commctrl.h> | ||||
| #endif // WIN32
 | ||||
|  | @ -2876,7 +2875,7 @@ void TabPrinter::build_fff() | |||
| 
 | ||||
|     //    build_preset_description_line(optgroup.get());
 | ||||
| #endif | ||||
|     build_print_host_page(); | ||||
| 
 | ||||
|     build_unregular_pages(true); | ||||
| } | ||||
| 
 | ||||
|  | @ -3039,256 +3038,6 @@ PageShp TabPrinter::build_kinematics_page() | |||
| 
 | ||||
|     return page; | ||||
| } | ||||
| void TabPrinter::update_printers() | ||||
| { | ||||
|     wxBusyCursor wait; | ||||
| 
 | ||||
|     std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config)); | ||||
| 
 | ||||
|     wxArrayString printers; | ||||
|     Field *rs = get_field("printhost_port"); | ||||
|     try { | ||||
|         if (! host->get_printers(printers)) | ||||
|             printers.clear(); | ||||
|     } catch (const HostNetworkError &err) { | ||||
|         printers.clear(); | ||||
|         show_error(this, _L("Connection to printers connected via the 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 TabPrinter::update_host_type(bool printer_change) | ||||
| { | ||||
|     Field* ht = get_field("host_type"); | ||||
| 
 | ||||
|     wxArrayString types; | ||||
|     // Append localized enum_labels
 | ||||
|     assert(ht->m_opt.enum_labels.size() == ht->m_opt.enum_values.size()); | ||||
|     for (size_t i = 0; i < ht->m_opt.enum_labels.size(); i++) { | ||||
|         if (ht->m_opt.enum_values[i] == "prusalink") | ||||
|             continue; | ||||
|         types.Add(_(ht->m_opt.enum_labels[i])); | ||||
|     } | ||||
| 
 | ||||
|     Choice* choice = dynamic_cast<Choice*>(ht); | ||||
|     choice->set_values(types); | ||||
|     auto set_to_choice_and_config = [this, choice](PrintHostType type) { | ||||
|         choice->set_value(static_cast<int>(type)); | ||||
|         m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(type)); | ||||
|     }; | ||||
|     set_to_choice_and_config(htOctoPrint); | ||||
| } | ||||
| 
 | ||||
| void TabPrinter::update_printhost_buttons() | ||||
| { | ||||
|     std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config)); | ||||
|     m_printhost_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); | ||||
|     m_printhost_browse_btn->Enable(host->has_auto_discovery()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| PageShp TabPrinter::build_print_host_page() | ||||
| { | ||||
|     auto page = add_options_page(L("Connection"), "printer"); | ||||
|     #if 1 | ||||
|     auto optgroup = page->new_optgroup(L("Moonraker")); | ||||
|         Option option = optgroup->get_option("connection_moonraker_url"); | ||||
|         option.opt.full_width = true; | ||||
|         optgroup->append_single_option_line(option); | ||||
|         optgroup->append_single_option_line("connection_port"); | ||||
|     #else | ||||
|     auto optgroup = page->new_optgroup(L("Print Host upload")); | ||||
|     optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { | ||||
|         if (opt_key == "host_type" || opt_key == "printhost_authorization_type") | ||||
|             this->update(); | ||||
|         if (opt_key == "print_host") | ||||
|             this->update_printhost_buttons(); | ||||
|     }; | ||||
| 
 | ||||
|     optgroup->append_single_option_line("host_type"); | ||||
| 
 | ||||
|     auto create_sizer_with_btn = [](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { | ||||
|         *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); | ||||
|         (*btn)->SetFont(wxGetApp().normal_font()); | ||||
| 
 | ||||
|         auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         sizer->Add(*btn); | ||||
|         return sizer; | ||||
|     }; | ||||
| 
 | ||||
|     auto printhost_browse = [=](wxWindow* parent)  | ||||
|     { | ||||
|         auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots); | ||||
|         // m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
 | ||||
|         //     BonjourDialog dialog(this, m_printer_technology);
 | ||||
|         //     if (dialog.show_and_lookup()) {
 | ||||
|         //         optgroup->set_value("print_host", dialog.get_selected(), true);
 | ||||
|         //         optgroup->get_field("print_host")->field_changed();
 | ||||
|         //     }
 | ||||
|         // });
 | ||||
| 
 | ||||
|         return sizer; | ||||
|     }; | ||||
| 
 | ||||
|     auto print_host_test = [=](wxWindow* parent) { | ||||
|         auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test")); | ||||
| 
 | ||||
|         m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { | ||||
|             std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config)); | ||||
|             if (!host) { | ||||
|                 const wxString text = _L("Could not get a valid Printer Host reference"); | ||||
|                 show_error(this, text); | ||||
|                 return; | ||||
|             } | ||||
|             wxString 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 | ||||
|                 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 = optgroup->get_option("print_host"); | ||||
|     option.opt.width = Field::def_width_wider(); | ||||
|     Line host_line = optgroup->create_single_option_line(option); | ||||
|     host_line.append_widget(printhost_browse); | ||||
|     host_line.append_widget(print_host_test); | ||||
|     optgroup->append_line(host_line); | ||||
| 
 | ||||
|     optgroup->append_single_option_line("printhost_authorization_type"); | ||||
| 
 | ||||
|     option = optgroup->get_option("printhost_apikey"); | ||||
|     option.opt.width = Field::def_width_wider(); | ||||
|     optgroup->append_single_option_line(option); | ||||
| 
 | ||||
|     option = optgroup->get_option("printhost_port"); | ||||
|     option.opt.width = Field::def_width_wider(); | ||||
|     Line port_line = optgroup->create_single_option_line(option); | ||||
|     port_line.append_widget(print_host_printers); | ||||
|     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()) { | ||||
|         option = optgroup->get_option("printhost_cafile"); | ||||
|         option.opt.width = Field::def_width_wider(); | ||||
|         Line cafile_line = optgroup->create_single_option_line(option); | ||||
| 
 | ||||
|         auto printhost_cafile_browse = [=](wxWindow* parent) { | ||||
|             auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots); | ||||
|             m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, optgroup](wxCommandEvent e) { | ||||
|                 static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); | ||||
|                 wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||||
|                 if (openFileDialog.ShowModal() != wxID_CANCEL) { | ||||
|                     optgroup->set_value("printhost_cafile", openFileDialog.GetPath(), true); | ||||
|                     optgroup->get_field("printhost_cafile")->field_changed(); | ||||
|                 } | ||||
|                 }); | ||||
| 
 | ||||
|             return sizer; | ||||
|         }; | ||||
| 
 | ||||
|         cafile_line.append_widget(printhost_cafile_browse); | ||||
|         optgroup->append_line(cafile_line); | ||||
| 
 | ||||
|         Line cafile_hint{ "", "" }; | ||||
|         cafile_hint.full_width = 1; | ||||
|         cafile_hint.widget = [ca_file_hint](wxWindow* parent) { | ||||
|             auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); | ||||
|             auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             sizer->Add(txt); | ||||
|             return sizer; | ||||
|         }; | ||||
|         optgroup->append_line(cafile_hint); | ||||
|     } | ||||
|     else { | ||||
|          | ||||
|         Line line{ "", "" }; | ||||
|         line.full_width = 1; | ||||
| 
 | ||||
|         line.widget = [ca_file_hint](wxWindow* parent) { | ||||
|             std::string info = _u8L("HTTPS CA File") + ":\n\t" + | ||||
|                 (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + | ||||
|                 "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); | ||||
| 
 | ||||
|             //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str()));
 | ||||
|             auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str())); | ||||
|             txt->SetFont(wxGetApp().normal_font()); | ||||
|             auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             sizer->Add(txt, 1, wxEXPAND); | ||||
|             return sizer; | ||||
|         }; | ||||
|         optgroup->append_line(line); | ||||
|     } | ||||
| 
 | ||||
|     for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" }) {         | ||||
|         option = optgroup->get_option(opt_key); | ||||
|         option.opt.width = Field::def_width_wider(); | ||||
|         optgroup->append_single_option_line(option); | ||||
|     } | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
|     option = optgroup->get_option("printhost_ssl_ignore_revoke"); | ||||
|     option.opt.width = Field::def_width_wider(); | ||||
|     optgroup->append_single_option_line(option); | ||||
| #endif | ||||
| 
 | ||||
|     optgroup->activate(); | ||||
| 
 | ||||
|     Field* printhost_field = optgroup->get_field("print_host"); | ||||
|     if (printhost_field) | ||||
|     { | ||||
|         wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(printhost_field->getWindow()); | ||||
|         if (temp) | ||||
|             temp->Bind(wxEVT_TEXT, ([printhost_field, temp](wxEvent& e) | ||||
|             { | ||||
| #ifndef __WXGTK__ | ||||
|                 e.Skip(); | ||||
|                 temp->GetToolTip()->Enable(true); | ||||
| #endif // __WXGTK__
 | ||||
|                 // Remove all leading and trailing spaces from the input
 | ||||
|                 std::string trimed_str, str = trimed_str = temp->GetValue().ToStdString(); | ||||
|                 boost::trim(trimed_str); | ||||
|                 if (trimed_str != str) | ||||
|                     temp->SetValue(trimed_str); | ||||
| 
 | ||||
|                 TextCtrl* field = dynamic_cast<TextCtrl*>(printhost_field); | ||||
|                 if (field) | ||||
|                     field->propagate_value(); | ||||
|             }), temp->GetId()); | ||||
|     } | ||||
| 
 | ||||
|     // Always fill in the "printhost_port" combo box from the config and select it.
 | ||||
|     { | ||||
|         Choice* choice = dynamic_cast<Choice*>(optgroup->get_field("printhost_port")); | ||||
|         // choice->set_values({ m_config->opt_string("printhost_port") });
 | ||||
|         choice->set_selection(); | ||||
|     } | ||||
| 
 | ||||
|     // update();
 | ||||
|     #endif | ||||
|     return page; | ||||
| } | ||||
| 
 | ||||
| /* Previous name build_extruder_pages().
 | ||||
|  * | ||||
|  |  | |||
|  | @ -547,14 +547,6 @@ private: | |||
|     std::vector<PageShp>			m_pages_fff; | ||||
|     std::vector<PageShp>			m_pages_sla; | ||||
| 
 | ||||
| 	//SoftFever
 | ||||
|     ScalableButton*     m_add_preset_btn                {nullptr}; | ||||
|     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}; | ||||
| public: | ||||
| 	ScalableButton*	m_reset_to_filament_color = nullptr; | ||||
| 
 | ||||
|  | @ -593,13 +585,6 @@ public: | |||
| 	//wxSizer*	create_bed_shape_widget(wxWindow* parent);
 | ||||
| 	void		cache_extruder_cnt(); | ||||
| 	bool		apply_extruder_cnt_from_cache(); | ||||
| 
 | ||||
| 	//SoftFever
 | ||||
| 	void        update_host_type(bool printer_change); | ||||
|     void        update_printhost_buttons(); | ||||
|     void        update_printers(); | ||||
| 	PageShp		build_print_host_page(); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| class TabSLAMaterial : public Tab | ||||
|  |  | |||
|  | @ -1,173 +0,0 @@ | |||
| #include "AstroBox.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <sstream> | ||||
| #include <exception> | ||||
| #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 "Http.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| namespace pt = boost::property_tree; | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| AstroBox::AstroBox(DynamicPrintConfig *config) : | ||||
|     host(config->opt_string("print_host")), | ||||
|     apikey(config->opt_string("printhost_apikey")), | ||||
|     cafile(config->opt_string("printhost_cafile")) | ||||
| {} | ||||
| 
 | ||||
| const char* AstroBox::get_name() const { return "AstroBox"; } | ||||
| 
 | ||||
| bool AstroBox::test(wxString &msg) const | ||||
| { | ||||
|     // Since the request is performed synchronously here,
 | ||||
|     // it is ok to refer to `msg` from within the closure
 | ||||
| 
 | ||||
|     const char *name = get_name(); | ||||
| 
 | ||||
|     bool res = true; | ||||
|     auto url = make_url("api/version"); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; | ||||
| 
 | ||||
|     auto http = Http::get(std::move(url)); | ||||
|     set_auth(http); | ||||
|     http.on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
|             res = false; | ||||
|             msg = format_error(body, error, status); | ||||
|         }) | ||||
|         .on_complete([&, this](std::string body, unsigned) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body; | ||||
| 
 | ||||
|             try { | ||||
|                 std::stringstream ss(body); | ||||
|                 pt::ptree ptree; | ||||
|                 pt::read_json(ss, ptree); | ||||
| 
 | ||||
|                 if (! ptree.get_optional<std::string>("api")) { | ||||
|                     res = false; | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 const auto text = ptree.get_optional<std::string>("text"); | ||||
|                 res = validate_version_text(text); | ||||
|                 if (! res) { | ||||
|                     msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "AstroBox")).str()); | ||||
|                 } | ||||
|             } | ||||
|             catch (const std::exception &) { | ||||
|                 res = false; | ||||
|                 msg = "Could not parse server response"; | ||||
|             } | ||||
|         }) | ||||
|         .perform_sync(); | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| wxString AstroBox::get_test_ok_msg () const | ||||
| { | ||||
|     return _(L("Connection to AstroBox works correctly.")); | ||||
| } | ||||
| 
 | ||||
| wxString AstroBox::get_test_failed_msg (wxString &msg) const | ||||
| { | ||||
|     return GUI::from_u8((boost::format("%s: %s\n\n%s") | ||||
|         % _utf8(L("Could not connect to AstroBox")) | ||||
|         % std::string(msg.ToUTF8()) | ||||
|         % _utf8(L("Note: AstroBox version at least 1.1.0 is required."))).str()); | ||||
| } | ||||
| 
 | ||||
| bool AstroBox::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const | ||||
| { | ||||
|     const char *name = get_name(); | ||||
| 
 | ||||
|     const auto upload_filename = upload_data.upload_path.filename(); | ||||
|     const auto upload_parent_path = upload_data.upload_path.parent_path(); | ||||
| 
 | ||||
|     wxString test_msg; | ||||
|     if (! test(test_msg)) { | ||||
|         error_fn(std::move(test_msg)); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool res = true; | ||||
| 
 | ||||
|     auto url = make_url("api/files/local"); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%") | ||||
|         % name | ||||
|         % upload_data.source_path | ||||
|         % url | ||||
|         % upload_filename.string() | ||||
|         % upload_parent_path.string() | ||||
|         % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false"); | ||||
| 
 | ||||
|     auto http = Http::post(std::move(url)); | ||||
|     set_auth(http); | ||||
|     http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false") | ||||
|         .form_add("path", upload_parent_path.string())      // XXX: slashes on windows ???
 | ||||
|         .form_add_file("file", upload_data.source_path.string(), upload_filename.string()) | ||||
|         .on_complete([&](std::string body, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; | ||||
|         }) | ||||
|         .on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
|             error_fn(format_error(body, error, status)); | ||||
|             res = false; | ||||
|         }) | ||||
|         .on_progress([&](Http::Progress progress, bool &cancel) { | ||||
|             prorgess_fn(std::move(progress), cancel); | ||||
|             if (cancel) { | ||||
|                 // Upload was canceled
 | ||||
|                 BOOST_LOG_TRIVIAL(info) << "AstroBox: Upload canceled"; | ||||
|                 res = false; | ||||
|             } | ||||
|         }) | ||||
|         .perform_sync(); | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| bool AstroBox::validate_version_text(const boost::optional<std::string> &version_text) const | ||||
| { | ||||
|     return version_text ? boost::starts_with(*version_text, "AstroBox") : true; | ||||
| } | ||||
| 
 | ||||
| void AstroBox::set_auth(Http &http) const | ||||
| { | ||||
|     http.header("X-Api-Key", apikey); | ||||
| 
 | ||||
|     if (! cafile.empty()) { | ||||
|         http.ca_file(cafile); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::string AstroBox::make_url(const std::string &path) const | ||||
| { | ||||
|     if (host.find("http://") == 0 || host.find("https://") == 0) { | ||||
|         if (host.back() == '/') { | ||||
|             return (boost::format("%1%%2%") % host % path).str(); | ||||
|         } else { | ||||
|             return (boost::format("%1%/%2%") % host % path).str(); | ||||
|         } | ||||
|     } else { | ||||
|         return (boost::format("http://%1%/%2%") % host % path).str(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,46 +0,0 @@ | |||
| #ifndef slic3r_AstroBox_hpp_ | ||||
| #define slic3r_AstroBox_hpp_ | ||||
| 
 | ||||
| #include <string> | ||||
| #include <wx/string.h> | ||||
| #include <boost/optional.hpp> | ||||
| 
 | ||||
| #include "PrintHost.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class DynamicPrintConfig; | ||||
| class Http; | ||||
| 
 | ||||
| class AstroBox : public PrintHost | ||||
| { | ||||
| public: | ||||
|     AstroBox(DynamicPrintConfig *config); | ||||
|     ~AstroBox() override = default; | ||||
| 
 | ||||
|     const char* get_name() const override; | ||||
| 
 | ||||
|     bool test(wxString &curl_msg) const override; | ||||
|     wxString get_test_ok_msg () const override; | ||||
|     wxString get_test_failed_msg (wxString &msg) const override; | ||||
|     bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override; | ||||
|     bool has_auto_discovery() const override { return true; } | ||||
|     bool can_test() const override { return true; } | ||||
|     PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } | ||||
|     std::string get_host() const override { return host; } | ||||
|      | ||||
| protected: | ||||
|     bool validate_version_text(const boost::optional<std::string> &version_text) const; | ||||
| 
 | ||||
| private: | ||||
|     std::string host; | ||||
|     std::string apikey; | ||||
|     std::string cafile; | ||||
| 
 | ||||
|     void set_auth(Http &http) const; | ||||
|     std::string make_url(const std::string &path) const; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,286 +0,0 @@ | |||
| #include "Duet.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <ctime> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| #include <boost/format.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/property_tree/ptree.hpp> | ||||
| #include <boost/property_tree/json_parser.hpp> | ||||
| 
 | ||||
| #include <wx/frame.h> | ||||
| #include <wx/event.h> | ||||
| #include <wx/progdlg.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/textctrl.h> | ||||
| #include <wx/checkbox.h> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| #include "slic3r/GUI/I18N.hpp" | ||||
| #include "slic3r/GUI/MsgDialog.hpp" | ||||
| #include "Http.hpp" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| namespace pt = boost::property_tree; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| Duet::Duet(DynamicPrintConfig *config) : | ||||
| 	host(config->opt_string("print_host")), | ||||
| 	password(config->opt_string("printhost_apikey")) | ||||
| {} | ||||
| 
 | ||||
| const char* Duet::get_name() const { return "Duet"; } | ||||
| 
 | ||||
| bool Duet::test(wxString &msg) const | ||||
| { | ||||
| 	auto connectionType = connect(msg); | ||||
| 	disconnect(connectionType); | ||||
| 
 | ||||
| 	return connectionType != ConnectionType::error; | ||||
| } | ||||
| 
 | ||||
| wxString Duet::get_test_ok_msg () const | ||||
| { | ||||
| 	return _(L("Connection to Duet works correctly.")); | ||||
| } | ||||
| 
 | ||||
| wxString Duet::get_test_failed_msg (wxString &msg) const | ||||
| { | ||||
|     return GUI::from_u8((boost::format("%s: %s") | ||||
|                     % _utf8(L("Could not connect to Duet")) | ||||
|                     % std::string(msg.ToUTF8())).str()); | ||||
| } | ||||
| 
 | ||||
| bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const | ||||
| { | ||||
| 	wxString connect_msg; | ||||
| 	auto connectionType = connect(connect_msg); | ||||
| 	if (connectionType == ConnectionType::error) { | ||||
| 		error_fn(std::move(connect_msg)); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	bool res = true; | ||||
| 	bool dsf = (connectionType == ConnectionType::dsf); | ||||
| 
 | ||||
| 	auto upload_cmd = get_upload_url(upload_data.upload_path.string(), connectionType); | ||||
| 	BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filepath: %2%, post_action: %3%, command: %4%") | ||||
| 		% upload_data.source_path | ||||
| 		% upload_data.upload_path | ||||
| 		% int(upload_data.post_action) | ||||
| 		% upload_cmd; | ||||
| 
 | ||||
| 	auto http = (dsf ? Http::put(std::move(upload_cmd)) : Http::post(std::move(upload_cmd))); | ||||
| 	if (dsf) { | ||||
| 		http.set_put_body(upload_data.source_path); | ||||
| 	} else { | ||||
| 		http.set_post_body(upload_data.source_path); | ||||
| 	} | ||||
| 	http.on_complete([&](std::string body, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body; | ||||
| 
 | ||||
| 			int err_code = dsf ? (status == 201 ? 0 : 1) : get_err_code_from_body(body); | ||||
| 			if (err_code != 0) { | ||||
| 				BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Request completed but error code was received: %1%") % err_code; | ||||
| 				error_fn(format_error(body, L("Unknown error occured"), 0)); | ||||
| 				res = false; | ||||
| 			} else if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) { | ||||
| 				wxString errormsg; | ||||
| 				res = start_print(errormsg, upload_data.upload_path.string(), connectionType, false); | ||||
| 				if (! res) { | ||||
| 					error_fn(std::move(errormsg)); | ||||
| 				} | ||||
| 			} else if (upload_data.post_action == PrintHostPostUploadAction::StartSimulation) { | ||||
| 				wxString errormsg; | ||||
| 				res = start_print(errormsg, upload_data.upload_path.string(), connectionType, true); | ||||
| 				if (! res) { | ||||
| 					error_fn(std::move(errormsg)); | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 		.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; | ||||
| 			error_fn(format_error(body, error, status)); | ||||
| 			res = false; | ||||
| 		}) | ||||
| 		.on_progress([&](Http::Progress progress, bool &cancel) { | ||||
| 			prorgess_fn(std::move(progress), cancel); | ||||
| 			if (cancel) { | ||||
| 				// Upload was canceled
 | ||||
| 				BOOST_LOG_TRIVIAL(info) << "Duet: Upload canceled"; | ||||
| 				res = false; | ||||
| 			} | ||||
| 		}) | ||||
| 		.perform_sync(); | ||||
| 
 | ||||
| 	disconnect(connectionType); | ||||
| 
 | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| Duet::ConnectionType Duet::connect(wxString &msg) const | ||||
| { | ||||
| 	auto res = ConnectionType::error; | ||||
| 	auto url = get_connect_url(false); | ||||
| 
 | ||||
| 	auto http = Http::get(std::move(url)); | ||||
| 	http.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 			auto dsfUrl = get_connect_url(true); | ||||
| 			auto dsfHttp = Http::get(std::move(dsfUrl)); | ||||
| 			dsfHttp.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 					BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body; | ||||
| 					msg = format_error(body, error, status); | ||||
| 				}) | ||||
| 				.on_complete([&](std::string body, unsigned) { | ||||
| 					res = ConnectionType::dsf; | ||||
| 				}) | ||||
| 				.perform_sync(); | ||||
| 		}) | ||||
| 		.on_complete([&](std::string body, unsigned) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body; | ||||
| 
 | ||||
| 			int err_code = get_err_code_from_body(body); | ||||
| 			switch (err_code) { | ||||
| 				case 0: | ||||
| 					res = ConnectionType::rrf; | ||||
| 					break; | ||||
| 				case 1: | ||||
| 					msg = format_error(body, L("Wrong password"), 0); | ||||
| 					break; | ||||
| 				case 2: | ||||
| 					msg = format_error(body, L("Could not get resources to create a new connection"), 0); | ||||
| 					break; | ||||
| 				default: | ||||
| 					msg = format_error(body, L("Unknown error occured"), 0); | ||||
| 					break; | ||||
| 			} | ||||
| 
 | ||||
| 		}) | ||||
| 		.perform_sync(); | ||||
| 
 | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| void Duet::disconnect(ConnectionType connectionType) const | ||||
| { | ||||
| 	// we don't need to disconnect from DSF or if it failed anyway
 | ||||
| 	if (connectionType != ConnectionType::rrf) { | ||||
| 		return; | ||||
| 	} | ||||
| 	auto url =  (boost::format("%1%rr_disconnect") | ||||
| 			% get_base_url()).str(); | ||||
| 
 | ||||
| 	auto http = Http::get(std::move(url)); | ||||
| 	http.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 		// we don't care about it, if disconnect is not working Duet will disconnect automatically after some time
 | ||||
| 		BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body; | ||||
| 	}) | ||||
| 	.perform_sync(); | ||||
| } | ||||
| 
 | ||||
| std::string Duet::get_upload_url(const std::string &filename, ConnectionType connectionType) const | ||||
| { | ||||
|     assert(connectionType != ConnectionType::error); | ||||
| 
 | ||||
| 	if (connectionType == ConnectionType::dsf) { | ||||
| 		return (boost::format("%1%machine/file/gcodes/%2%") | ||||
| 				% get_base_url() | ||||
| 				% Http::url_encode(filename)).str(); | ||||
| 	} else { | ||||
| 		return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%") | ||||
| 				% get_base_url() | ||||
| 				% Http::url_encode(filename) | ||||
| 				% timestamp_str()).str(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| std::string Duet::get_connect_url(const bool dsfUrl) const | ||||
| { | ||||
| 	if (dsfUrl)	{ | ||||
| 		return (boost::format("%1%machine/status") | ||||
| 				% get_base_url()).str(); | ||||
| 	} else { | ||||
| 		return (boost::format("%1%rr_connect?password=%2%&%3%") | ||||
| 				% get_base_url() | ||||
| 				% (password.empty() ? "reprap" : password) | ||||
| 				% timestamp_str()).str(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| std::string Duet::get_base_url() const | ||||
| { | ||||
| 	if (host.find("http://") == 0 || host.find("https://") == 0) { | ||||
| 		if (host.back() == '/') { | ||||
| 			return host; | ||||
| 		} else { | ||||
| 			return (boost::format("%1%/") % host).str(); | ||||
| 		} | ||||
| 	} else { | ||||
| 		return (boost::format("http://%1%/") % host).str(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| std::string Duet::timestamp_str() const | ||||
| { | ||||
| 	enum { BUFFER_SIZE = 32 }; | ||||
| 
 | ||||
| 	auto t = std::time(nullptr); | ||||
| 	auto tm = *std::localtime(&t); | ||||
| 
 | ||||
| 	char buffer[BUFFER_SIZE]; | ||||
| 	std::strftime(buffer, BUFFER_SIZE, "time=%Y-%m-%dT%H:%M:%S", &tm); | ||||
| 
 | ||||
| 	return std::string(buffer); | ||||
| } | ||||
| 
 | ||||
| bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const | ||||
| { | ||||
|     assert(connectionType != ConnectionType::error); | ||||
| 
 | ||||
| 	bool res = false; | ||||
| 	bool dsf = (connectionType == ConnectionType::dsf); | ||||
| 
 | ||||
| 	auto url = dsf | ||||
| 		? (boost::format("%1%machine/code") | ||||
| 			% get_base_url()).str() | ||||
| 		: (boost::format(simulationMode | ||||
| 				? "%1%rr_gcode?gcode=M37%%20P\"0:/gcodes/%2%\"" | ||||
| 				: "%1%rr_gcode?gcode=M32%%20\"0:/gcodes/%2%\"") | ||||
| 			% get_base_url() | ||||
| 			% Http::url_encode(filename)).str(); | ||||
| 
 | ||||
| 	auto http = (dsf ? Http::post(std::move(url)) : Http::get(std::move(url))); | ||||
| 	if (dsf) { | ||||
| 		http.set_post_body( | ||||
| 				(boost::format(simulationMode | ||||
| 						? "M37 P\"0:/gcodes/%1%\"" | ||||
| 						: "M32 \"0:/gcodes/%1%\"") | ||||
| 					% filename).str() | ||||
| 				); | ||||
| 	} | ||||
| 	http.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body; | ||||
| 			msg = format_error(body, error, status); | ||||
| 		}) | ||||
| 		.on_complete([&](std::string body, unsigned) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body; | ||||
| 			res = true; | ||||
| 		}) | ||||
| 		.perform_sync(); | ||||
| 
 | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| int Duet::get_err_code_from_body(const std::string &body) const | ||||
| { | ||||
| 	pt::ptree root; | ||||
| 	std::istringstream iss (body); // wrap returned json to istringstream
 | ||||
| 	pt::read_json(iss, root); | ||||
| 
 | ||||
| 	return root.get<int>("err", 0); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,48 +0,0 @@ | |||
| #ifndef slic3r_Duet_hpp_ | ||||
| #define slic3r_Duet_hpp_ | ||||
| 
 | ||||
| #include <string> | ||||
| #include <wx/string.h> | ||||
| 
 | ||||
| #include "PrintHost.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class DynamicPrintConfig; | ||||
| class Http; | ||||
| 
 | ||||
| class Duet : public PrintHost | ||||
| { | ||||
| public: | ||||
|     explicit Duet(DynamicPrintConfig *config); | ||||
| 	~Duet() override = default; | ||||
| 
 | ||||
| 	const char* get_name() const override; | ||||
| 
 | ||||
| 	bool test(wxString &curl_msg) const override; | ||||
| 	wxString get_test_ok_msg() const override; | ||||
| 	wxString get_test_failed_msg(wxString &msg) const override; | ||||
| 	bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override; | ||||
| 	bool has_auto_discovery() const override { return false; } | ||||
| 	bool can_test() const override { return true; } | ||||
|     PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::StartSimulation; } | ||||
| 	std::string get_host() const override { return host; } | ||||
|     | ||||
| private: | ||||
| 	enum class ConnectionType { rrf, dsf, error }; | ||||
| 	std::string host; | ||||
| 	std::string password; | ||||
| 
 | ||||
| 	std::string get_upload_url(const std::string &filename, ConnectionType connectionType) const; | ||||
| 	std::string get_connect_url(const bool dsfUrl) const; | ||||
| 	std::string get_base_url() const; | ||||
| 	std::string timestamp_str() const; | ||||
| 	ConnectionType connect(wxString &msg) const; | ||||
| 	void disconnect(ConnectionType connectionType) const; | ||||
| 	bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const; | ||||
| 	int get_err_code_from_body(const std::string &body) const; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,229 +0,0 @@ | |||
| #include "FlashAir.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <ctime> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| #include <boost/format.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/algorithm/string/predicate.hpp> | ||||
| 
 | ||||
| #include <wx/frame.h> | ||||
| #include <wx/event.h> | ||||
| #include <wx/progdlg.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/textctrl.h> | ||||
| #include <wx/checkbox.h> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| #include "slic3r/GUI/I18N.hpp" | ||||
| #include "slic3r/GUI/MsgDialog.hpp" | ||||
| #include "Http.hpp" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| namespace pt = boost::property_tree; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| FlashAir::FlashAir(DynamicPrintConfig *config) : | ||||
| 	host(config->opt_string("print_host")) | ||||
| {} | ||||
| 
 | ||||
| const char* FlashAir::get_name() const { return "FlashAir"; } | ||||
| 
 | ||||
| bool FlashAir::test(wxString &msg) const | ||||
| { | ||||
| 	// Since the request is performed synchronously here,
 | ||||
| 	// it is ok to refer to `msg` from within the closure
 | ||||
| 
 | ||||
| 	const char *name = get_name(); | ||||
| 
 | ||||
| 	bool res = false; | ||||
| 	auto url = make_url("command.cgi", "op", "118"); | ||||
| 
 | ||||
| 	BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url; | ||||
| 
 | ||||
| 	auto http = Http::get(std::move(url)); | ||||
| 	http.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
| 			res = false; | ||||
| 			msg = format_error(body, error, status); | ||||
| 		}) | ||||
|         .on_complete([&](std::string body, unsigned) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body; | ||||
| 
 | ||||
| 			res = boost::starts_with(body, "1"); | ||||
| 			if (! res) { | ||||
| 				msg = _(L("Upload not enabled on FlashAir card.")); | ||||
| 			} | ||||
| 		}) | ||||
| 		.perform_sync(); | ||||
| 
 | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| wxString FlashAir::get_test_ok_msg () const | ||||
| { | ||||
| 	return _(L("Connection to FlashAir works correctly and upload is enabled.")); | ||||
| } | ||||
| 
 | ||||
| wxString FlashAir::get_test_failed_msg (wxString &msg) const | ||||
| { | ||||
|     return GUI::from_u8((boost::format("%s: %s\n%s") | ||||
|                     % _utf8(L("Could not connect to FlashAir")) | ||||
|                     % std::string(msg.ToUTF8()) | ||||
|                     % _utf8(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."))).str()); | ||||
| } | ||||
| 
 | ||||
| bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const | ||||
| { | ||||
| 	const char *name = get_name(); | ||||
| 
 | ||||
| 	const auto upload_filename = upload_data.upload_path.filename(); | ||||
| 	const auto upload_parent_path = upload_data.upload_path.parent_path(); | ||||
| 	wxString test_msg; | ||||
| 	if (! test(test_msg)) { | ||||
| 		error_fn(std::move(test_msg)); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	bool res = false; | ||||
| 
 | ||||
|     std::string strDest = upload_parent_path.string(); | ||||
|     if (strDest.front()!='/') // Needs a leading / else root uploads fail.
 | ||||
|     { | ||||
|         strDest.insert(0,"/"); | ||||
|     } | ||||
| 
 | ||||
| 	auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str()); | ||||
|     auto urlSetDir = make_url("upload.cgi","UPDIR",strDest); | ||||
| 	auto urlUpload = make_url("upload.cgi"); | ||||
| 
 | ||||
| 	BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%") | ||||
| 		% name | ||||
| 		% upload_data.source_path | ||||
| 		% urlPrepare | ||||
| 		% urlUpload | ||||
| 		% upload_filename.string(); | ||||
| 
 | ||||
| 	// set filetime for upload and make card writeprotect to prevent filesystem damage
 | ||||
| 	auto httpPrepare = Http::get(std::move(urlPrepare)); | ||||
| 	httpPrepare.on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error preparing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
| 			error_fn(format_error(body, error, status)); | ||||
| 			res = false; | ||||
| 		}) | ||||
| 		.on_complete([&, this](std::string body, unsigned) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body; | ||||
| 			res = boost::icontains(body, "SUCCESS"); | ||||
| 			if (! res) { | ||||
| 				BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; | ||||
| 				error_fn(format_error(body, L("Unknown error occured"), 0)); | ||||
| 			} | ||||
| 		}) | ||||
| 		.perform_sync(); | ||||
| 	 | ||||
| 	if(! res ) { | ||||
| 		return res; | ||||
| 	} | ||||
| 	 | ||||
| 	// start file upload
 | ||||
|     auto httpDir = Http::get(std::move(urlSetDir)); | ||||
|     httpDir.on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error setting upload dir: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
|             error_fn(format_error(body, error, status)); | ||||
|             res = false; | ||||
|         }) | ||||
|         .on_complete([&, this](std::string body, unsigned) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got dir select result: %2%") % name % body; | ||||
|             res = boost::icontains(body, "SUCCESS"); | ||||
|             if (! res) { | ||||
|                 BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; | ||||
|                 error_fn(format_error(body, L("Unknown error occured"), 0)); | ||||
|             } | ||||
|         }) | ||||
|         .perform_sync(); | ||||
| 
 | ||||
|     if(! res ) { | ||||
|         return res; | ||||
|     } | ||||
| 
 | ||||
| 	auto http = Http::post(std::move(urlUpload)); | ||||
| 	http.form_add_file("file", upload_data.source_path.string(), upload_filename.string()) | ||||
| 		.on_complete([&](std::string body, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; | ||||
| 			res = boost::icontains(body, "SUCCESS"); | ||||
| 			if (! res) { | ||||
| 				BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; | ||||
| 				error_fn(format_error(body, L("Unknown error occured"), 0)); | ||||
| 			} | ||||
| 		}) | ||||
| 		.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
| 			error_fn(format_error(body, error, status)); | ||||
| 			res = false; | ||||
| 		}) | ||||
| 		.on_progress([&](Http::Progress progress, bool &cancel) { | ||||
| 			prorgess_fn(std::move(progress), cancel); | ||||
| 			if (cancel) { | ||||
| 				// Upload was canceled
 | ||||
| 				BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Upload canceled") % name; | ||||
| 				res = false; | ||||
| 			} | ||||
| 		}) | ||||
| 		.perform_sync(); | ||||
| 
 | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| std::string FlashAir::timestamp_str() const | ||||
| { | ||||
| 	auto t = std::time(nullptr); | ||||
| 	auto tm = *std::localtime(&t); | ||||
| 
 | ||||
| 	unsigned long fattime = ((tm.tm_year - 80) << 25) |  | ||||
| 							((tm.tm_mon + 1) << 21) | | ||||
| 							(tm.tm_mday << 16) | | ||||
| 							(tm.tm_hour << 11) | | ||||
| 							(tm.tm_min << 5) | | ||||
| 							(tm.tm_sec >> 1); | ||||
| 
 | ||||
| 	return (boost::format("%1$#x") % fattime).str(); | ||||
| } | ||||
| 
 | ||||
| std::string FlashAir::make_url(const std::string &path) const | ||||
| { | ||||
| 	if (host.find("http://") == 0 || host.find("https://") == 0) { | ||||
| 		if (host.back() == '/') { | ||||
| 			return (boost::format("%1%%2%") % host % path).str(); | ||||
| 		} else { | ||||
| 			return (boost::format("%1%/%2%") % host % path).str(); | ||||
| 		} | ||||
| 	} else { | ||||
| 		if (host.back() == '/') { | ||||
| 			return (boost::format("http://%1%%2%") % host % path).str(); | ||||
| 		} else { | ||||
| 			return (boost::format("http://%1%/%2%") % host % path).str(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const | ||||
| { | ||||
| 	if (host.find("http://") == 0 || host.find("https://") == 0) { | ||||
| 		if (host.back() == '/') { | ||||
| 			return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str(); | ||||
| 		} else { | ||||
| 			return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str(); | ||||
| 		} | ||||
| 	} else { | ||||
| 		if (host.back() == '/') { | ||||
| 			return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str(); | ||||
| 		} else { | ||||
| 			return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,42 +0,0 @@ | |||
| #ifndef slic3r_FlashAir_hpp_ | ||||
| #define slic3r_FlashAir_hpp_ | ||||
| 
 | ||||
| #include <string> | ||||
| #include <wx/string.h> | ||||
| 
 | ||||
| #include "PrintHost.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class DynamicPrintConfig; | ||||
| class Http; | ||||
| 
 | ||||
| class FlashAir : public PrintHost | ||||
| { | ||||
| public: | ||||
| 	FlashAir(DynamicPrintConfig *config); | ||||
| 	~FlashAir() override = default; | ||||
| 
 | ||||
| 	const char* get_name() const override; | ||||
| 
 | ||||
| 	bool test(wxString &curl_msg) const override; | ||||
| 	wxString get_test_ok_msg() const override; | ||||
| 	wxString get_test_failed_msg(wxString &msg) const override; | ||||
| 	bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override; | ||||
| 	bool has_auto_discovery() const override { return false; } | ||||
| 	bool can_test() const override { return true; } | ||||
|     PrintHostPostUploadActions get_post_upload_actions() const override { return {}; } | ||||
| 	std::string get_host() const override { return host; } | ||||
|      | ||||
| private: | ||||
| 	std::string host; | ||||
| 
 | ||||
| 	std::string timestamp_str() const; | ||||
| 	std::string make_url(const std::string &path) const; | ||||
| 	std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,150 +0,0 @@ | |||
| #include "MKS.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <ctime> | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
| #include <boost/filesystem/path.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/asio.hpp> | ||||
| #include <boost/algorithm/string.hpp> | ||||
| 
 | ||||
| #include <wx/frame.h> | ||||
| #include <wx/event.h> | ||||
| #include <wx/progdlg.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/textctrl.h> | ||||
| #include <wx/checkbox.h> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| #include "slic3r/GUI/I18N.hpp" | ||||
| #include "slic3r/GUI/MsgDialog.hpp" | ||||
| #include "Http.hpp" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| namespace pt = boost::property_tree; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| MKS::MKS(DynamicPrintConfig* config) : | ||||
| 	m_host(config->opt_string("print_host")), m_console_port("8080") | ||||
| {} | ||||
| 
 | ||||
| const char* MKS::get_name() const { return "MKS"; } | ||||
| 
 | ||||
| bool MKS::test(wxString& msg) const | ||||
| { | ||||
| 	Utils::TCPConsole console(m_host, m_console_port); | ||||
| 
 | ||||
| 	console.enqueue_cmd("M105"); | ||||
| 	bool ret = console.run_queue(); | ||||
| 
 | ||||
| 	if (!ret) | ||||
| 		msg = wxString::FromUTF8(console.error_message().c_str()); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| wxString MKS::get_test_ok_msg() const | ||||
| { | ||||
| 	return _(L("Connection to MKS works correctly.")); | ||||
| } | ||||
| 
 | ||||
| wxString MKS::get_test_failed_msg(wxString& msg) const | ||||
| { | ||||
| 	return GUI::from_u8((boost::format("%s: %s") | ||||
| 		% _utf8(L("Could not connect to MKS")) | ||||
| 		% std::string(msg.ToUTF8())).str()); | ||||
| } | ||||
| 
 | ||||
| bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const | ||||
| { | ||||
| 	bool res = true; | ||||
| 
 | ||||
| 	auto upload_cmd = get_upload_url(upload_data.upload_path.string()); | ||||
| 	BOOST_LOG_TRIVIAL(info) << boost::format("MKS: Uploading file %1%, filepath: %2%, print: %3%, command: %4%") | ||||
| 		% upload_data.source_path | ||||
| 		% upload_data.upload_path | ||||
| 		% (upload_data.post_action == PrintHostPostUploadAction::StartPrint) | ||||
| 		% upload_cmd; | ||||
| 
 | ||||
| 	auto http = Http::post(std::move(upload_cmd)); | ||||
| 	http.set_post_body(upload_data.source_path); | ||||
| 
 | ||||
| 	http.on_complete([&](std::string body, unsigned status) { | ||||
| 		BOOST_LOG_TRIVIAL(debug) << boost::format("MKS: File uploaded: HTTP %1%: %2%") % status % body; | ||||
| 
 | ||||
| 		int err_code = get_err_code_from_body(body); | ||||
| 		if (err_code != 0) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << boost::format("MKS: Request completed but error code was received: %1%") % err_code; | ||||
| 			error_fn(format_error(body, L("Unknown error occured"), 0)); | ||||
| 			res = false; | ||||
| 		} | ||||
| 		else if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) { | ||||
| 			wxString errormsg; | ||||
| 			res = start_print(errormsg, upload_data.upload_path.string()); | ||||
| 			if (!res) { | ||||
| 				error_fn(std::move(errormsg)); | ||||
| 			} | ||||
| 		} | ||||
| 		}) | ||||
| 		.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << boost::format("MKS: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; | ||||
| 			error_fn(format_error(body, error, status)); | ||||
| 			res = false; | ||||
| 		}) | ||||
| 		.on_progress([&](Http::Progress progress, bool& cancel) { | ||||
| 			prorgess_fn(std::move(progress), cancel); | ||||
| 			if (cancel) { | ||||
| 				// Upload was canceled
 | ||||
| 				BOOST_LOG_TRIVIAL(info) << "MKS: Upload canceled"; | ||||
| 				res = false; | ||||
| 			} | ||||
| 		}).perform_sync(); | ||||
| 
 | ||||
| 
 | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| std::string MKS::get_upload_url(const std::string& filename) const | ||||
| { | ||||
| 	return (boost::format("http://%1%/upload?X-Filename=%2%") | ||||
| 		% m_host | ||||
| 		% Http::url_encode(filename)).str(); | ||||
| } | ||||
| 
 | ||||
| bool MKS::start_print(wxString& msg, const std::string& filename) const | ||||
| { | ||||
| 	// For some reason printer firmware does not want to respond on gcode commands immediately after file upload.
 | ||||
| 	// So we just introduce artificial delay to workaround it.
 | ||||
| 	// TODO: Inspect reasons
 | ||||
| 	std::this_thread::sleep_for(std::chrono::milliseconds(1500)); | ||||
| 
 | ||||
| 	Utils::TCPConsole console(m_host, m_console_port); | ||||
| 
 | ||||
| 	console.enqueue_cmd(std::string("M23 ") + filename); | ||||
| 	console.enqueue_cmd("M24"); | ||||
| 
 | ||||
| 	bool ret = console.run_queue(); | ||||
| 
 | ||||
| 	if (!ret) | ||||
| 		msg = wxString::FromUTF8(console.error_message().c_str()); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| int MKS::get_err_code_from_body(const std::string& body) const | ||||
| { | ||||
| 	pt::ptree root; | ||||
| 	std::istringstream iss(body); // wrap returned json to istringstream
 | ||||
| 	pt::read_json(iss, root); | ||||
| 
 | ||||
| 	return root.get<int>("err", 0); | ||||
| } | ||||
| 
 | ||||
| } // Slic3r
 | ||||
|  | @ -1,42 +0,0 @@ | |||
| #ifndef slic3r_MKS_hpp_ | ||||
| #define slic3r_MKS_hpp_ | ||||
| 
 | ||||
| #include <string> | ||||
| #include <wx/string.h> | ||||
| 
 | ||||
| #include "PrintHost.hpp" | ||||
| #include "TCPConsole.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| class DynamicPrintConfig; | ||||
| class Http; | ||||
| 
 | ||||
| class MKS : public PrintHost | ||||
| { | ||||
| public: | ||||
| 	explicit MKS(DynamicPrintConfig* config); | ||||
| 	~MKS() override = default; | ||||
| 
 | ||||
| 	const char* get_name() const override; | ||||
| 
 | ||||
| 	bool test(wxString& curl_msg) const override; | ||||
| 	wxString get_test_ok_msg() const override; | ||||
| 	wxString get_test_failed_msg(wxString& msg) const override; | ||||
| 	bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override; | ||||
| 	bool has_auto_discovery() const override { return false; } | ||||
| 	bool can_test() const override { return true; } | ||||
|     PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } | ||||
| 	std::string get_host() const override { return m_host; } | ||||
| 
 | ||||
| private: | ||||
| 	std::string m_host; | ||||
| 	std::string m_console_port; | ||||
| 
 | ||||
| 	std::string get_upload_url(const std::string& filename) const; | ||||
| 	bool start_print(wxString& msg, const std::string& filename) const; | ||||
| 	int get_err_code_from_body(const std::string& body) const; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,367 +0,0 @@ | |||
| #include "OctoPrint.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <sstream> | ||||
| #include <exception> | ||||
| #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 <curl/curl.h> | ||||
| 
 | ||||
| #include <wx/progdlg.h> | ||||
| 
 | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| #include "slic3r/GUI/I18N.hpp" | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| #include "Http.hpp" | ||||
| #include "libslic3r/AppConfig.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| namespace pt = boost::property_tree; | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
| // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
 | ||||
| namespace { | ||||
| std::string substitute_host(const std::string& orig_addr, std::string sub_addr) | ||||
| { | ||||
|     // put ipv6 into [] brackets 
 | ||||
|     if (sub_addr.find(':') != std::string::npos && sub_addr.at(0) != '[') | ||||
|         sub_addr = "[" + sub_addr + "]"; | ||||
| 
 | ||||
| #if 0 | ||||
|     //URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
 | ||||
|     std::string final_addr = orig_addr; | ||||
|     //  http
 | ||||
|     size_t double_dash = orig_addr.find("//"); | ||||
|     size_t host_start = (double_dash == std::string::npos ? 0 : double_dash + 2); | ||||
|     // userinfo
 | ||||
|     size_t at = orig_addr.find("@"); | ||||
|     host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start); | ||||
|     // end of host, could be port(:), subpath(/) (could be query(?) or fragment(#)?)
 | ||||
|     // or it will be ']' if address is ipv6 )
 | ||||
|     size_t potencial_host_end = orig_addr.find_first_of(":/", host_start);  | ||||
|     // if there are more ':' it must be ipv6
 | ||||
|     if (potencial_host_end != std::string::npos && orig_addr[potencial_host_end] == ':' && orig_addr.rfind(':') != potencial_host_end) { | ||||
|         size_t ipv6_end = orig_addr.find(']', host_start); | ||||
|         // DK: Uncomment and replace orig_addr.length() if we want to allow subpath after ipv6 without [] parentheses.
 | ||||
|         potencial_host_end = (ipv6_end != std::string::npos ? ipv6_end + 1 : orig_addr.length()); //orig_addr.find('/', host_start));
 | ||||
|     } | ||||
|     size_t host_end = (potencial_host_end != std::string::npos ? potencial_host_end : orig_addr.length()); | ||||
|     // now host_start and host_end should mark where to put resolved addr
 | ||||
|     // check host_start. if its nonsense, lets just use original addr (or  resolved addr?)
 | ||||
|     if (host_start >= orig_addr.length()) { | ||||
|         return final_addr; | ||||
|     } | ||||
|     final_addr.replace(host_start, host_end - host_start, sub_addr); | ||||
|     return final_addr; | ||||
| #else | ||||
|     // Using the new CURL API for handling URL. https://everything.curl.dev/libcurl/url
 | ||||
|     // If anything fails, return the input unchanged.
 | ||||
|     std::string out = orig_addr; | ||||
|     CURLU *hurl = curl_url(); | ||||
|     if (hurl) { | ||||
|         // Parse the input URL.
 | ||||
|         CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, orig_addr.c_str(), 0); | ||||
|         if (rc == CURLUE_OK) { | ||||
|             // Replace the address.
 | ||||
|             rc = curl_url_set(hurl, CURLUPART_HOST, sub_addr.c_str(), 0); | ||||
|             if (rc == CURLUE_OK) { | ||||
|                 // Extract a string fromt the CURL URL handle.
 | ||||
|                 char *url; | ||||
|                 rc = curl_url_get(hurl, CURLUPART_URL, &url, 0); | ||||
|                 if (rc == CURLUE_OK) { | ||||
|                     out = url; | ||||
|                     curl_free(url); | ||||
|                 } else | ||||
|                     BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to extract the URL after substitution"; | ||||
|             } else | ||||
|                 BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to substitute host " << sub_addr << " in URL " << orig_addr; | ||||
|         } else | ||||
|             BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to parse URL " << orig_addr; | ||||
|         curl_url_cleanup(hurl); | ||||
|     } else | ||||
|         BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to allocate curl_url"; | ||||
|     return out; | ||||
| #endif | ||||
| } | ||||
| } //namespace
 | ||||
| #endif // WIN32
 | ||||
| 
 | ||||
| OctoPrint::OctoPrint(DynamicPrintConfig *config) : | ||||
|     m_host(config->opt_string("print_host")), | ||||
|     m_apikey(config->opt_string("printhost_apikey")), | ||||
|     m_cafile(config->opt_string("printhost_cafile")), | ||||
|     m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke")) | ||||
| {} | ||||
| 
 | ||||
| const char* OctoPrint::get_name() const { return "OctoPrint"; } | ||||
| 
 | ||||
| bool OctoPrint::test(wxString &msg) const | ||||
| { | ||||
|     // Since the request is performed synchronously here,
 | ||||
|     // it is ok to refer to `msg` from within the closure
 | ||||
| 
 | ||||
|     const char *name = get_name(); | ||||
| 
 | ||||
|     bool res = true; | ||||
|     auto url = make_url("api/version"); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; | ||||
| 
 | ||||
|     auto http = Http::get(std::move(url)); | ||||
|     set_auth(http); | ||||
|     http.on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
|             res = false; | ||||
|             msg = format_error(body, error, status); | ||||
|         }) | ||||
|         .on_complete([&, this](std::string body, unsigned) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body; | ||||
| 
 | ||||
|             try { | ||||
|                 std::stringstream ss(body); | ||||
|                 pt::ptree ptree; | ||||
|                 pt::read_json(ss, ptree); | ||||
| 
 | ||||
|                 if (! ptree.get_optional<std::string>("api")) { | ||||
|                     res = false; | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 const auto text = ptree.get_optional<std::string>("text"); | ||||
|                 res = validate_version_text(text); | ||||
|                 if (! res) { | ||||
|                     msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str()); | ||||
|                 } | ||||
|             } | ||||
|             catch (const std::exception &) { | ||||
|                 res = false; | ||||
|                 msg = "Could not parse server response"; | ||||
|             } | ||||
|         }) | ||||
| #ifdef WIN32 | ||||
|         .ssl_revoke_best_effort(m_ssl_revoke_best_effort) | ||||
|         .on_ip_resolve([&](std::string address) { | ||||
|             // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
 | ||||
|             // Remember resolved address to be reused at successive REST API call.
 | ||||
|             msg = GUI::from_u8(address); | ||||
|         }) | ||||
| #endif // WIN32
 | ||||
|         .perform_sync(); | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| wxString OctoPrint::get_test_ok_msg () const | ||||
| { | ||||
|     return _(L("Connection to OctoPrint works correctly.")); | ||||
| } | ||||
| 
 | ||||
| wxString OctoPrint::get_test_failed_msg (wxString &msg) const | ||||
| { | ||||
|     return GUI::from_u8((boost::format("%s: %s\n\n%s") | ||||
|         % _utf8(L("Could not connect to OctoPrint")) | ||||
|         % std::string(msg.ToUTF8()) | ||||
|         % _utf8(L("Note: OctoPrint version at least 1.1.0 is required."))).str()); | ||||
| } | ||||
| 
 | ||||
| bool OctoPrint::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(); | ||||
| 
 | ||||
|     // If test fails, test_msg_or_host_ip contains the error message.
 | ||||
|     // Otherwise on Windows it contains the resolved IP address of the host.
 | ||||
|     wxString test_msg_or_host_ip; | ||||
|     if (! test(test_msg_or_host_ip)) { | ||||
|         error_fn(std::move(test_msg_or_host_ip)); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     std::string url; | ||||
|     bool res = true; | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
|     // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
 | ||||
|     if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || GUI::get_app_config()->get("allow_ip_resolve") != "1") | ||||
| #endif // _WIN32
 | ||||
|     { | ||||
|         // If https is entered we assume signed ceritificate is being used
 | ||||
|         // IP resolving will not happen - it could resolve into address not being specified in cert
 | ||||
|         url = make_url("api/files/local"); | ||||
|     } | ||||
| #ifdef WIN32 | ||||
|     else { | ||||
|         // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
 | ||||
|         // Curl uses easy_getinfo to get ip address of last successful transaction.
 | ||||
|         // If it got the address use it instead of the stored in "host" variable.
 | ||||
|         // This new address returns in "test_msg_or_host_ip" variable.
 | ||||
|         // Solves troubles of uploades failing with name address.
 | ||||
|         // in original address (m_host) replace host for resolved ip 
 | ||||
|         url = substitute_host(make_url("api/files/local"), GUI::into_u8(test_msg_or_host_ip)); | ||||
|         BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url; | ||||
|     } | ||||
| #endif // _WIN32
 | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%") | ||||
|         % name | ||||
|         % upload_data.source_path | ||||
|         % url | ||||
|         % upload_filename.string() | ||||
|         % upload_parent_path.string() | ||||
|         % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false"); | ||||
| 
 | ||||
|     auto http = Http::post(std::move(url)); | ||||
|     set_auth(http); | ||||
|     http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false") | ||||
|         .form_add("path", upload_parent_path.string())      // XXX: slashes on windows ???
 | ||||
|         .form_add_file("file", upload_data.source_path.string(), upload_filename.string()) | ||||
|         .on_complete([&](std::string body, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; | ||||
|         }) | ||||
|         .on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
|             error_fn(format_error(body, error, status)); | ||||
|             res = false; | ||||
|         }) | ||||
|         .on_progress([&](Http::Progress progress, bool &cancel) { | ||||
|             prorgess_fn(std::move(progress), cancel); | ||||
|             if (cancel) { | ||||
|                 // Upload was canceled
 | ||||
|                 BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled"; | ||||
|                 res = false; | ||||
|             } | ||||
|         }) | ||||
| #ifdef WIN32 | ||||
|         .ssl_revoke_best_effort(m_ssl_revoke_best_effort) | ||||
| #endif | ||||
|         .perform_sync(); | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| bool OctoPrint::validate_version_text(const boost::optional<std::string> &version_text) const | ||||
| { | ||||
|     return version_text ? boost::starts_with(*version_text, "OctoPrint") : true; | ||||
| } | ||||
| 
 | ||||
| void OctoPrint::set_auth(Http &http) const | ||||
| { | ||||
|     http.header("X-Api-Key", m_apikey); | ||||
| 
 | ||||
|     if (!m_cafile.empty()) { | ||||
|         http.ca_file(m_cafile); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::string OctoPrint::make_url(const std::string &path) const | ||||
| { | ||||
|     if (m_host.find("http://") == 0 || m_host.find("https://") == 0) { | ||||
|         if (m_host.back() == '/') { | ||||
|             return (boost::format("%1%%2%") % m_host % path).str(); | ||||
|         } else { | ||||
|             return (boost::format("%1%/%2%") % m_host % path).str(); | ||||
|         } | ||||
|     } else { | ||||
|         return (boost::format("http://%1%/%2%") % m_host % path).str(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| SL1Host::SL1Host(DynamicPrintConfig *config) :  | ||||
|     OctoPrint(config), | ||||
|     m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value), | ||||
|     m_username(config->opt_string("printhost_user")), | ||||
|     m_password(config->opt_string("printhost_password")) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| // SL1Host
 | ||||
| const char* SL1Host::get_name() const { return "SL1Host"; } | ||||
| 
 | ||||
| wxString SL1Host::get_test_ok_msg () const | ||||
| { | ||||
|     return _(L("Connection to Prusa SL1 / SL1S works correctly.")); | ||||
| } | ||||
| 
 | ||||
| wxString SL1Host::get_test_failed_msg (wxString &msg) const | ||||
| { | ||||
|     return GUI::from_u8((boost::format("%s: %s") | ||||
|                     % _utf8(L("Could not connect to Prusa SLA")) | ||||
|                     % std::string(msg.ToUTF8())).str()); | ||||
| } | ||||
| 
 | ||||
| bool SL1Host::validate_version_text(const boost::optional<std::string> &version_text) const | ||||
| { | ||||
|     return version_text ? boost::starts_with(*version_text, "Prusa SLA") : false; | ||||
| } | ||||
| 
 | ||||
| void SL1Host::set_auth(Http &http) const | ||||
| { | ||||
|     switch (m_authorization_type) { | ||||
|     case atKeyPassword: | ||||
|         http.header("X-Api-Key", get_apikey()); | ||||
|         break; | ||||
|     case atUserPassword: | ||||
|         http.auth_digest(m_username, m_password); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     if (! get_cafile().empty()) { | ||||
|         http.ca_file(get_cafile()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // PrusaLink
 | ||||
| PrusaLink::PrusaLink(DynamicPrintConfig* config) : | ||||
|     OctoPrint(config), | ||||
|     m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value), | ||||
|     m_username(config->opt_string("printhost_user")), | ||||
|     m_password(config->opt_string("printhost_password")) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| const char* PrusaLink::get_name() const { return "PrusaLink"; } | ||||
| 
 | ||||
| wxString PrusaLink::get_test_ok_msg() const | ||||
| { | ||||
|     return _(L("Connection to PrusaLink works correctly.")); | ||||
| } | ||||
| 
 | ||||
| wxString PrusaLink::get_test_failed_msg(wxString& msg) const | ||||
| { | ||||
|     return GUI::from_u8((boost::format("%s: %s") | ||||
|         % _utf8(L("Could not connect to PrusaLink")) | ||||
|         % std::string(msg.ToUTF8())).str()); | ||||
| } | ||||
| 
 | ||||
| bool PrusaLink::validate_version_text(const boost::optional<std::string>& version_text) const | ||||
| { | ||||
|     return version_text ? (boost::starts_with(*version_text, "PrusaLink") || boost::starts_with(*version_text, "OctoPrint")) : false; | ||||
| } | ||||
| 
 | ||||
| void PrusaLink::set_auth(Http& http) const | ||||
| { | ||||
|     switch (m_authorization_type) { | ||||
|     case atKeyPassword: | ||||
|         http.header("X-Api-Key", get_apikey()); | ||||
|         break; | ||||
|     case atUserPassword: | ||||
|         http.auth_digest(m_username, m_password); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     if (!get_cafile().empty()) { | ||||
|         http.ca_file(get_cafile()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,101 +0,0 @@ | |||
| #ifndef slic3r_OctoPrint_hpp_ | ||||
| #define slic3r_OctoPrint_hpp_ | ||||
| 
 | ||||
| #include <string> | ||||
| #include <wx/string.h> | ||||
| #include <boost/optional.hpp> | ||||
| 
 | ||||
| #include "PrintHost.hpp" | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class DynamicPrintConfig; | ||||
| class Http; | ||||
| 
 | ||||
| class OctoPrint : public PrintHost | ||||
| { | ||||
| public: | ||||
|     OctoPrint(DynamicPrintConfig *config); | ||||
|     ~OctoPrint() override = default; | ||||
| 
 | ||||
|     const char* get_name() const override; | ||||
| 
 | ||||
|     bool test(wxString &curl_msg) const override; | ||||
|     wxString get_test_ok_msg () const override; | ||||
|     wxString get_test_failed_msg (wxString &msg) const override; | ||||
|     bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override; | ||||
|     bool has_auto_discovery() const override { return true; } | ||||
|     bool can_test() const override { return true; } | ||||
|     PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } | ||||
|     std::string get_host() const override { return m_host; } | ||||
|     const std::string& get_apikey() const { return m_apikey; } | ||||
|     const std::string& get_cafile() const { return m_cafile; } | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool validate_version_text(const boost::optional<std::string> &version_text) const; | ||||
| 
 | ||||
| private: | ||||
|     std::string m_host; | ||||
|     std::string m_apikey; | ||||
|     std::string m_cafile; | ||||
|     bool        m_ssl_revoke_best_effort; | ||||
| 
 | ||||
|     virtual void set_auth(Http &http) const; | ||||
|     std::string make_url(const std::string &path) const; | ||||
| }; | ||||
| 
 | ||||
| class SL1Host: public OctoPrint | ||||
| { | ||||
| public: | ||||
|     SL1Host(DynamicPrintConfig *config); | ||||
|     ~SL1Host() override = default; | ||||
| 
 | ||||
|     const char* get_name() const override; | ||||
| 
 | ||||
|     wxString get_test_ok_msg() const override; | ||||
|     wxString get_test_failed_msg(wxString &msg) const override; | ||||
|     PrintHostPostUploadActions get_post_upload_actions() const override { return {}; } | ||||
| 
 | ||||
| protected: | ||||
|     bool validate_version_text(const boost::optional<std::string> &version_text) const override; | ||||
| 
 | ||||
| private: | ||||
|     void set_auth(Http &http) const override; | ||||
| 
 | ||||
|     // Host authorization type.
 | ||||
|     AuthorizationType m_authorization_type; | ||||
|     // username and password for HTTP Digest Authentization (RFC RFC2617)
 | ||||
|     std::string m_username; | ||||
|     std::string m_password; | ||||
| }; | ||||
| 
 | ||||
| class PrusaLink : public OctoPrint | ||||
| { | ||||
| public: | ||||
|     PrusaLink(DynamicPrintConfig* config); | ||||
|     ~PrusaLink() override = default; | ||||
| 
 | ||||
|     const char* get_name() const override; | ||||
| 
 | ||||
|     wxString get_test_ok_msg() const override; | ||||
|     wxString get_test_failed_msg(wxString& msg) const override; | ||||
|     PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } | ||||
| 
 | ||||
| protected: | ||||
|     bool validate_version_text(const boost::optional<std::string>& version_text) const override; | ||||
| 
 | ||||
| private: | ||||
|     void set_auth(Http& http) const override; | ||||
| 
 | ||||
|     // Host authorization type.
 | ||||
|     AuthorizationType m_authorization_type; | ||||
|     // username and password for HTTP Digest Authentization (RFC RFC2617)
 | ||||
|     std::string m_username; | ||||
|     std::string m_password; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,281 +0,0 @@ | |||
| #include "PrintHost.hpp" | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <thread> | ||||
| #include <exception> | ||||
| #include <boost/optional.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/filesystem.hpp> | ||||
| 
 | ||||
| #include <wx/string.h> | ||||
| #include <wx/app.h> | ||||
| #include <wx/arrstr.h> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "libslic3r/Channel.hpp" | ||||
| #include "OctoPrint.hpp" | ||||
| #include "Duet.hpp" | ||||
| #include "FlashAir.hpp" | ||||
| #include "AstroBox.hpp" | ||||
| #include "Repetier.hpp" | ||||
| #include "MKS.hpp" | ||||
| #include "../GUI/PrintHostDialogs.hpp" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| using boost::optional; | ||||
| using Slic3r::GUI::PrintHostQueueDialog; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| 
 | ||||
| PrintHost::~PrintHost() {} | ||||
| 
 | ||||
| PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) | ||||
| { | ||||
|     PrinterTechnology tech = ptFFF; | ||||
| 
 | ||||
|     { | ||||
|         const auto opt = config->option<ConfigOptionEnum<PrinterTechnology>>("printer_technology"); | ||||
|         if (opt != nullptr) { | ||||
|             tech = opt->value; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (tech == ptFFF) { | ||||
|         const auto opt = config->option<ConfigOptionEnum<PrintHostType>>("host_type"); | ||||
|         const auto host_type = opt != nullptr ? opt->value : htOctoPrint; | ||||
| 
 | ||||
|         switch (host_type) { | ||||
|             case htOctoPrint: return new OctoPrint(config); | ||||
|             case htDuet:      return new Duet(config); | ||||
|             case htFlashAir:  return new FlashAir(config); | ||||
|             case htAstroBox:  return new AstroBox(config); | ||||
|             case htRepetier:  return new Repetier(config); | ||||
|             case htPrusaLink: return new PrusaLink(config); | ||||
|             case htMKS:       return new MKS(config); | ||||
|             default:          return nullptr; | ||||
|         } | ||||
|     } else { | ||||
|         return new SL1Host(config); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const | ||||
| { | ||||
|     if (status != 0) { | ||||
|         auto wxbody = wxString::FromUTF8(body.data()); | ||||
|         return wxString::Format("HTTP %u: %s", status, wxbody); | ||||
|     } else { | ||||
|         return wxString::FromUTF8(error.data()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| struct PrintHostJobQueue::priv | ||||
| { | ||||
|     // XXX: comment on how bg thread works
 | ||||
| 
 | ||||
|     PrintHostJobQueue *q; | ||||
| 
 | ||||
|     Channel<PrintHostJob> channel_jobs; | ||||
|     Channel<size_t> channel_cancels; | ||||
|     size_t job_id = 0; | ||||
|     int prev_progress = -1; | ||||
|     fs::path source_to_remove; | ||||
| 
 | ||||
|     std::thread bg_thread; | ||||
|     bool bg_exit = false; | ||||
| 
 | ||||
|     PrintHostQueueDialog *queue_dialog; | ||||
| 
 | ||||
|     priv(PrintHostJobQueue *q) : q(q) {} | ||||
| 
 | ||||
|     void emit_progress(int progress); | ||||
|     void emit_error(wxString error); | ||||
|     void emit_cancel(size_t id); | ||||
|     void start_bg_thread(); | ||||
|     void stop_bg_thread(); | ||||
|     void bg_thread_main(); | ||||
|     void progress_fn(Http::Progress progress, bool &cancel); | ||||
|     void remove_source(const fs::path &path); | ||||
|     void remove_source(); | ||||
|     void perform_job(PrintHostJob the_job); | ||||
| }; | ||||
| 
 | ||||
| PrintHostJobQueue::PrintHostJobQueue(PrintHostQueueDialog *queue_dialog) | ||||
|     : p(new priv(this)) | ||||
| { | ||||
|     p->queue_dialog = queue_dialog; | ||||
| } | ||||
| 
 | ||||
| PrintHostJobQueue::~PrintHostJobQueue() | ||||
| { | ||||
|     if (p) { p->stop_bg_thread(); } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::emit_progress(int progress) | ||||
| { | ||||
|     auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, progress); | ||||
|     wxQueueEvent(queue_dialog, evt); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::emit_error(wxString error) | ||||
| { | ||||
|     auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_ERROR, queue_dialog->GetId(), job_id, std::move(error)); | ||||
|     wxQueueEvent(queue_dialog, evt); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::emit_cancel(size_t id) | ||||
| { | ||||
|     auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, queue_dialog->GetId(), id); | ||||
|     wxQueueEvent(queue_dialog, evt); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::start_bg_thread() | ||||
| { | ||||
|     if (bg_thread.joinable()) { return; } | ||||
| 
 | ||||
|     std::shared_ptr<priv> p2 = q->p; | ||||
|     bg_thread = std::thread([p2]() { | ||||
|         p2->bg_thread_main(); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::stop_bg_thread() | ||||
| { | ||||
|     if (bg_thread.joinable()) { | ||||
|         bg_exit = true; | ||||
|         channel_jobs.push(PrintHostJob()); // Push an empty job to wake up bg_thread in case it's sleeping
 | ||||
|         bg_thread.detach();                // Let the background thread go, it should exit on its own
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::bg_thread_main() | ||||
| { | ||||
|     // bg thread entry point
 | ||||
| 
 | ||||
|     try { | ||||
|         // Pick up jobs from the job channel:
 | ||||
|         while (! bg_exit) { | ||||
|             auto job = channel_jobs.pop();   // Sleeps in a cond var if there are no jobs
 | ||||
|             if (job.empty()) { | ||||
|                 // This happens when the thread is being stopped
 | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             source_to_remove = job.upload_data.source_path; | ||||
| 
 | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue/bg_thread: Received job: [%1%]: `%2%` -> `%3%`, cancelled: %4%") | ||||
|                 % job_id | ||||
|                 % job.upload_data.upload_path | ||||
|                 % job.printhost->get_host() | ||||
|                 % job.cancelled; | ||||
| 
 | ||||
|             if (! job.cancelled) { | ||||
|                 perform_job(std::move(job)); | ||||
|             } | ||||
| 
 | ||||
|             remove_source(); | ||||
|             job_id++; | ||||
|         } | ||||
|     } catch (const std::exception &e) { | ||||
|         emit_error(e.what()); | ||||
|     } | ||||
| 
 | ||||
|     // Cleanup leftover files, if any
 | ||||
|     remove_source(); | ||||
|     auto jobs = channel_jobs.lock_rw(); | ||||
|     for (const PrintHostJob &job : *jobs) { | ||||
|         remove_source(job.upload_data.source_path); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel) | ||||
| { | ||||
|     if (cancel) { | ||||
|         // When cancel is true from the start, Http indicates request has been cancelled
 | ||||
|         emit_cancel(job_id); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (bg_exit) { | ||||
|         cancel = true; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (channel_cancels.size_hint() > 0) { | ||||
|         // Lock both queues
 | ||||
|         auto cancels = channel_cancels.lock_rw(); | ||||
|         auto jobs = channel_jobs.lock_rw(); | ||||
| 
 | ||||
|         for (size_t cancel_id : *cancels) { | ||||
|             if (cancel_id == job_id) { | ||||
|                 cancel = true; | ||||
|             } else if (cancel_id > job_id) { | ||||
|                 const size_t idx = cancel_id - job_id - 1; | ||||
|                 if (idx < jobs->size()) { | ||||
|                     jobs->at(idx).cancelled = true; | ||||
|                     BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue: Job id %1% cancelled") % cancel_id; | ||||
|                     emit_cancel(cancel_id); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         cancels->clear(); | ||||
|     } | ||||
| 
 | ||||
|     if (! cancel) { | ||||
|         int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0; | ||||
|         if (gui_progress != prev_progress) { | ||||
|             emit_progress(gui_progress); | ||||
|             prev_progress = gui_progress; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::remove_source(const fs::path &path) | ||||
| { | ||||
|     if (! path.empty()) { | ||||
|         boost::system::error_code ec; | ||||
|         fs::remove(path, ec); | ||||
|         if (ec) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("PrintHostJobQueue: Error removing file `%1%`: %2%") % path % ec; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::remove_source() | ||||
| { | ||||
|     remove_source(source_to_remove); | ||||
|     source_to_remove.clear(); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job) | ||||
| { | ||||
|     emit_progress(0);   // Indicate the upload is starting
 | ||||
| 
 | ||||
|     bool success = the_job.printhost->upload(std::move(the_job.upload_data), | ||||
|         [this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); }, | ||||
|         [this](wxString error) { | ||||
|             emit_error(std::move(error)); | ||||
|         } | ||||
|     ); | ||||
| 
 | ||||
|     if (success) { | ||||
|         emit_progress(100); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::enqueue(PrintHostJob job) | ||||
| { | ||||
|     p->start_bg_thread(); | ||||
|     p->queue_dialog->append_job(job); | ||||
|     p->channel_jobs.push(std::move(job)); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::cancel(size_t id) | ||||
| { | ||||
|     p->channel_cancels.push(id); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,129 +0,0 @@ | |||
| #ifndef slic3r_PrintHost_hpp_ | ||||
| #define slic3r_PrintHost_hpp_ | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <set> | ||||
| #include <string> | ||||
| #include <functional> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| 
 | ||||
| #include <wx/string.h> | ||||
| 
 | ||||
| #include <libslic3r/enum_bitmask.hpp> | ||||
| #include "Http.hpp" | ||||
| 
 | ||||
| class wxArrayString; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class DynamicPrintConfig; | ||||
| 
 | ||||
| enum class PrintHostPostUploadAction { | ||||
|     None, | ||||
|     StartPrint, | ||||
|     StartSimulation | ||||
| }; | ||||
| using PrintHostPostUploadActions = enum_bitmask<PrintHostPostUploadAction>; | ||||
| ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction); | ||||
| 
 | ||||
| struct PrintHostUpload | ||||
| { | ||||
|     boost::filesystem::path source_path; | ||||
|     boost::filesystem::path upload_path; | ||||
|      | ||||
|     std::string group; | ||||
|      | ||||
|     PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None }; | ||||
| }; | ||||
| 
 | ||||
| class PrintHost | ||||
| { | ||||
| public: | ||||
|     virtual ~PrintHost(); | ||||
| 
 | ||||
|     typedef Http::ProgressFn ProgressFn; | ||||
|     typedef std::function<void(wxString /* error */)> ErrorFn; | ||||
| 
 | ||||
|     virtual const char* get_name() const = 0; | ||||
| 
 | ||||
|     virtual bool test(wxString &curl_msg) const = 0; | ||||
|     virtual wxString get_test_ok_msg () const = 0; | ||||
|     virtual wxString get_test_failed_msg (wxString &msg) const = 0; | ||||
|     virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const = 0; | ||||
|     virtual bool has_auto_discovery() const = 0; | ||||
|     virtual bool can_test() const = 0; | ||||
|     virtual PrintHostPostUploadActions get_post_upload_actions() 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: | ||||
|     virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| struct PrintHostJob | ||||
| { | ||||
|     PrintHostUpload upload_data; | ||||
|     std::unique_ptr<PrintHost> printhost; | ||||
|     bool cancelled = false; | ||||
| 
 | ||||
|     PrintHostJob() {} | ||||
|     PrintHostJob(const PrintHostJob&) = delete; | ||||
|     PrintHostJob(PrintHostJob &&other) | ||||
|         : upload_data(std::move(other.upload_data)) | ||||
|         , printhost(std::move(other.printhost)) | ||||
|         , cancelled(other.cancelled) | ||||
|     {} | ||||
| 
 | ||||
|     PrintHostJob(DynamicPrintConfig *config) | ||||
|         : printhost(PrintHost::get_print_host(config)) | ||||
|     {} | ||||
| 
 | ||||
|     PrintHostJob& operator=(const PrintHostJob&) = delete; | ||||
|     PrintHostJob& operator=(PrintHostJob &&other) | ||||
|     { | ||||
|         upload_data = std::move(other.upload_data); | ||||
|         printhost = std::move(other.printhost); | ||||
|         cancelled = other.cancelled; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     bool empty() const { return !printhost; } | ||||
|     operator bool() const { return !!printhost; } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| namespace GUI { class PrintHostQueueDialog; } | ||||
| 
 | ||||
| class PrintHostJobQueue | ||||
| { | ||||
| public: | ||||
|     PrintHostJobQueue(GUI::PrintHostQueueDialog *queue_dialog); | ||||
|     PrintHostJobQueue(const PrintHostJobQueue &) = delete; | ||||
|     PrintHostJobQueue(PrintHostJobQueue &&other) = delete; | ||||
|     ~PrintHostJobQueue(); | ||||
| 
 | ||||
|     PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete; | ||||
|     PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete; | ||||
| 
 | ||||
|     void enqueue(PrintHostJob job); | ||||
|     void cancel(size_t id); | ||||
| 
 | ||||
| private: | ||||
|     struct priv; | ||||
|     std::shared_ptr<priv> p; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,274 +0,0 @@ | |||
| #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 = upload_data.post_action == PrintHostPostUploadAction::StartPrint | ||||
|         ? make_url((boost::format("printer/job/%1%") % port).str()) | ||||
|         : make_url((boost::format("printer/model/%1%") % port).str()); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, group: %7%") | ||||
|         % name | ||||
|         % upload_data.source_path | ||||
|         % url | ||||
|         % upload_filename.string() | ||||
|         % upload_parent_path.string() | ||||
|         % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false") | ||||
|         % upload_data.group; | ||||
| 
 | ||||
|     auto http = Http::post(std::move(url)); | ||||
|     set_auth(http); | ||||
| 
 | ||||
|     if (! upload_data.group.empty() && upload_data.group != _utf8(L("Default"))) { | ||||
|         http.form_add("group", upload_data.group); | ||||
|     } | ||||
| 
 | ||||
|     if(upload_data.post_action == PrintHostPostUploadAction::StartPrint) { | ||||
|         http.form_add("name", upload_filename.string()); | ||||
|     } | ||||
| 
 | ||||
|     http.form_add("a", "upload") | ||||
|         .form_add_file("filename", upload_data.source_path.string(), upload_filename.string()) | ||||
|         .on_complete([&](std::string body, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; | ||||
|         }) | ||||
|         .on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
|             error_fn(format_error(body, error, status)); | ||||
|             res = false; | ||||
|         }) | ||||
|         .on_progress([&](Http::Progress progress, bool &cancel) { | ||||
|             prorgess_fn(std::move(progress), cancel); | ||||
|             if (cancel) { | ||||
|                 // Upload was canceled
 | ||||
|                 BOOST_LOG_TRIVIAL(info) << "Repetier: Upload canceled"; | ||||
|                 res = false; | ||||
|             } | ||||
|         }) | ||||
|         .perform_sync(); | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| bool Repetier::validate_version_text(const boost::optional<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([&](std::string body, unsigned) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got groups: %2%") % name % body; | ||||
| 
 | ||||
|             try { | ||||
|                 std::stringstream ss(body); | ||||
|                 pt::ptree ptree; | ||||
|                 pt::read_json(ss, ptree); | ||||
| 
 | ||||
|                 BOOST_FOREACH(boost::property_tree::ptree::value_type &v, ptree.get_child("groupNames.")) { | ||||
|                     if (v.second.data() == "#") { | ||||
|                         groups.push_back(_utf8(L("Default"))); | ||||
|                     } else { | ||||
|                         // Is it safe to assume that the data are utf-8 encoded?
 | ||||
|                         groups.push_back(GUI::from_u8(v.second.data())); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             catch (const std::exception &) { | ||||
|                 //msg = "Could not parse server response";
 | ||||
|                 res = false; | ||||
|             } | ||||
|         }) | ||||
|         .perform_sync(); | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| bool Repetier::get_printers(wxArrayString& printers) const | ||||
| { | ||||
|     const char *name = get_name(); | ||||
| 
 | ||||
|     bool res = true; | ||||
|     auto url = make_url("printer/list"); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << boost::format("%1%: List printers at: %2%") % name % url; | ||||
| 
 | ||||
|     auto http = Http::get(std::move(url)); | ||||
|     set_auth(http); | ||||
|      | ||||
|     http.on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error listing printers: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; | ||||
|             res = false; | ||||
|         }) | ||||
|         .on_complete([&](std::string body, unsigned http_status) { | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got printers: %2%, HTTP status: %3%") % name % body % http_status; | ||||
|              | ||||
|             if (http_status != 200) | ||||
|                 throw HostNetworkError(GUI::format(_L("HTTP status: %1%\nMessage body: \"%2%\""), http_status, body)); | ||||
| 
 | ||||
|             std::stringstream ss(body); | ||||
|             pt::ptree ptree; | ||||
|             try { | ||||
|                 pt::read_json(ss, ptree); | ||||
|             } catch (const pt::ptree_error &err) { | ||||
|                 throw HostNetworkError(GUI::format(_L("Parsing of host response failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what())); | ||||
|             } | ||||
|              | ||||
|             const auto error = ptree.get_optional<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; | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,51 +0,0 @@ | |||
| #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 override; | ||||
| 
 | ||||
|     bool test(wxString &curl_msg) const override; | ||||
|     wxString get_test_ok_msg () const override; | ||||
|     wxString get_test_failed_msg (wxString &msg) const override; | ||||
|     bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override; | ||||
|     bool has_auto_discovery() const override { return false; } | ||||
|     bool can_test() const override { return true; } | ||||
|     PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } | ||||
|     bool supports_multiple_printers() const override { return true; } | ||||
|     std::string get_host() const override { return host; } | ||||
|      | ||||
|     bool get_groups(wxArrayString &groups) const override; | ||||
|     bool get_printers(wxArrayString &printers) const override; | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool validate_version_text(const boost::optional<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 | ||||
|  | @ -1,504 +0,0 @@ | |||
| #include "Serial.hpp" | ||||
| 
 | ||||
| #include "libslic3r/Exception.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
| #include <fstream> | ||||
| #include <exception> | ||||
| #include <stdexcept> | ||||
| 
 | ||||
| #include <boost/algorithm/string/predicate.hpp> | ||||
| #include <boost/filesystem.hpp> | ||||
| #include <boost/format.hpp> | ||||
| #include <boost/optional.hpp> | ||||
| 
 | ||||
| #if _WIN32 | ||||
| 	#include <Windows.h> | ||||
| 	#include <Setupapi.h> | ||||
| 	#include <initguid.h> | ||||
| 	#include <devguid.h> | ||||
| 	#include <regex> | ||||
| 	// Undefine min/max macros incompatible with the standard library
 | ||||
| 	// For example, std::numeric_limits<std::streamsize>::max()
 | ||||
| 	// produces some weird errors
 | ||||
| 	#ifdef min | ||||
| 	#undef min | ||||
| 	#endif | ||||
| 	#ifdef max | ||||
| 	#undef max | ||||
| 	#endif | ||||
| 	#include "boost/nowide/convert.hpp" | ||||
| 	#pragma comment(lib, "user32.lib") | ||||
| #elif __APPLE__ | ||||
| 	#include <CoreFoundation/CoreFoundation.h> | ||||
| 	#include <CoreFoundation/CFString.h> | ||||
| 	#include <IOKit/IOKitLib.h> | ||||
| 	#include <IOKit/serial/IOSerialKeys.h> | ||||
| 	#include <IOKit/serial/ioss.h> | ||||
| 	#include <sys/syslimits.h> | ||||
| #endif | ||||
| 
 | ||||
| #ifndef _WIN32 | ||||
| 	#include <sys/ioctl.h> | ||||
| 	#include <sys/time.h> | ||||
| 	#include <sys/unistd.h> | ||||
| 	#include <sys/select.h> | ||||
| #endif | ||||
| 
 | ||||
| #if defined(__APPLE__) || defined(__OpenBSD__) | ||||
| 	#include <termios.h> | ||||
| #elif defined __linux__ | ||||
| 	#include <fcntl.h> | ||||
| 	#include <asm-generic/ioctls.h> | ||||
| #endif | ||||
| 
 | ||||
| using boost::optional; | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace Utils { | ||||
| 
 | ||||
| static bool looks_like_printer(const std::string &friendly_name) | ||||
| { | ||||
| 	return friendly_name.find("Original Prusa") != std::string::npos; | ||||
| } | ||||
| 
 | ||||
| #if _WIN32 | ||||
| void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi) | ||||
| { | ||||
| 	unsigned vid, pid; | ||||
| 	std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*"); | ||||
| 	std::smatch matches; | ||||
| 	if (std::regex_match(hardware_id, matches, pattern)) { | ||||
| 		vid = std::stoul(matches[1].str(), 0, 16); | ||||
| 		pid = std::stoul(matches[2].str(), 0, 16); | ||||
| 		spi.id_vendor = vid; | ||||
| 		spi.id_product = pid; | ||||
| 	} | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #ifdef __linux__ | ||||
| optional<std::string> sysfs_tty_prop(const std::string &tty_dev, const std::string &name) | ||||
| { | ||||
| 	const auto prop_path = (boost::format("/sys/class/tty/%1%/device/../%2%") % tty_dev % name).str(); | ||||
| 	std::ifstream file(prop_path); | ||||
| 	std::string res; | ||||
| 
 | ||||
| 	std::getline(file, res); | ||||
| 	if (file.good()) { return res; } | ||||
| 	else { return boost::none; } | ||||
| } | ||||
| 
 | ||||
| optional<unsigned long> sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name) | ||||
| { | ||||
| 	auto prop = sysfs_tty_prop(tty_dev, name); | ||||
| 	if (!prop) { return boost::none; } | ||||
| 
 | ||||
| 	try { return std::stoul(*prop, 0, 16); } | ||||
| 	catch (const std::exception&) { return boost::none; } | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| std::vector<SerialPortInfo> scan_serial_ports_extended() | ||||
| { | ||||
| 	std::vector<SerialPortInfo> output; | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| 	SP_DEVINFO_DATA devInfoData = { 0 }; | ||||
| 	devInfoData.cbSize = sizeof(devInfoData); | ||||
| 	// Get the tree containing the info for the ports.
 | ||||
| 	HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, 0, nullptr, DIGCF_PRESENT); | ||||
| 	if (hDeviceInfo != INVALID_HANDLE_VALUE) { | ||||
| 		// Iterate over all the devices in the tree.
 | ||||
| 		for (int nDevice = 0; SetupDiEnumDeviceInfo(hDeviceInfo, nDevice, &devInfoData); ++ nDevice) { | ||||
| 			SerialPortInfo port_info; | ||||
| 			// Get the registry key which stores the ports settings.
 | ||||
| 			HKEY hDeviceKey = SetupDiOpenDevRegKey(hDeviceInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE); | ||||
| 			if (hDeviceKey) { | ||||
| 				// Read in the name of the port.
 | ||||
| 				wchar_t pszPortName[4096]; | ||||
| 				DWORD dwSize = sizeof(pszPortName); | ||||
| 				DWORD dwType = 0; | ||||
| 				if (RegQueryValueEx(hDeviceKey, L"PortName", NULL, &dwType, (LPBYTE)pszPortName, &dwSize) == ERROR_SUCCESS) | ||||
| 					port_info.port = boost::nowide::narrow(pszPortName); | ||||
| 				RegCloseKey(hDeviceKey); | ||||
| 				if (port_info.port.empty()) | ||||
| 					continue; | ||||
| 			} | ||||
| 
 | ||||
| 			// Find the size required to hold the device info.
 | ||||
| 			DWORD regDataType; | ||||
| 			DWORD reqSize = 0; | ||||
| 			SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize); | ||||
| 			std::vector<wchar_t> hardware_id(reqSize > 1 ? reqSize : 1); | ||||
| 			// Now store it in a buffer.
 | ||||
| 			if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, (BYTE*)hardware_id.data(), reqSize, nullptr)) | ||||
| 				continue; | ||||
| 			parse_hardware_id(boost::nowide::narrow(hardware_id.data()), port_info); | ||||
| 
 | ||||
| 			// Find the size required to hold the friendly name.
 | ||||
| 			reqSize = 0; | ||||
| 			SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize); | ||||
| 			std::vector<wchar_t> friendly_name; | ||||
| 			friendly_name.reserve(reqSize > 1 ? reqSize : 1); | ||||
| 			// Now store it in a buffer.
 | ||||
| 			if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, (BYTE*)friendly_name.data(), reqSize, nullptr)) { | ||||
| 				port_info.friendly_name = port_info.port; | ||||
| 			} else { | ||||
| 				port_info.friendly_name = boost::nowide::narrow(friendly_name.data()); | ||||
| 				port_info.is_printer = looks_like_printer(port_info.friendly_name); | ||||
| 			} | ||||
| 			output.emplace_back(std::move(port_info)); | ||||
| 		} | ||||
| 	} | ||||
| #elif __APPLE__ | ||||
| 	// inspired by https://sigrok.org/wiki/Libserialport
 | ||||
| 	CFMutableDictionaryRef classes = IOServiceMatching(kIOSerialBSDServiceValue); | ||||
| 	if (classes != 0) { | ||||
| 		io_iterator_t iter; | ||||
| 		if (IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iter) == KERN_SUCCESS) { | ||||
| 			io_object_t port; | ||||
| 			while ((port = IOIteratorNext(iter)) != 0) { | ||||
| 				CFTypeRef cf_property = IORegistryEntryCreateCFProperty(port, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0); | ||||
| 				if (cf_property) { | ||||
| 					char path[PATH_MAX]; | ||||
| 					Boolean result = CFStringGetCString((CFStringRef)cf_property, path, sizeof(path), kCFStringEncodingUTF8); | ||||
| 					CFRelease(cf_property); | ||||
| 					if (result) { | ||||
| 						SerialPortInfo port_info; | ||||
| 						port_info.port = path; | ||||
| 
 | ||||
| 						// Attempt to read out the device friendly name
 | ||||
| 						if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane, | ||||
| 						         CFSTR("USB Interface Name"), kCFAllocatorDefault, | ||||
| 						         kIORegistryIterateRecursively | kIORegistryIterateParents)) || | ||||
| 						    (cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane, | ||||
| 						         CFSTR("USB Product Name"), kCFAllocatorDefault, | ||||
| 						         kIORegistryIterateRecursively | kIORegistryIterateParents)) || | ||||
| 						    (cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane, | ||||
| 						         CFSTR("Product Name"), kCFAllocatorDefault, | ||||
| 						         kIORegistryIterateRecursively | kIORegistryIterateParents)) || | ||||
| 						    (cf_property = IORegistryEntryCreateCFProperty(port,  | ||||
| 						         CFSTR(kIOTTYDeviceKey), kCFAllocatorDefault, 0))) { | ||||
| 							// Description limited to 127 char, anything longer would not be user friendly anyway.
 | ||||
| 							char description[128]; | ||||
| 							if (CFStringGetCString((CFStringRef)cf_property, description, sizeof(description), kCFStringEncodingUTF8)) { | ||||
| 								port_info.friendly_name = std::string(description) + " (" + port_info.port + ")"; | ||||
| 								port_info.is_printer = looks_like_printer(port_info.friendly_name); | ||||
| 							} | ||||
| 							CFRelease(cf_property); | ||||
| 						} | ||||
| 						if (port_info.friendly_name.empty()) | ||||
| 							port_info.friendly_name = port_info.port; | ||||
| 
 | ||||
| 						// Attempt to read out the VID & PID
 | ||||
| 						int vid, pid; | ||||
| 						auto cf_vendor = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idVendor"), | ||||
| 							kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); | ||||
| 						auto cf_product = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idProduct"), | ||||
| 							kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); | ||||
| 						if (cf_vendor && cf_product) { | ||||
| 							if (CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberIntType, &vid) && | ||||
| 								CFNumberGetValue((CFNumberRef)cf_product, kCFNumberIntType, &pid)) { | ||||
| 								port_info.id_vendor = vid; | ||||
| 								port_info.id_product = pid; | ||||
| 							} | ||||
| 						} | ||||
| 						if (cf_vendor) { CFRelease(cf_vendor); } | ||||
| 						if (cf_product) { CFRelease(cf_product); } | ||||
| 
 | ||||
| 						output.emplace_back(std::move(port_info)); | ||||
| 					} | ||||
| 				} | ||||
| 				IOObjectRelease(port); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| #else | ||||
|     // UNIX / Linux
 | ||||
|     std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" }; | ||||
|     for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) { | ||||
|         std::string name = dir_entry.path().filename().string(); | ||||
|         for (const char *prefix : prefixes) { | ||||
|             if (boost::starts_with(name, prefix)) { | ||||
|                 const auto path = dir_entry.path().string(); | ||||
|                 SerialPortInfo spi; | ||||
|                 spi.port = path; | ||||
| #ifdef __linux__ | ||||
| 				auto friendly_name = sysfs_tty_prop(name, "product"); | ||||
| 				if (friendly_name) { | ||||
| 					spi.is_printer = looks_like_printer(*friendly_name); | ||||
| 					spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str(); | ||||
| 				} else { | ||||
| 					spi.friendly_name = path; | ||||
| 				} | ||||
| 				auto vid = sysfs_tty_prop_hex(name, "idVendor"); | ||||
| 				auto pid = sysfs_tty_prop_hex(name, "idProduct"); | ||||
| 				if (vid && pid) { | ||||
| 					spi.id_vendor = *vid; | ||||
| 					spi.id_product = *pid; | ||||
| 				} | ||||
| #else | ||||
|                 spi.friendly_name = path; | ||||
| #endif | ||||
|                 output.emplace_back(std::move(spi)); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     output.erase(std::remove_if(output.begin(), output.end(),  | ||||
|         [](const SerialPortInfo &info) { | ||||
|             return boost::starts_with(info.port, "Bluetooth") || boost::starts_with(info.port, "FireFly");  | ||||
|         }), | ||||
|         output.end()); | ||||
|     return output; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> scan_serial_ports() | ||||
| { | ||||
| 	std::vector<SerialPortInfo> ports = scan_serial_ports_extended(); | ||||
| 	std::vector<std::string> output; | ||||
| 	output.reserve(ports.size()); | ||||
| 	for (const SerialPortInfo &spi : ports) | ||||
| 		output.emplace_back(std::move(spi.port)); | ||||
| 	return output; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| // Class Serial
 | ||||
| 
 | ||||
| namespace asio = boost::asio; | ||||
| using boost::system::error_code; | ||||
| 
 | ||||
| Serial::Serial(asio::io_service& io_service) : | ||||
| 	asio::serial_port(io_service) | ||||
| {} | ||||
| 
 | ||||
| Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) : | ||||
| 	asio::serial_port(io_service, name) | ||||
| { | ||||
| 	set_baud_rate(baud_rate); | ||||
| } | ||||
| 
 | ||||
| Serial::~Serial() {} | ||||
| 
 | ||||
| void Serial::set_baud_rate(unsigned baud_rate) | ||||
| { | ||||
| 	try { | ||||
| 		// This does not support speeds > 115200
 | ||||
| 		set_option(boost::asio::serial_port_base::baud_rate(baud_rate)); | ||||
| 	} catch (boost::system::system_error &) { | ||||
| 		auto handle = native_handle(); | ||||
| 
 | ||||
| 		auto handle_errno = [](int retval) { | ||||
| 			if (retval != 0) { | ||||
| 				throw Slic3r::RuntimeError( | ||||
| 					(boost::format("Could not set baud rate: %1%") % strerror(errno)).str() | ||||
| 				); | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| #if __APPLE__ | ||||
| 		termios ios; | ||||
| 		handle_errno(::tcgetattr(handle, &ios)); | ||||
| 		handle_errno(::cfsetspeed(&ios, baud_rate)); | ||||
| 		speed_t newSpeed = baud_rate; | ||||
| 		handle_errno(::ioctl(handle, IOSSIOSPEED, &newSpeed)); | ||||
| 		handle_errno(::tcsetattr(handle, TCSANOW, &ios)); | ||||
| #elif __linux__ | ||||
| 
 | ||||
| 		/* The following definitions are kindly borrowed from:
 | ||||
| 			/usr/include/asm-generic/termbits.h | ||||
| 			Unfortunately we cannot just include that one because | ||||
| 			it would redefine the "struct termios" already defined | ||||
| 			the <termios.h> already included by Boost.ASIO. */ | ||||
| #define K_NCCS 19 | ||||
| 		struct termios2 { | ||||
| 			tcflag_t c_iflag; | ||||
| 			tcflag_t c_oflag; | ||||
| 			tcflag_t c_cflag; | ||||
| 			tcflag_t c_lflag; | ||||
| 			cc_t c_line; | ||||
| 			cc_t c_cc[K_NCCS]; | ||||
| 			speed_t c_ispeed; | ||||
| 			speed_t c_ospeed; | ||||
| 		}; | ||||
| #define BOTHER CBAUDEX | ||||
| 
 | ||||
| 		termios2 ios; | ||||
| 		handle_errno(::ioctl(handle, TCGETS2, &ios)); | ||||
| 		ios.c_ispeed = ios.c_ospeed = baud_rate; | ||||
| 		ios.c_cflag &= ~CBAUD; | ||||
| 		ios.c_cflag |= BOTHER | CLOCAL | CREAD; | ||||
| 		ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
 | ||||
| 		ios.c_cc[VTIME] = 1; | ||||
| 		handle_errno(::ioctl(handle, TCSETS2, &ios)); | ||||
| 
 | ||||
| #elif __OpenBSD__ | ||||
| 		struct termios ios; | ||||
| 		handle_errno(::tcgetattr(handle, &ios)); | ||||
| 		handle_errno(::cfsetspeed(&ios, baud_rate)); | ||||
| 		handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios)); | ||||
| #else | ||||
| 		throw Slic3r::RuntimeError("Custom baud rates are not currently supported on this OS"); | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*
 | ||||
| void Serial::set_DTR(bool on) | ||||
| { | ||||
| 	auto handle = native_handle(); | ||||
| #if defined(_WIN32) && !defined(__SYMBIAN32__) | ||||
| 	if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) { | ||||
| 		throw Slic3r::RuntimeError("Could not set serial port DTR"); | ||||
| 	} | ||||
| #else | ||||
| 	int status; | ||||
| 	if (::ioctl(handle, TIOCMGET, &status) == 0) { | ||||
| 		on ? status |= TIOCM_DTR : status &= ~TIOCM_DTR; | ||||
| 		if (::ioctl(handle, TIOCMSET, &status) == 0) { | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	throw Slic3r::RuntimeError( | ||||
| 		(boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str() | ||||
| 	); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void Serial::reset_line_num() | ||||
| { | ||||
| 	// See https://github.com/MarlinFirmware/Marlin/wiki/M110
 | ||||
| 	write_string("M110 N0\n"); | ||||
| 	m_line_num = 0; | ||||
| } | ||||
| 
 | ||||
| bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) | ||||
| { | ||||
| 	auto& io_service = | ||||
| #if BOOST_VERSION >= 107000 | ||||
| 		//FIXME this is most certainly wrong!
 | ||||
| 		(boost::asio::io_context&)this->get_executor().context(); | ||||
|  #else | ||||
| 		this->get_io_service(); | ||||
| #endif | ||||
| 	asio::deadline_timer timer(io_service); | ||||
| 	char c = 0; | ||||
| 	bool fail = false; | ||||
| 
 | ||||
| 	while (true) { | ||||
| 		io_service.reset(); | ||||
| 
 | ||||
| 		asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) { | ||||
| 			if (ec || size == 0) { | ||||
| 				fail = true; | ||||
| 				ec = read_ec;   // FIXME: only if operation not aborted
 | ||||
| 			} | ||||
| 			timer.cancel();   // FIXME: ditto
 | ||||
| 		}); | ||||
| 
 | ||||
| 		if (timeout > 0) { | ||||
| 			timer.expires_from_now(boost::posix_time::milliseconds(timeout)); | ||||
| 			timer.async_wait([&](const error_code &ec) { | ||||
| 				// Ignore timer aborts
 | ||||
| 				if (!ec) { | ||||
| 					fail = true; | ||||
| 					this->cancel(); | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		io_service.run(); | ||||
| 
 | ||||
| 		if (fail) { | ||||
| 			return false; | ||||
| 		} else if (c != '\n') { | ||||
| 			line += c; | ||||
| 		} else { | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void Serial::printer_setup() | ||||
| { | ||||
| 	printer_reset(); | ||||
| 	write_string("\r\r\r\r\r\r\r\r\r\r");    // Gets rid of line noise, if any
 | ||||
| } | ||||
| 
 | ||||
| size_t Serial::write_string(const std::string &str) | ||||
| { | ||||
| 	// TODO: might be wise to timeout here as well
 | ||||
| 	return asio::write(*this, asio::buffer(str)); | ||||
| } | ||||
| 
 | ||||
| bool Serial::printer_ready_wait(unsigned retries, unsigned timeout) | ||||
| { | ||||
| 	std::string line; | ||||
| 	error_code ec; | ||||
| 
 | ||||
| 	for (; retries > 0; retries--) { | ||||
| 		reset_line_num(); | ||||
| 
 | ||||
| 		while (read_line(timeout, line, ec)) { | ||||
| 			if (line == "ok") { | ||||
| 				return true; | ||||
| 			} | ||||
| 			line.clear(); | ||||
| 		} | ||||
| 
 | ||||
| 		line.clear(); | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| size_t Serial::printer_write_line(const std::string &line, unsigned line_num) | ||||
| { | ||||
| 	const auto formatted_line = Utils::Serial::printer_format_line(line, line_num); | ||||
| 	return write_string(formatted_line); | ||||
| } | ||||
| 
 | ||||
| size_t Serial::printer_write_line(const std::string &line) | ||||
| { | ||||
| 	m_line_num++; | ||||
| 	return printer_write_line(line, m_line_num); | ||||
| } | ||||
| 
 | ||||
| void Serial::printer_reset() | ||||
| { | ||||
| 	this->set_DTR(false); | ||||
| 	std::this_thread::sleep_for(std::chrono::milliseconds(200)); | ||||
| 	this->set_DTR(true); | ||||
| 	std::this_thread::sleep_for(std::chrono::milliseconds(200)); | ||||
| 	this->set_DTR(false); | ||||
| 	std::this_thread::sleep_for(std::chrono::milliseconds(1000)); | ||||
| } | ||||
| 
 | ||||
| std::string Serial::printer_format_line(const std::string &line, unsigned line_num) | ||||
| { | ||||
| 	const auto line_num_str = std::to_string(line_num); | ||||
| 
 | ||||
| 	unsigned checksum = 'N'; | ||||
| 	for (auto c : line_num_str) { checksum ^= c; } | ||||
| 	checksum ^= ' '; | ||||
| 	for (auto c : line) { checksum ^= c; } | ||||
| 
 | ||||
| 	return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str(); | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| } // namespace Utils
 | ||||
| } // namespace Slic3r
 | ||||
|  | @ -1,97 +0,0 @@ | |||
| #ifndef slic3r_GUI_Utils_Serial_hpp_ | ||||
| #define slic3r_GUI_Utils_Serial_hpp_ | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include <boost/system/error_code.hpp> | ||||
| #include <boost/asio.hpp> | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace Utils { | ||||
| 
 | ||||
| struct SerialPortInfo { | ||||
| 	std::string port; | ||||
| 	unsigned    id_vendor = -1; | ||||
| 	unsigned    id_product = -1; | ||||
| 	std::string friendly_name; | ||||
| 	bool        is_printer = false; | ||||
| 
 | ||||
| 	SerialPortInfo() {} | ||||
| 	SerialPortInfo(std::string port) : port(port), friendly_name(std::move(port)) {} | ||||
| 
 | ||||
| 	bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; } | ||||
| }; | ||||
| 
 | ||||
| inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2) | ||||
| { | ||||
| 	return | ||||
| 		sp1.port       == sp2.port       && | ||||
| 		sp1.id_vendor  == sp2.id_vendor  && | ||||
| 		sp1.id_product == sp2.id_product && | ||||
| 		sp1.is_printer == sp2.is_printer; | ||||
| } | ||||
| 
 | ||||
| extern std::vector<std::string> 	scan_serial_ports(); | ||||
| extern std::vector<SerialPortInfo> 	scan_serial_ports_extended(); | ||||
| 
 | ||||
| 
 | ||||
| class Serial : public boost::asio::serial_port | ||||
| { | ||||
| public: | ||||
| 	Serial(boost::asio::io_service &io_service); | ||||
| 	Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate); | ||||
| 	Serial(const Serial &) = delete; | ||||
| 	Serial &operator=(const Serial &) = delete; | ||||
| 	~Serial(); | ||||
| 
 | ||||
| 	void set_baud_rate(unsigned baud_rate); | ||||
| 
 | ||||
| 	// The Serial implementation is currently in disarray and therefore commented out.
 | ||||
| 	// The boost implementation seems to have several problems, such as lack of support
 | ||||
| 	// for custom baud rates, few weird implementation bugs and a history of API breakages.
 | ||||
| 	// It's questionable whether it solves more problems than causes. Probably not.
 | ||||
| 	// TODO: Custom implementation not based on asio.
 | ||||
| 	//
 | ||||
| 	// As of now, this class is only kept for the purpose of rebooting AVR109,
 | ||||
| 	// see FirmwareDialog::priv::avr109_reboot()
 | ||||
| 
 | ||||
| /*
 | ||||
| 	void set_DTR(bool on); | ||||
| 
 | ||||
| 	// Resets the line number both internally as well as with the firmware using M110
 | ||||
| 	void reset_line_num(); | ||||
| 
 | ||||
| 	// Reads a line or times out, the timeout is in milliseconds
 | ||||
| 	bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec); | ||||
| 
 | ||||
| 	// Perform an initial setup for communicating with a printer
 | ||||
| 	void printer_setup(); | ||||
| 
 | ||||
| 	// Write data from a string
 | ||||
| 	size_t write_string(const std::string &str); | ||||
| 
 | ||||
| 	// Attempts to reset the line numer and waits until the printer says "ok"
 | ||||
| 	bool printer_ready_wait(unsigned retries, unsigned timeout); | ||||
| 
 | ||||
| 	// Write Marlin-formatted line, with a line number and a checksum
 | ||||
| 	size_t printer_write_line(const std::string &line, unsigned line_num); | ||||
| 
 | ||||
| 	// Same as above, but with internally-managed line number
 | ||||
| 	size_t printer_write_line(const std::string &line); | ||||
| 
 | ||||
| 	// Toggles DTR to reset the printer
 | ||||
| 	void printer_reset(); | ||||
| 
 | ||||
| 	// Formats a line Marlin-style, ie. with a sequential number and a checksum
 | ||||
| 	static std::string printer_format_line(const std::string &line, unsigned line_num); | ||||
| private: | ||||
| 	unsigned m_line_num = 0; | ||||
| */ | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } // Utils
 | ||||
| } // Slic3r
 | ||||
| 
 | ||||
| #endif /* slic3r_GUI_Utils_Serial_hpp_ */ | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 SoftFever
						SoftFever