diff --git a/resources/profiles/Prusa.json b/resources/profiles/Prusa.json
index 5eda29198c..41d5be7b4e 100644
--- a/resources/profiles/Prusa.json
+++ b/resources/profiles/Prusa.json
@@ -1,9 +1,13 @@
{
"name": "Prusa",
- "version": "01.06.04.00",
+ "version": "01.06.06.00",
"force_update": "0",
"description": "Prusa configurations",
"machine_model_list": [
+ {
+ "name": "Prusa MK4",
+ "sub_path": "machine/Prusa MK4.json"
+ },
{
"name": "Prusa MK3S",
"sub_path": "machine/Prusa MK3S.json"
@@ -22,6 +26,10 @@
"name": "0.20mm Standard @MK3S",
"sub_path": "process/0.20mm Standard @MK3S.json"
},
+ {
+ "name": "0.20mm Standard @MK4",
+ "sub_path": "process/0.20mm Standard @MK4.json"
+ },
{
"name": "0.20mm Standard @MINI",
"sub_path": "process/0.20mm Standard @MINI.json"
@@ -114,6 +122,10 @@
"name": "Prusa MK3S 0.4 nozzle",
"sub_path": "machine/Prusa MK3S 0.4 nozzle.json"
},
+ {
+ "name": "Prusa MK4 0.4 nozzle",
+ "sub_path": "machine/Prusa MK4 0.4 nozzle.json"
+ },
{
"name": "Prusa MINI 0.4 nozzle",
"sub_path": "machine/Prusa MINI 0.4 nozzle.json"
diff --git a/resources/profiles/Prusa/Prusa MK4_cover.png b/resources/profiles/Prusa/Prusa MK4_cover.png
new file mode 100644
index 0000000000..ccdbceb909
Binary files /dev/null and b/resources/profiles/Prusa/Prusa MK4_cover.png differ
diff --git a/resources/profiles/Prusa/filament/Prusa Generic ABS.json b/resources/profiles/Prusa/filament/Prusa Generic ABS.json
index 3953a58e9c..466fbcce29 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic ABS.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic ABS.json
@@ -14,6 +14,7 @@
],
"compatible_printers": [
"Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
diff --git a/resources/profiles/Prusa/filament/Prusa Generic ASA.json b/resources/profiles/Prusa/filament/Prusa Generic ASA.json
index ff83c0ec4e..7f68596fe1 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic ASA.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic ASA.json
@@ -14,6 +14,7 @@
],
"compatible_printers": [
"Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
diff --git a/resources/profiles/Prusa/filament/Prusa Generic PA-CF.json b/resources/profiles/Prusa/filament/Prusa Generic PA-CF.json
index 2eaa2bf34d..3ace579a36 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic PA-CF.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic PA-CF.json
@@ -19,7 +19,8 @@
"8"
],
"compatible_printers": [
- "Prusa MK3S 0.4 nozzle",
+ "Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
\ No newline at end of file
diff --git a/resources/profiles/Prusa/filament/Prusa Generic PA.json b/resources/profiles/Prusa/filament/Prusa Generic PA.json
index 13991aad2c..dfefb4bbbf 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic PA.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic PA.json
@@ -16,7 +16,8 @@
"12"
],
"compatible_printers": [
- "Prusa MK3S 0.4 nozzle",
+ "Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
\ No newline at end of file
diff --git a/resources/profiles/Prusa/filament/Prusa Generic PC.json b/resources/profiles/Prusa/filament/Prusa Generic PC.json
index aa8556bcca..edae14c5a7 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic PC.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic PC.json
@@ -13,7 +13,8 @@
"0.94"
],
"compatible_printers": [
- "Prusa MK3S 0.4 nozzle",
+ "Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
\ No newline at end of file
diff --git a/resources/profiles/Prusa/filament/Prusa Generic PETG.json b/resources/profiles/Prusa/filament/Prusa Generic PETG.json
index a338b24f23..6a68e00329 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic PETG.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic PETG.json
@@ -44,6 +44,7 @@
],
"compatible_printers": [
"Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
diff --git a/resources/profiles/Prusa/filament/Prusa Generic PLA-CF.json b/resources/profiles/Prusa/filament/Prusa Generic PLA-CF.json
index 7651c3afc5..8252c86fc6 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic PLA-CF.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic PLA-CF.json
@@ -19,7 +19,8 @@
"7"
],
"compatible_printers": [
- "Prusa MK3S 0.4 nozzle",
+ "Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
\ No newline at end of file
diff --git a/resources/profiles/Prusa/filament/Prusa Generic PLA.json b/resources/profiles/Prusa/filament/Prusa Generic PLA.json
index bf58f0271a..d074f3f71a 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic PLA.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic PLA.json
@@ -17,6 +17,7 @@
],
"compatible_printers": [
"Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
diff --git a/resources/profiles/Prusa/filament/Prusa Generic PVA.json b/resources/profiles/Prusa/filament/Prusa Generic PVA.json
index b38afd0291..86db19e8ed 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic PVA.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic PVA.json
@@ -20,6 +20,7 @@
],
"compatible_printers": [
"Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
diff --git a/resources/profiles/Prusa/filament/Prusa Generic TPU.json b/resources/profiles/Prusa/filament/Prusa Generic TPU.json
index f92b633cc2..96e1382478 100644
--- a/resources/profiles/Prusa/filament/Prusa Generic TPU.json
+++ b/resources/profiles/Prusa/filament/Prusa Generic TPU.json
@@ -11,6 +11,7 @@
],
"compatible_printers": [
"Prusa MK3S 0.4 nozzle",
+ "Prusa MK4 0.4 nozzle",
"Prusa MINI 0.4 nozzle"
]
}
diff --git a/resources/profiles/Prusa/machine/Prusa MK4 0.4 nozzle.json b/resources/profiles/Prusa/machine/Prusa MK4 0.4 nozzle.json
new file mode 100644
index 0000000000..d623cb2c4e
--- /dev/null
+++ b/resources/profiles/Prusa/machine/Prusa MK4 0.4 nozzle.json
@@ -0,0 +1,35 @@
+{
+ "type": "machine",
+ "setting_id": "GM003",
+ "name": "Prusa MK4 0.4 nozzle",
+ "from": "system",
+ "instantiation": "true",
+ "inherits": "fdm_machine_common",
+ "printer_model": "Prusa MK4",
+ "default_filament_profile": [
+ "Prusa Generic PLA"
+ ],
+ "default_print_profile": "0.20mm Standard @MK4",
+ "nozzle_diameter": [
+ "0.4"
+ ],
+ "bed_exclude_area": [
+ "0x0"
+ ],
+ "printable_area": [
+ "0x0",
+ "250x0",
+ "250x210",
+ "0x210"
+ ],
+ "printable_height": "220",
+ "machine_start_gcode": "G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[bed_temperature_initial_layer_single] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[bed_temperature_initial_layer_single] ; wait for bed temp\nM204 T1250 ; set travel acceleration\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM204 T[machine_max_acceleration_travel] ; restore travel acceleration\nM104 S[nozzle_temperature_initial_layer] ; set extruder temp\nG92 E0\nG1 Y-2 X179 F2400\nG1 Z3 F720\nM109 S[nozzle_temperature_initial_layer] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110 E8 F900\nG1 X40 E10 F700\nG92 E0\n\nM221 S95 ; set flow",
+ "machine_end_gcode": "G1 E-1 F2100 ; retract\n{if max_layer_z < 210}G1 Z{min(max_layer_z+2, 210)} F720 ; Move print head up{endif}\nG1 X178 Y178 F4200 ; park print head\n{if max_layer_z < 210}G1 Z{min(max_layer_z+30, 210)} F720 ; Move print head further up{endif}\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM221 S100 ; reset flow\nM900 K0 ; reset LA\nM84 ; disable motors",
+ "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
+ "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
+ "scan_first_layer": "0",
+ "machine_load_filament_time": "17",
+ "machine_unload_filament_time": "16",
+ "nozzle_type": "hardened_steel",
+ "auxiliary_fan": "0"
+}
diff --git a/resources/profiles/Prusa/machine/Prusa MK4.json b/resources/profiles/Prusa/machine/Prusa MK4.json
new file mode 100644
index 0000000000..5b5392a879
--- /dev/null
+++ b/resources/profiles/Prusa/machine/Prusa MK4.json
@@ -0,0 +1,12 @@
+{
+ "type": "machine_model",
+ "name": "Prusa MK4",
+ "model_id": "MK4",
+ "nozzle_diameter": "0.4",
+ "machine_tech": "FFF",
+ "family": "Prusa",
+ "bed_model": "mk4_bed.stl",
+ "bed_texture": "mk4.svg",
+ "hotend_model": "",
+ "default_materials": "Prusa Generic ABS;Prusa Generic PLA;Prusa Generic PLA-CF;Prusa Generic PETG;Prusa Generic TPU;Prusa Generic ASA;Prusa Generic PC;Prusa Generic PVA;Prusa Generic PA;Prusa Generic PA-CF"
+}
diff --git a/resources/profiles/Prusa/mk4.svg b/resources/profiles/Prusa/mk4.svg
new file mode 100644
index 0000000000..76d2cb069a
--- /dev/null
+++ b/resources/profiles/Prusa/mk4.svg
@@ -0,0 +1,37 @@
+
+
\ No newline at end of file
diff --git a/resources/profiles/Prusa/mk4_bed.stl b/resources/profiles/Prusa/mk4_bed.stl
new file mode 100644
index 0000000000..6aff36f0bc
Binary files /dev/null and b/resources/profiles/Prusa/mk4_bed.stl differ
diff --git a/resources/profiles/Prusa/process/0.20mm Standard @MK4.json b/resources/profiles/Prusa/process/0.20mm Standard @MK4.json
new file mode 100644
index 0000000000..1cd5ed3ca5
--- /dev/null
+++ b/resources/profiles/Prusa/process/0.20mm Standard @MK4.json
@@ -0,0 +1,26 @@
+{
+ "type": "process",
+ "setting_id": "GP004",
+ "name": "0.20mm Standard @MK4",
+ "from": "system",
+ "instantiation": "true",
+ "inherits": "fdm_process_common",
+ "initial_layer_speed": "45",
+ "initial_layer_infill_speed": "80",
+ "outer_wall_speed": "170",
+ "inner_wall_speed": "170",
+ "sparse_infill_speed": "200",
+ "internal_solid_infill_speed": "200",
+ "top_surface_speed": "100",
+ "gap_infill_speed": "120",
+ "travel_speed": "300",
+ "default_acceleration": "4000",
+ "initial_layer_acceleration": "700",
+ "top_surface_acceleration": "1000",
+ "travel_acceleration": "4000",
+ "inner_wall_acceleration": "4000",
+ "outer_wall_acceleration": "3000",
+ "compatible_printers": [
+ "Prusa MK4 0.4 nozzle"
+ ]
+}
\ No newline at end of file
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index b2dd67a746..2732a09cf3 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -335,6 +335,9 @@ void AppConfig::set_defaults()
// }
// #endif
+ if (get("allow_ip_resolve").empty())
+ set("allow_ip_resolve", "1");
+
if (get("presets", "filament_colors").empty()) {
set_str("presets", "filament_colors", "#F2754E");
}
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index afa894b68b..0e6a6d81a0 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -80,6 +80,8 @@ public:
{ std::string value; this->get(section, key, value); return value; }
std::string get(const std::string &key) const
{ std::string value; this->get("app", key, value); return value; }
+ bool get_bool(const std::string &key) const
+ { return this->get(key) == "true"; }
void set(const std::string §ion, const std::string &key, const std::string &value)
{
#ifndef NDEBUG
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index b12f31782f..caae0bc812 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -2489,6 +2489,12 @@ void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print)
file.write_format("M204 P%d T%d ; sets acceleration (P, T), mm/sec^2\n",
int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
travel_acc);
+ else if (flavor == gcfMarlinFirmware)
+ // New Marlin uses M204 P[print] R[retract] T[travel]
+ file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
+ int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
+ int(print.config().machine_max_acceleration_retracting.values.front() + 0.5),
+ int(print.config().machine_max_acceleration_travel.values.front() + 0.5));
else
file.write_format("M204 P%d R%d T%d\n",
int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp
index e28e142d05..4d9dd3fefb 100644
--- a/src/libslic3r/GCodeWriter.cpp
+++ b/src/libslic3r/GCodeWriter.cpp
@@ -18,16 +18,20 @@ namespace Slic3r {
bool GCodeWriter::full_gcode_comment = true;
const double GCodeWriter::slope_threshold = 3 * PI / 180;
+bool supports_separate_travel_acceleration(GCodeFlavor flavor)
+{
+ return (flavor == gcfRepetier || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware);
+}
+
void GCodeWriter::apply_print_config(const PrintConfig &print_config)
{
this->config.apply(print_config, true);
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
- bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy ||
- print_config.gcode_flavor.value == gcfMarlinFirmware ||
- print_config.gcode_flavor.value == gcfKlipper ||
- print_config.gcode_flavor.value == gcfRepRapFirmware;
+ bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware ||
+ print_config.gcode_flavor.value == gcfKlipper || print_config.gcode_flavor.value == gcfRepRapFirmware;
m_max_acceleration = std::lrint(use_mach_limits ? print_config.machine_max_acceleration_extruding.values.front() : 0);
- m_max_jerk = std::lrint(use_mach_limits ? std::min(print_config.machine_max_jerk_x.values.front(), print_config.machine_max_jerk_y.values.front()) : 0);
+ m_max_jerk = std::lrint(
+ use_mach_limits ? std::min(print_config.machine_max_jerk_x.values.front(), print_config.machine_max_jerk_y.values.front()) : 0);
m_max_jerk_z = print_config.machine_max_jerk_z.values.front();
m_max_jerk_e = print_config.machine_max_jerk_e.values.front();
}
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index e2074bd217..76b8fcfd0b 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -1972,7 +1972,7 @@ void PrintConfigDef::init_fff_params()
//def->enum_values.push_back("repetier");
//def->enum_values.push_back("teacup");
//def->enum_values.push_back("makerware");
- //def->enum_values.push_back("marlin2");
+ def->enum_values.push_back("marlin2");
//def->enum_values.push_back("sailfish");
//def->enum_values.push_back("mach3");
//def->enum_values.push_back("machinekit");
@@ -1985,7 +1985,7 @@ void PrintConfigDef::init_fff_params()
//def->enum_labels.push_back("Repetier");
//def->enum_labels.push_back("Teacup");
//def->enum_labels.push_back("MakerWare (MakerBot)");
- //def->enum_labels.push_back("Marlin 2");
+ def->enum_labels.push_back("Marlin 2");
//def->enum_labels.push_back("Sailfish (MakerBot)");
//def->enum_labels.push_back("Mach3/LinuxCNC");
//def->enum_labels.push_back("Machinekit");
diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp
index 0c64650df6..060643c1ff 100644
--- a/src/slic3r/GUI/BonjourDialog.cpp
+++ b/src/slic3r/GUI/BonjourDialog.cpp
@@ -5,6 +5,8 @@
#include
#include
+#include
+
#include
#include
#include
@@ -15,8 +17,8 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
+#include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/Bonjour.hpp"
-#include "Widgets/Button.hpp"
namespace Slic3r {
@@ -61,8 +63,6 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
, timer_state(0)
, tech(tech)
{
- SetBackgroundColour(*wxWHITE);
-
const int em = GUI::wxGetApp().em_unit();
list->SetMinSize(wxSize(80 * em, 30 * em));
@@ -81,39 +81,10 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
vsizer->Add(list, 1, wxEXPAND | wxALL, em);
-
- auto button_sizer = new wxBoxSizer(wxHORIZONTAL);
-
- StateColor btn_bg_green(std::pair(wxColour(0, 137, 123), StateColor::Pressed), std::pair(wxColour(38, 166, 154), StateColor::Hovered),
- std::pair(wxColour(0, 150, 136), StateColor::Normal));
-
- StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered),
- std::pair(*wxWHITE, StateColor::Normal));
-
- auto m_button_ok = new Button(this, _L("OK"));
- m_button_ok->SetBackgroundColor(btn_bg_green);
- m_button_ok->SetBorderColor(*wxWHITE);
- m_button_ok->SetTextColor(*wxWHITE);
- m_button_ok->SetFont(Label::Body_12);
- m_button_ok->SetSize(wxSize(FromDIP(58), FromDIP(24)));
- m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24)));
- m_button_ok->SetCornerRadius(FromDIP(12));
-
- m_button_ok->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { this->EndModal(wxID_OK); });
-
- auto m_button_cancel = new Button(this, _L("Cancel"));
- m_button_cancel->SetBackgroundColor(btn_bg_white);
- m_button_cancel->SetBorderColor(wxColour(38, 46, 48));
- m_button_cancel->SetFont(Label::Body_12);
- m_button_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24)));
- m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24)));
- m_button_cancel->SetCornerRadius(FromDIP(12));
-
- m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { this->EndModal(wxID_CANCEL); });
-
- button_sizer->AddStretchSpacer();
- button_sizer->Add(m_button_ok, 0, wxALL, FromDIP(5));
- button_sizer->Add(m_button_cancel, 0, wxALL, FromDIP(5));
+ 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);
@@ -253,19 +224,61 @@ void BonjourDialog::on_timer(wxTimerEvent &)
// explicitly (wxTimerEvent should not be created by user code).
void BonjourDialog::on_timer_process()
{
- const auto search_str = _utf8(L("Searching for devices"));
+ const auto search_str = _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()));
+ label->SetLabel(search_str + dots);
timer_state = (timer_state) % 3 + 1;
} else {
- label->SetLabel(GUI::from_u8((boost::format("%1%: %2%") % search_str % (_utf8(L("Finished"))+".")).str()));
+ label->SetLabel(search_str + ": " + _L("Finished") + ".");
timer->Stop();
}
}
+IPListDialog::IPListDialog(wxWindow* parent, const wxString& hostname, const std::vector& ips, size_t& selected_index)
+ : wxDialog(parent, wxID_ANY, _(L("Multiple resolved IP addresses")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ , m_list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxSIMPLE_BORDER))
+ , m_selected_index (selected_index)
+{
+ const int em = GUI::wxGetApp().em_unit();
+ m_list->SetMinSize(wxSize(40 * em, 30 * em));
+ wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
+ auto* label = new wxStaticText(this, wxID_ANY, GUI::format_wxstr(_L("There are several IP addresses resolving to hostname %1%.\nPlease select one that should be used."), hostname));
+ vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
+
+ m_list->SetSingleStyle(wxLC_SINGLE_SEL);
+ m_list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 40 * em);
+
+ for (size_t i = 0; i < ips.size(); i++)
+ m_list->InsertItem(i, boost::nowide::widen(ips[i].to_string()));
+
+ m_list->Select(0);
+
+ vsizer->Add(m_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);
+
+ vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
+ SetSizerAndFit(vsizer);
+
+ GUI::wxGetApp().UpdateDlgDarkUI(this);
+}
+
+IPListDialog::~IPListDialog()
+{
+}
+
+void IPListDialog::EndModal(int retCode)
+{
+ if (retCode == wxID_OK) {
+ m_selected_index = (size_t)m_list->GetFirstSelected();
+ }
+ wxDialog::EndModal(retCode);
+}
}
diff --git a/src/slic3r/GUI/BonjourDialog.hpp b/src/slic3r/GUI/BonjourDialog.hpp
index def0838d7e..8bfc076c44 100644
--- a/src/slic3r/GUI/BonjourDialog.hpp
+++ b/src/slic3r/GUI/BonjourDialog.hpp
@@ -1,9 +1,13 @@
#ifndef slic3r_BonjourDialog_hpp_
#define slic3r_BonjourDialog_hpp_
+#include
#include
+#include
+
#include
+#include
#include "libslic3r/PrintConfig.hpp"
@@ -11,7 +15,7 @@ class wxListView;
class wxStaticText;
class wxTimer;
class wxTimerEvent;
-
+class address;
namespace Slic3r {
@@ -41,12 +45,26 @@ private:
unsigned timer_state;
Slic3r::PrinterTechnology tech;
- void on_reply(BonjourReplyEvent &);
+ virtual void on_reply(BonjourReplyEvent &);
void on_timer(wxTimerEvent &);
void on_timer_process();
};
+class IPListDialog : public wxDialog
+{
+public:
+ IPListDialog(wxWindow* parent, const wxString& hostname, const std::vector& ips, size_t& selected_index);
+ IPListDialog(IPListDialog&&) = delete;
+ IPListDialog(const IPListDialog&) = delete;
+ IPListDialog& operator=(IPListDialog&&) = delete;
+ IPListDialog& operator=(const IPListDialog&) = delete;
+ ~IPListDialog();
+ virtual void EndModal(int retCode) wxOVERRIDE;
+private:
+ wxListView* m_list;
+ size_t& m_selected_index;
+};
}
diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp
index 015df7f463..b07b4a0919 100644
--- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp
+++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp
@@ -406,24 +406,36 @@ void PhysicalPrinterDialog::update(bool printer_change)
// 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);
+update_host_type(printer_change);
const auto opt = m_config->option>("host_type");
m_optgroup->show_field("host_type");
- if (opt->value == htPrusaLink)
- {
+
+ // hide PrusaConnect address
+ if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
+ if (wxTextCtrl* temp = dynamic_cast(printhost_field->getWindow()); temp && temp->GetValue() == L"https://connect.prusa3d.com") {
+ temp->SetValue(wxString());
+ }
+ }
+ if (opt->value == htPrusaLink) { // PrusaConnect does NOT allow http digest
m_optgroup->show_field("printhost_authorization_type");
AuthorizationType auth_type = m_config->option>("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(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{ "printhost_user", "printhost_password" })
m_optgroup->hide_field(opt_key);
supports_multiple_printers = opt && opt->value == htRepetier;
+ if (opt->value == htPrusaConnect) { // automatically show default prusaconnect address
+ if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
+ if (wxTextCtrl* temp = dynamic_cast(printhost_field->getWindow()); temp && temp->GetValue().IsEmpty()) {
+ temp->SetValue(L"https://connect.prusa3d.com");
+ }
+ }
+ }
}
-
}
else {
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
@@ -453,30 +465,23 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
{
if (m_config == nullptr)
return;
- bool all_presets_are_from_mk3_family = false;
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]));
- }
+ int last_in_conf = m_config->option("host_type")->getInt(); // this is real position in last choice
Choice* choice = dynamic_cast(ht);
choice->set_values(types);
- auto set_to_choice_and_config = [this, choice](PrintHostType type) {
- choice->set_value(static_cast(type));
+ int index_in_choice = (printer_change ? std::clamp(last_in_conf - ((int)ht->m_opt.enum_values.size() - (int)types.size()), 0, (int)ht->m_opt.enum_values.size() - 1) : last_in_conf);
+ choice->set_value(index_in_choice);
+ if ("prusalink" == ht->m_opt.enum_values.at(index_in_choice))
+ m_config->set_key_value("host_type", new ConfigOptionEnum(htPrusaLink));
+ else if ("prusaconnect" == ht->m_opt.enum_values.at(index_in_choice))
+ m_config->set_key_value("host_type", new ConfigOptionEnum(htPrusaConnect));
+ else {
+ int host_type = std::clamp(index_in_choice + ((int)ht->m_opt.enum_values.size() - (int)types.size()), 0, (int)ht->m_opt.enum_values.size() - 1);
+ PrintHostType type = static_cast(host_type);
m_config->set_key_value("host_type", new ConfigOptionEnum(type));
- };
- if ((printer_change && all_presets_are_from_mk3_family) || 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>("host_type")->value == htPrusaLink))
- set_to_choice_and_config(htOctoPrint);
- else
- choice->set_value(m_config->option("host_type")->getInt());
+ }
}
void PhysicalPrinterDialog::update_printers()
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index ec8e583046..43424d98be 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -10966,42 +10966,34 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn)
upload_job.printhost->get_groups(groups);
}
- // orca merge todo
- PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups);
+ // PrusaLink specific: Query the server for the list of file groups.
+ wxArrayString storage_paths;
+ wxArrayString storage_names;
+ {
+ wxBusyCursor wait;
+ try {
+ upload_job.printhost->get_storage(storage_paths, storage_names);
+ } catch (const Slic3r::IOError& ex) {
+ show_error(this, ex.what(), false);
+ return;
+ }
+ }
+
+ PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names);
if (dlg.ShowModal() == wxID_OK) {
upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.post_action = dlg.post_action();
upload_job.upload_data.group = dlg.group();
+ upload_job.upload_data.storage = dlg.storage();
+
+ // Show "Is printer clean" dialog for PrusaConnect - Upload and print.
+ if (std::string(upload_job.printhost->get_name()) == "PrusaConnect" && upload_job.upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
+ GUI::MessageDialog dlg(nullptr, _L("Is the printer ready? Is the print sheet in place, empty and clean?"), _L("Upload and Print"), wxOK | wxCANCEL);
+ if (dlg.ShowModal() != wxID_OK)
+ return;
+ }
p->export_gcode(fs::path(), false, std::move(upload_job));
-
- try {
- json j;
- switch (dlg.post_action()) {
- case PrintHostPostUploadAction::None:
- j["post_action"] = "Upload";
- break;
- case PrintHostPostUploadAction::StartPrint:
- j["post_action"] = "StartPrint";
- break;
- case PrintHostPostUploadAction::StartSimulation:
- j["post_action"] = "StartSimulation";
- break;
- }
-
- PresetBundle *preset_bundle = wxGetApp().preset_bundle;
- if (preset_bundle) {
- j["gcode_printer_model"] = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
- }
-
- if (physical_printer_config) {
- j["printer_preset"] = physical_printer_config->opt_string("inherits");
- }
-
- NetworkAgent *agent = wxGetApp().getAgent();
- } catch (...) {
- return;
- }
}
}
int Plater::send_gcode(int plate_idx, Export3mfProgressFn proFn)
diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp
index ec93b70162..9dd9f115d3 100644
--- a/src/slic3r/GUI/PrintHostDialogs.cpp
+++ b/src/slic3r/GUI/PrintHostDialogs.cpp
@@ -38,13 +38,14 @@ static const char *CONFIG_KEY_PATH = "printhost_path";
static const char *CONFIG_KEY_GROUP = "printhost_group";
static const char* CONFIG_KEY_STORAGE = "printhost_storage";
-PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage)
+PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage_paths, const wxArrayString& storage_names)
: MsgDialog(static_cast(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), 0) // Set style = 0 to avoid default creation of the "OK" button.
// All buttons will be added later in this constructor
, txt_filename(new wxTextCtrl(this, wxID_ANY))
, combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
- , combo_storage(storage.GetCount() > 1 ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, storage, wxCB_READONLY) : nullptr)
+ , combo_storage(storage_names.GetCount() > 1 ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, storage_names, wxCB_READONLY) : nullptr)
, post_upload_action(PrintHostPostUploadAction::None)
+ , m_paths(storage_paths)
{
#ifdef __APPLE__
txt_filename->OSXDisableAllSmartSubstitutions();
@@ -70,18 +71,18 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
if (combo_storage != nullptr) {
// PrusaLink specific: User needs to choose a storage
- auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage:"));
+ auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage") + ":");
content_sizer->Add(label_group);
content_sizer->Add(combo_storage, 0, wxBOTTOM, 2 * VERT_SPACING);
- combo_storage->SetValue(storage.front());
+ combo_storage->SetValue(storage_names.front());
wxString recent_storage = from_u8(app_config->get("recent", CONFIG_KEY_STORAGE));
if (!recent_storage.empty())
combo_storage->SetValue(recent_storage);
- } else if (storage.GetCount() == 1){
+ } else if (storage_names.GetCount() == 1){
// PrusaLink specific: Show which storage has been detected.
- auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage: ") + storage.front());
+ auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage") + ": " + storage_names.front());
content_sizer->Add(label_group);
- m_preselected_storage = storage.front();
+ m_preselected_storage = storage_paths.front();
}
@@ -196,7 +197,9 @@ std::string PrintHostSendDialog::storage() const
{
if (!combo_storage)
return GUI::format("%1%", m_preselected_storage);
- return boost::nowide::narrow(combo_storage->GetValue());
+ if (combo_storage->GetSelection() < 0 || combo_storage->GetSelection() >= int(m_paths.size()))
+ return {};
+ return boost::nowide::narrow(m_paths[combo_storage->GetSelection()]);
}
void PrintHostSendDialog::EndModal(int ret)
@@ -226,8 +229,6 @@ void PrintHostSendDialog::EndModal(int ret)
MsgDialog::EndModal(ret);
}
-
-
wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
@@ -355,8 +356,6 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
if (selected == wxNOT_FOUND) { return; }
GUI::show_error(nullptr, job_list->GetTextValue(selected, COL_ERRORMSG));
});
-
- wxGetApp().UpdateDlgDarkUI(this);
}
void PrintHostQueueDialog::append_job(const PrintHostJob &job)
@@ -474,7 +473,7 @@ void PrintHostQueueDialog::on_error(Event &evt)
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.status.ToUTF8())).str());
+ auto errormsg = format_wxstr("%1%\n%2%", _L("Error uploading to print host") + ":", evt.status);
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
@@ -505,6 +504,7 @@ void PrintHostQueueDialog::on_cancel(Event &evt)
void PrintHostQueueDialog::on_info(Event& evt)
{
+ /*
wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
if (evt.tag == L"resolve") {
@@ -524,6 +524,7 @@ void PrintHostQueueDialog::on_info(Event& evt)
} else if (evt.tag == L"set_complete_off") {
wxGetApp().notification_manager()->set_upload_job_notification_comp_on_100(evt.job_id + 1, false);
}
+ */
}
void PrintHostQueueDialog::get_active_jobs(std::vector>& ret)
@@ -565,8 +566,11 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector& vector)
auto* app_config = wxGetApp().app_config;
auto hasget = [app_config](const std::string& name, std::vector& vector)->bool {
if (app_config->has(name)) {
- vector.push_back(std::stoi(app_config->get(name)));
- return true;
+ std::string val = app_config->get(name);
+ if (!val.empty() || val[0]!='\0') {
+ vector.push_back(std::stoi(val));
+ return true;
+ }
}
return false;
};
diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp
index 80e2a0f485..b3f5504050 100644
--- a/src/slic3r/GUI/PrintHostDialogs.hpp
+++ b/src/slic3r/GUI/PrintHostDialogs.hpp
@@ -26,7 +26,7 @@ namespace GUI {
class PrintHostSendDialog : public GUI::MsgDialog
{
public:
- PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage);
+ PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage_paths, const wxArrayString& storage_names);
boost::filesystem::path filename() const;
PrintHostPostUploadAction post_action() const;
std::string group() const;
@@ -40,6 +40,7 @@ private:
PrintHostPostUploadAction post_upload_action;
wxString m_valid_suffix;
wxString m_preselected_storage;
+ wxArrayString m_paths;
};
diff --git a/src/slic3r/Utils/Bonjour.hpp b/src/slic3r/Utils/Bonjour.hpp
index e61cd18331..50b71791fa 100644
--- a/src/slic3r/Utils/Bonjour.hpp
+++ b/src/slic3r/Utils/Bonjour.hpp
@@ -7,12 +7,17 @@
#include
#include
#include
-#include
+#include
+#include
+#include
+#include
+#include
namespace Slic3r {
+
struct BonjourReply
{
typedef std::unordered_map TxtData;
@@ -40,7 +45,6 @@ struct BonjourReply
std::ostream& operator<<(std::ostream &, const BonjourReply &);
-
/// Bonjour lookup performer
class Bonjour : public std::enable_shared_from_this {
private:
@@ -49,6 +53,7 @@ public:
typedef std::shared_ptr Ptr;
typedef std::function ReplyFn;
typedef std::function CompleteFn;
+ typedef std::function&)> ResolveFn;
typedef std::set TxtKeys;
Bonjour(std::string service);
@@ -65,15 +70,217 @@ public:
// ^ Note: By default there is 1 retry (meaning 1 broadcast is sent).
// Timeout is per one retry, ie. total time spent listening = retries * timeout.
// If retries > 1, then care needs to be taken as more than one reply from the same service may be received.
+
+ // sets hostname queried by resolve()
+ Bonjour& set_hostname(const std::string& hostname);
Bonjour& on_reply(ReplyFn fn);
Bonjour& on_complete(CompleteFn fn);
+ Bonjour& on_resolve(ResolveFn fn);
+ // lookup all devices by given TxtKeys
+ // each correct reply is passed back in ReplyFn, finishes with CompleteFn
Ptr lookup();
+ // performs resolving of hostname into vector of ip adresses passed back by ResolveFn
+ // needs set_hostname and on_resolve to be called before.
+ Ptr resolve();
+ // resolve on the current thread
+ void resolve_sync();
private:
std::unique_ptr p;
};
+struct BonjourRequest
+{
+ static const boost::asio::ip::address_v4 MCAST_IP4;
+ static const boost::asio::ip::address_v6 MCAST_IP6;
+ static const uint16_t MCAST_PORT;
+
+ std::vector m_data;
+
+ static boost::optional make_PTR(const std::string& service, const std::string& protocol);
+ static boost::optional make_A(const std::string& hostname);
+ static boost::optional make_AAAA(const std::string& hostname);
+private:
+ BonjourRequest(std::vector&& data) : m_data(std::move(data)) {}
+};
+
+
+class LookupSocket;
+class ResolveSocket;
+
+// Session is created for each async_receive of socket. On receive, its handle_receive method is called (Thru io_service->post).
+// ReplyFn is called if correct datagram was received.
+class UdpSession
+{
+public:
+ UdpSession(Bonjour::ReplyFn rfn);
+ virtual void handle_receive(const boost::system::error_code& error, size_t bytes) = 0;
+ std::vector buffer;
+ boost::asio::ip::udp::endpoint remote_endpoint;
+protected:
+ Bonjour::ReplyFn replyfn;
+};
+typedef std::shared_ptr SharedSession;
+// Session for LookupSocket
+class LookupSession : public UdpSession
+{
+public:
+ LookupSession(const LookupSocket* sckt, Bonjour::ReplyFn rfn) : UdpSession(rfn), socket(sckt) {}
+ void handle_receive(const boost::system::error_code& error, size_t bytes) override;
+protected:
+ // const pointer to socket to get needed data as txt_keys etc.
+ const LookupSocket* socket;
+};
+// Session for ResolveSocket
+class ResolveSession : public UdpSession
+{
+public:
+ ResolveSession(const ResolveSocket* sckt, Bonjour::ReplyFn rfn) : UdpSession(rfn), socket(sckt) {}
+ void handle_receive(const boost::system::error_code& error, size_t bytes) override;
+protected:
+ // const pointer to seocket to get hostname during handle_receive
+ const ResolveSocket* socket;
+};
+
+// Udp socket, starts receiving answers after first send() call until io_service is stopped.
+class UdpSocket
+{
+public:
+ // Two constructors: 1st is with interface which must be resolved before calling this
+ UdpSocket(Bonjour::ReplyFn replyfn
+ , const boost::asio::ip::address& multicast_address
+ , const boost::asio::ip::address& interface_address
+ , std::shared_ptr< boost::asio::io_service > io_service);
+
+ UdpSocket(Bonjour::ReplyFn replyfn
+ , const boost::asio::ip::address& multicast_address
+ , std::shared_ptr< boost::asio::io_service > io_service);
+
+ void send();
+ void async_receive();
+ void cancel() { socket.cancel(); }
+protected:
+ void receive_handler(SharedSession session, const boost::system::error_code& error, size_t bytes);
+ virtual SharedSession create_session() const = 0;
+
+ Bonjour::ReplyFn replyfn;
+ boost::asio::ip::address multicast_address;
+ boost::asio::ip::udp::socket socket;
+ boost::asio::ip::udp::endpoint mcast_endpoint;
+ std::shared_ptr< boost::asio::io_service > io_service;
+ std::vector requests;
+};
+
+class LookupSocket : public UdpSocket
+{
+public:
+ LookupSocket(Bonjour::TxtKeys txt_keys
+ , std::string service
+ , std::string service_dn
+ , std::string protocol
+ , Bonjour::ReplyFn replyfn
+ , const boost::asio::ip::address& multicast_address
+ , const boost::asio::ip::address& interface_address
+ , std::shared_ptr< boost::asio::io_service > io_service)
+ : UdpSocket(replyfn, multicast_address, interface_address, io_service)
+ , txt_keys(txt_keys)
+ , service(service)
+ , service_dn(service_dn)
+ , protocol(protocol)
+ {
+ assert(!service.empty() && replyfn);
+ create_request();
+ }
+
+ LookupSocket(Bonjour::TxtKeys txt_keys
+ , std::string service
+ , std::string service_dn
+ , std::string protocol
+ , Bonjour::ReplyFn replyfn
+ , const boost::asio::ip::address& multicast_address
+ , std::shared_ptr< boost::asio::io_service > io_service)
+ : UdpSocket(replyfn, multicast_address, io_service)
+ , txt_keys(txt_keys)
+ , service(service)
+ , service_dn(service_dn)
+ , protocol(protocol)
+ {
+ assert(!service.empty() && replyfn);
+ create_request();
+ }
+
+ const Bonjour::TxtKeys get_txt_keys() const { return txt_keys; }
+ const std::string get_service() const { return service; }
+ const std::string get_service_dn() const { return service_dn; }
+
+protected:
+ SharedSession create_session() const override;
+ void create_request()
+ {
+ requests.clear();
+ // create PTR request
+ if (auto rqst = BonjourRequest::make_PTR(service, protocol); rqst)
+ requests.push_back(std::move(rqst.get()));
+ }
+ boost::optional request;
+ Bonjour::TxtKeys txt_keys;
+ std::string service;
+ std::string service_dn;
+ std::string protocol;
+};
+
+class ResolveSocket : public UdpSocket
+{
+public:
+ ResolveSocket(const std::string& hostname
+ , Bonjour::ReplyFn replyfn
+ , const boost::asio::ip::address& multicast_address
+ , const boost::asio::ip::address& interface_address
+ , std::shared_ptr< boost::asio::io_service > io_service)
+ : UdpSocket(replyfn, multicast_address, interface_address, io_service)
+ , hostname(hostname)
+
+ {
+ assert(!hostname.empty() && replyfn);
+ create_requests();
+ }
+
+ ResolveSocket(const std::string& hostname
+ , Bonjour::ReplyFn replyfn
+ , const boost::asio::ip::address& multicast_address
+ , std::shared_ptr< boost::asio::io_service > io_service)
+ : UdpSocket(replyfn, multicast_address, io_service)
+ , hostname(hostname)
+
+ {
+ assert(!hostname.empty() && replyfn);
+ create_requests();
+ }
+
+ std::string get_hostname() const { return hostname; }
+protected:
+ SharedSession create_session() const override;
+ void create_requests()
+ {
+ requests.clear();
+ // BonjourRequest::make_A / AAAA is now implemented to add .local correctly after the hostname.
+ // If that is unsufficient, we need to change make_A / AAAA and pass full hostname.
+ std::string trimmed_hostname = hostname;
+ if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos)
+ trimmed_hostname = trimmed_hostname.substr(0, dot_pos);
+ if (auto rqst = BonjourRequest::make_A(trimmed_hostname); rqst)
+ requests.push_back(std::move(rqst.get()));
+
+ trimmed_hostname = hostname;
+ if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos)
+ trimmed_hostname = trimmed_hostname.substr(0, dot_pos);
+ if (auto rqst = BonjourRequest::make_AAAA(trimmed_hostname); rqst)
+ requests.push_back(std::move(rqst.get()));
+ }
+
+ std::string hostname;
+};
}
diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp
index 88be292920..1814a17a9f 100644
--- a/src/slic3r/Utils/OctoPrint.cpp
+++ b/src/slic3r/Utils/OctoPrint.cpp
@@ -33,6 +33,38 @@ namespace Slic3r {
namespace {
#ifdef WIN32
+std::string get_host_from_url(const std::string& url_in)
+{
+ std::string url = url_in;
+ // add http:// if there is no scheme
+ size_t double_slash = url.find("//");
+ if (double_slash == std::string::npos)
+ url = "http://" + url;
+ std::string out = url;
+ CURLU* hurl = curl_url();
+ if (hurl) {
+ // Parse the input URL.
+ CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0);
+ if (rc == CURLUE_OK) {
+ // Replace the address.
+ char* host;
+ rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0);
+ if (rc == CURLUE_OK) {
+ out = host;
+ curl_free(host);
+ }
+ else
+ BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to get host form URL " << url;
+ }
+ else
+ BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to parse URL " << url;
+ curl_url_cleanup(hurl);
+ }
+ else
+ BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to allocate curl_url";
+ return out;
+}
+
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
{
@@ -96,38 +128,6 @@ std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
return out;
#endif
}
-
-std::string get_host_from_url(const std::string& url_in)
-{
- std::string url = url_in;
- // add http:// if there is no scheme
- size_t double_slash = url.find("//");
- if (double_slash == std::string::npos)
- url = "http://" + url;
- std::string out = url;
- CURLU* hurl = curl_url();
- if (hurl) {
- // Parse the input URL.
- CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0);
- if (rc == CURLUE_OK) {
- // Replace the address.
- char* host;
- rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0);
- if (rc == CURLUE_OK) {
- out = host;
- curl_free(host);
- }
- else
- BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to get host form URL " << url;
- }
- else
- BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to parse URL " << url;
- curl_url_cleanup(hurl);
- }
- else
- BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to allocate curl_url";
- return out;
-}
#endif // WIN32
std::string escape_string(const std::string& unescaped)
{
@@ -170,7 +170,7 @@ const char* OctoPrint::get_name() const { return "OctoPrint"; }
bool OctoPrint::test_with_resolved_ip(wxString &msg) const
{
// Since the request is performed synchronously here,
- // it is ok to refer to `msg` from within the closure
+ // it is ok to refer to `msg` from within the closure
const char* name = get_name();
bool res = true;
// Msg contains ip string.
@@ -179,7 +179,15 @@ bool OctoPrint::test_with_resolved_ip(wxString &msg) const
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
+ std::string host = get_host_from_url(m_host);
auto http = Http::get(url);//std::move(url));
+ // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
+ // And when creating Http object above, libcurl automatically includes "Host" header from address it got.
+ // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
+ // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
+ // Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
+ // https://www.rfc-editor.org/rfc/rfc7230#section-5.4
+ http.header("Host", host);
set_auth(http);
http
.on_error([&](std::string body, std::string error, unsigned status) {
@@ -203,7 +211,7 @@ bool OctoPrint::test_with_resolved_ip(wxString &msg) const
const auto text = ptree.get_optional("text");
res = validate_version_text(text);
if (!res) {
- msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
+ msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : name));
}
}
catch (const std::exception&) {
@@ -228,7 +236,7 @@ bool OctoPrint::test(wxString& msg) const
auto url = make_url("api/version");
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
-
+ // Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself.
auto http = Http::get(std::move(url));
set_auth(http);
http.on_error([&](std::string body, std::string error, unsigned status) {
@@ -252,7 +260,7 @@ bool OctoPrint::test(wxString& msg) const
const auto text = ptree.get_optional("text");
res = validate_version_text(text);
if (! res) {
- msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
+ msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : name));
}
}
catch (const std::exception &) {
@@ -273,7 +281,6 @@ bool OctoPrint::test(wxString& msg) const
return res;
}
-
wxString OctoPrint::get_test_ok_msg () const
{
return _(L("Connection to OctoPrint works correctly."));
@@ -281,10 +288,10 @@ wxString OctoPrint::get_test_ok_msg () const
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());
+ return GUI::format_wxstr("%s: %s\n\n%s"
+ , _L("Could not connect to OctoPrint")
+ , msg
+ , _L("Note: OctoPrint version at least 1.1.0 is required."));
}
bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
@@ -300,7 +307,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
boost::asio::ip::address host_ip = boost::asio::ip::make_address(host, ec);
if (!ec) {
resolved_addr.push_back(host_ip);
- } else if ( GUI::get_app_config()->get("allow_ip_resolve") == "1" && boost::algorithm::ends_with(host, ".local")){
+ } else if ( GUI::get_app_config()->get_bool("allow_ip_resolve") && boost::algorithm::ends_with(host, ".local")){
Bonjour("octoprint")
.set_hostname(host)
.set_retries(5) // number of rounds of queries send
@@ -340,7 +347,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
return true;
} else {
// There are multiple addresses - user needs to choose which to use.
- size_t selected_index = resolved_addr.size();
+ size_t selected_index = resolved_addr.size();
IPListDialog dialog(nullptr, boost::nowide::widen(m_host), resolved_addr, selected_index);
if (dialog.ShowModal() == wxID_OK && selected_index < resolved_addr.size()) {
return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr[selected_index]);
@@ -379,7 +386,14 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr
% upload_parent_path.string()
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
+ std::string host = get_host_from_url(m_host);
auto http = Http::post(url);//std::move(url));
+ // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
+ // And when creating Http object above, libcurl automatically includes "Host" header from address it got.
+ // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
+ // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
+ // https://www.rfc-editor.org/rfc/rfc7230#section-5.4
+ http.header("Host", host);
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 ???
@@ -397,7 +411,7 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr
prorgess_fn(std::move(progress), cancel);
if (cancel) {
// Upload was canceled
- BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled";
+ BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
result = false;
}
})
@@ -428,7 +442,7 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p
#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")
+ if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
#endif // _WIN32
{
// If https is entered we assume signed ceritificate is being used
@@ -458,6 +472,16 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
auto http = Http::post(std::move(url));
+#ifdef WIN32
+ // "Host" header is necessary here. In the workaround above (two mDNS..) we have got IP address from test connection and subsituted it into "url" variable.
+ // And when creating Http object above, libcurl automatically includes "Host" header from address it got.
+ // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
+ // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
+ // Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
+ // https://www.rfc-editor.org/rfc/rfc7230#section-5.4
+ std::string host = get_host_from_url(m_host);
+ http.header("Host", host);
+#endif // _WIN32
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 ???
@@ -474,7 +498,7 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p
prorgess_fn(std::move(progress), cancel);
if (cancel) {
// Upload was canceled
- BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled";
+ BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
res = false;
}
})
@@ -513,11 +537,8 @@ std::string OctoPrint::make_url(const std::string &path) const
}
}
-SL1Host::SL1Host(DynamicPrintConfig *config) :
- OctoPrint(config),
- m_authorization_type(dynamic_cast*>(config->option("printhost_authorization_type"))->value),
- m_username(config->opt_string("printhost_user")),
- m_password(config->opt_string("printhost_password"))
+SL1Host::SL1Host(DynamicPrintConfig *config)
+ : PrusaLink(config)
{
}
@@ -531,9 +552,7 @@ wxString SL1Host::get_test_ok_msg () const
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());
+ return GUI::format_wxstr("%s: %s", _L("Could not connect to Prusa SLA"), msg);
}
bool SL1Host::validate_version_text(const boost::optional &version_text) const
@@ -541,26 +560,10 @@ bool SL1Host::validate_version_text(const boost::optional &version_
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, bool show_after_message) :
OctoPrint(config),
- m_authorization_type(dynamic_cast*>(config->option("printhost_authorization_type"))->value),
+ m_authorization_type(config->option>("printhost_authorization_type")->value),
m_username(config->opt_string("printhost_user")),
m_password(config->opt_string("printhost_password")),
m_show_after_message(show_after_message)
@@ -576,9 +579,7 @@ wxString PrusaLink::get_test_ok_msg() const
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());
+ return GUI::format_wxstr("%s: %s", _L("Could not connect to PrusaLink"), msg);
}
bool PrusaLink::validate_version_text(const boost::optional& version_text) const
@@ -664,7 +665,7 @@ bool PrusaLink::test(wxString& msg) const
const auto text = ptree.get_optional("text");
res = validate_version_text(text);
if (!res) {
- msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
+ msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "OctoPrint"));
}
}
catch (const std::exception&) {
@@ -685,7 +686,7 @@ bool PrusaLink::test(wxString& msg) const
return res;
}
-bool PrusaLink::get_storage(wxArrayString& output) const
+bool PrusaLink::get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const
{
const char* name = get_name();
@@ -693,17 +694,22 @@ bool PrusaLink::get_storage(wxArrayString& output) const
auto url = make_url("api/v1/storage");
wxString error_msg;
- struct StorageInfo{
+ struct StorageInfo {
+ wxString path;
wxString name;
- bool read_only;
- long long free_space;
+ bool read_only = false;
+ long long free_space = -1;
};
std::vector storage;
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage at: %2%") % name % url;
+ wxString wlang = GUI::wxGetApp().current_language_code();
+ std::string lang = GUI::format(wlang.SubString(0, 1));
+
auto http = Http::get(std::move(url));
set_auth(http);
+ http.header("Accept-Language", lang);
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting storage: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
error_msg = L"\n\n" + boost::nowide::widen(error);
@@ -716,7 +722,7 @@ bool PrusaLink::get_storage(wxArrayString& output) const
res = true;
})
- .on_complete([&, this](std::string body, unsigned) {
+ .on_complete([&](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got storage: %2%") % name % body;
try
{
@@ -731,14 +737,19 @@ bool PrusaLink::get_storage(wxArrayString& output) const
}
// each storage has own subtree of storage_list
for (const auto& section : ptree.front().second) {
+ const auto name = section.second.get_optional("name");
const auto path = section.second.get_optional("path");
const auto space = section.second.get_optional("free_space");
const auto read_only = section.second.get_optional("read_only");
+ const auto ro = section.second.get_optional("ro"); // In PrusaLink 0.7.0RC2 "read_only" value is stored under "ro".
const auto available = section.second.get_optional("available");
if (path && (!available || *available)) {
StorageInfo si;
- si.name = boost::nowide::widen(*path);
- si.read_only = read_only ? *read_only : false; // If read_only is missing, assume it is NOT read only.
+ si.path = boost::nowide::widen(*path);
+ si.name = name ? boost::nowide::widen(*name) : wxString();
+ // If read_only is missing, assume it is NOT read only.
+ // si.read_only = read_only ? *read_only : false; // version without "ro"
+ si.read_only = (read_only ? *read_only : (ro ? *ro : false));
si.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space.
storage.emplace_back(std::move(si));
}
@@ -756,19 +767,25 @@ bool PrusaLink::get_storage(wxArrayString& output) const
.perform_sync();
for (const auto& si : storage) {
- if (!si.read_only && si.free_space > 0)
- output.push_back(si.name);
+ if (!si.read_only && si.free_space > 0) {
+ storage_path.push_back(si.path);
+ storage_name.push_back(si.name);
+ }
}
- if (res && output.empty())
- {
+ if (res && storage_path.empty()) {
if (!storage.empty()) { // otherwise error_msg is already filled
- error_msg = L"\n\n" + _L("Storages found:") + L" \n";
+ error_msg = L"\n\n" + _L("Storages found") + L": \n";
for (const auto& si : storage) {
- error_msg += si.name + L" : " + (si.read_only ? _L("read only") : _L("no free space")) + L"\n";
+ error_msg += GUI::format_wxstr(si.read_only ?
+ // TRN %1% = storage path
+ _L("%1% : read only") :
+ // TRN %1% = storage path
+ _L("%1% : no free space"), si.path) + L"\n";
}
}
- std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%.%2%"), m_host, error_msg);
+ // TRN %1% = host
+ std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%."), m_host) + GUI::into_u8(error_msg);
BOOST_LOG_TRIVIAL(error) << message;
throw Slic3r::IOError(message);
}
@@ -787,7 +804,7 @@ bool PrusaLink::test_with_method_check(wxString& msg, bool& use_put) const
auto url = make_url("api/version");
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
-
+ // Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself.
auto http = Http::get(std::move(url));
set_auth(http);
http.on_error([&](std::string body, std::string error, unsigned status) {
@@ -811,7 +828,7 @@ bool PrusaLink::test_with_method_check(wxString& msg, bool& use_put) const
const auto text = ptree.get_optional("text");
res = validate_version_text(text);
if (!res) {
- msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
+ msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "OctoPrint"));
use_put = false;
return;
}
@@ -860,7 +877,15 @@ bool PrusaLink::test_with_resolved_ip_and_method_check(wxString& msg, bool& use_
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
+ std::string host = get_host_from_url(m_host);
auto http = Http::get(url);//std::move(url));
+ // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
+ // And when creating Http object above, libcurl automatically includes "Host" header from address it got.
+ // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
+ // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
+ // Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
+ // https://www.rfc-editor.org/rfc/rfc7230#section-5.4
+ http.header("Host", host);
set_auth(http);
http
.on_error([&](std::string body, std::string error, unsigned status) {
@@ -884,7 +909,7 @@ bool PrusaLink::test_with_resolved_ip_and_method_check(wxString& msg, bool& use_
const auto text = ptree.get_optional("text");
res = validate_version_text(text);
if (!res) {
- msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
+ msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "OctoPrint"));
use_put = false;
return;
}
@@ -968,12 +993,11 @@ bool PrusaLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p
}
std::string url;
- bool res = true;
std::string storage_path = (use_put ? "api/v1/files" : "api/files");
storage_path += (upload_data.storage.empty() ? "/local" : upload_data.storage);
#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")
+ if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
#endif // _WIN32
{
// If https is entered we assume signed ceritificate is being used
@@ -1015,13 +1039,20 @@ bool PrusaLink::put_inner(PrintHostUpload upload_data, std::string url, const st
bool res = true;
// Percent escape all filenames in on path and add it to the url. This is different from POST.
url += "/" + escape_path_by_element(upload_data.upload_path);
-
Http http = Http::put(std::move(url));
+#ifdef WIN32
+ // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
+ // And when creating Http object above, libcurl automatically includes "Host" header from address it got.
+ // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
+ // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
+ // https://www.rfc-editor.org/rfc/rfc7230#section-5.4
+ std::string host = get_host_from_url(m_host);
+ http.header("Host", host);
+#endif // _WIN32
set_auth(http);
// This is ugly, but works. There was an error at PrusaLink side that accepts any string at Print-After-Upload as true, thus False was also triggering print after upload.
if (upload_data.post_action == PrintHostPostUploadAction::StartPrint)
- http.header("Print-After-Upload", "True");
-
+ http.header("Print-After-Upload", "?1");
http.set_put_body(upload_data.source_path)
.header("Content-Type", "text/x.gcode")
.header("Overwrite", "?1")
@@ -1057,7 +1088,17 @@ bool PrusaLink::post_inner(PrintHostUpload upload_data, std::string url, const s
bool res = true;
const auto upload_filename = upload_data.upload_path.filename();
const auto upload_parent_path = upload_data.upload_path.parent_path();
+
Http http = Http::post(std::move(url));
+#ifdef WIN32
+ // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
+ // And when creating Http object above, libcurl automatically includes "Host" header from address it got.
+ // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
+ // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
+ // https://www.rfc-editor.org/rfc/rfc7230#section-5.4
+ std::string host = get_host_from_url(m_host);
+ http.header("Host", host);
+#endif // _WIN32
set_auth(http);
set_http_post_header_args(http, upload_data.post_action);
http.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
@@ -1128,14 +1169,11 @@ void PrusaConnect::set_http_post_header_args(Http& http, PrintHostPostUploadActi
wxString PrusaConnect::get_test_ok_msg() const
{
- return _(L("Connection to PrusaConnect works correctly."));
+ return _(L("Connection to Prusa Connect works correctly."));
}
wxString PrusaConnect::get_test_failed_msg(wxString& msg) const
{
- return GUI::from_u8((boost::format("%s: %s")
- % _utf8(L("Could not connect to PrusaConnect"))
- % std::string(msg.ToUTF8())).str());
+ return GUI::format_wxstr("%s: %s", _L("Could not connect to Prusa Connect"), msg);
}
-
}
diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp
index c2046dcf03..d9172f3225 100644
--- a/src/slic3r/Utils/OctoPrint.hpp
+++ b/src/slic3r/Utils/OctoPrint.hpp
@@ -55,30 +55,6 @@ private:
#endif
};
-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 &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
{
@@ -94,7 +70,7 @@ public:
virtual PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
// gets possible storage to be uploaded to. This allows different printer to have different storage. F.e. local vs sdcard vs usb.
- bool get_storage(wxArrayString& /* storage */) const override;
+ bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override;
protected:
bool test(wxString& curl_msg) const override;
bool validate_version_text(const boost::optional& version_text) const override;
@@ -106,6 +82,12 @@ protected:
bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const override;
#endif
+ // Host authorization type.
+ AuthorizationType m_authorization_type;
+ // username and password for HTTP Digest Authentization (RFC RFC2617)
+ std::string m_username;
+ std::string m_password;
+
private:
bool test_with_method_check(wxString& curl_msg, bool& use_put) const;
bool put_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
@@ -113,11 +95,7 @@ private:
#ifdef WIN32
bool test_with_resolved_ip_and_method_check(wxString& curl_msg, bool& use_put) const;
#endif
- // Host authorization type.
- AuthorizationType m_authorization_type;
- // username and password for HTTP Digest Authentization (RFC RFC2617)
- std::string m_username;
- std::string m_password;
+
bool m_show_after_message;
#if 0
@@ -139,6 +117,22 @@ protected:
void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const override;
};
+class SL1Host : public PrusaLink
+{
+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& version_text) const override;
+};
+
}
#endif
diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp
index c39f86288e..becaf138b4 100644
--- a/src/slic3r/Utils/PrintHost.hpp
+++ b/src/slic3r/Utils/PrintHost.hpp
@@ -66,7 +66,7 @@ public:
virtual bool get_printers(wxArrayString & /* printers */) const { return false; }
// Support for PrusaLink uploading to different storage. Not supported by other print hosts.
// Returns false if not supported or fail.
- virtual bool get_storage(wxArrayString& /* storage */) const { return false; }
+ virtual bool get_storage(wxArrayString& /*storage_path*/, wxArrayString& /*storage_name*/) const { return false; }
static PrintHost* get_print_host(DynamicPrintConfig *config);