diff --git a/resources/icons/open_browser.svg b/resources/icons/open_browser.svg
new file mode 100644
index 000000000..a19f62268
--- /dev/null
+++ b/resources/icons/open_browser.svg
@@ -0,0 +1,61 @@
+
+
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 8203f30ca..fd752873d 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -70,6 +70,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage)
static const t_config_enum_values s_keys_map_PrintHostType {
{ "prusalink", htPrusaLink },
+ { "prusaconnect", htPrusaConnect },
{ "octoprint", htOctoPrint },
{ "duet", htDuet },
{ "flashair", htFlashAir },
@@ -1938,6 +1939,7 @@ void PrintConfigDef::init_fff_params()
"the kind of the host.");
def->enum_keys_map = &ConfigOptionEnum::get_enum_values();
def->enum_values.push_back("prusalink");
+ def->enum_values.push_back("prusaconnect");
def->enum_values.push_back("octoprint");
def->enum_values.push_back("duet");
def->enum_values.push_back("flashair");
@@ -1945,6 +1947,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("repetier");
def->enum_values.push_back("mks");
def->enum_labels.push_back("PrusaLink");
+ def->enum_labels.push_back("PrusaConnect");
def->enum_labels.push_back("OctoPrint");
def->enum_labels.push_back("Duet");
def->enum_labels.push_back("FlashAir");
@@ -1953,7 +1956,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back("MKS");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
- def->set_default_value(new ConfigOptionEnum(htOctoPrint));
+ def->set_default_value(new ConfigOptionEnum(htPrusaLink));
def = this->add("only_retract_when_crossing_perimeters", coBool);
def->label = L("Only retract when crossing perimeters");
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index 13dab3fe1..7f8d5df12 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -43,7 +43,7 @@ enum class MachineLimitsUsage {
};
enum PrintHostType {
- htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS
+ htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS
};
enum AuthorizationType {
diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp
index 5692c880f..419d48d5b 100644
--- a/src/slic3r/GUI/Field.cpp
+++ b/src/slic3r/GUI/Field.cpp
@@ -1158,10 +1158,6 @@ void Choice::set_value(const boost::any& value, bool change_event)
}
case coEnum: {
int val = boost::any_cast(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_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern")
{
std::string key;
@@ -1240,10 +1236,7 @@ boost::any& Choice::get_value()
if (m_opt.type == coEnum)
{
- 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_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") {
+ if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") {
const std::string& key = m_opt.enum_values[field->GetSelection()];
m_value = int(ConfigOptionEnum::get_enum_values().at(key));
}
diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp
index b2e9bf008..b440b84de 100644
--- a/src/slic3r/GUI/NotificationManager.cpp
+++ b/src/slic3r/GUI/NotificationManager.cpp
@@ -422,7 +422,7 @@ void NotificationManager::PopNotification::init()
count_spaces();
count_lines();
- if (m_lines_count == 3)
+ if (m_lines_count == m_normal_lines_count + 1)
m_multiline = true;
m_notification_start = GLCanvas3D::timestamp_now();
if (m_state == EState::Unknown)
@@ -431,8 +431,8 @@ void NotificationManager::PopNotification::init()
void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui)
{
m_window_height = m_multiline ?
- std::max(m_lines_count, (size_t)2) * m_line_height :
- 2 * m_line_height;
+ std::max(m_lines_count, m_normal_lines_count) * m_line_height :
+ m_normal_lines_count * m_line_height;
m_window_height += 1 * m_line_height; // top and bottom
}
@@ -444,19 +444,20 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons
float shift_y = m_line_height;
std::string line;
- for (size_t i = 0; i < (m_multiline ? m_endlines.size() : std::min(m_endlines.size(), (size_t)2)); i++) {
+ for (size_t i = 0; i < (m_multiline ? m_endlines.size() : std::min(m_endlines.size(), m_normal_lines_count)); i++) {
assert(m_endlines.size() > i && m_text1.size() >= m_endlines[i]);
line.clear();
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) {
- if (i == 1 && m_endlines.size() > 2 && !m_multiline) {
+ if (i == m_normal_lines_count - 1 && m_endlines.size() > m_normal_lines_count && !m_multiline) {
// second line with "more" hypertext
- line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
- while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) {
+ assert(m_normal_lines_count - 2 >= 0);
+ line = m_text1.substr(m_endlines[m_normal_lines_count - 2] + (m_text1[m_endlines[m_normal_lines_count - 2]] == '\n' || m_text1[m_endlines[m_normal_lines_count - 2]] == ' ' ? 1 : 0), m_endlines[m_normal_lines_count - 1] - m_endlines[m_normal_lines_count - 2] - (m_text1[m_endlines[m_normal_lines_count - 2]] == '\n' || m_text1[m_endlines[m_normal_lines_count - 2]] == ' ' ? 1 : 0));
+ while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((" [" + _u8L("More") + "]").c_str()).x) {
line = line.substr(0, line.length() - 1);
}
- line += "..";
+ line += " ";//"..";
}
else {
// regural line
@@ -469,18 +470,18 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons
}
}
//hyperlink text
- if (!m_multiline && m_lines_count > 2) {
- render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + shift_y, _u8L("More"), true);
+ if (!m_multiline && m_lines_count > m_normal_lines_count) {
+ render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + (m_normal_lines_count -1) *shift_y, "[" + _u8L("More") + "]", true);
}
else if (!m_hypertext.empty()) {
render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty() ? "" : " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext);
}
// text2
- if (!m_text2.empty() && (m_multiline|| m_lines_count <= 2)) {
+ if (!m_text2.empty() && (m_multiline|| m_lines_count <= m_normal_lines_count)) {
starting_y += (m_endlines.size() - 1) * shift_y;
last_end = 0;
- for (size_t i = 0; i < (m_multiline ? m_endlines2.size() : 2); i++) {
+ for (size_t i = 0; i < (m_multiline ? m_endlines2.size() : m_normal_lines_count); i++) {
if (i == 0) //first line X is shifted by hypertext
ImGui::SetCursorPosX(x_offset + ImGui::CalcTextSize((line + m_hypertext + (line.empty() ? " " : " ")).c_str()).x);
else
@@ -514,7 +515,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui,
{
if (more)
{
- m_multiline = true;
+ on_more_hypertext_click();
set_next_window_size(imgui);
}
else if (on_text_click()) {
@@ -635,6 +636,11 @@ bool NotificationManager::PopNotification::on_text_click()
return m_data.callback(m_evt_handler);
return false;
}
+void NotificationManager::PopNotification::on_more_hypertext_click()
+{
+ m_multiline = true;
+}
+
void NotificationManager::PopNotification::update(const NotificationData& n)
{
m_text1 = n.text1;
@@ -875,7 +881,7 @@ void NotificationManager::ProgressBarNotification::init()
m_endlines.push_back(0);
}
if(m_lines_count >= 2) {
- m_lines_count = 3;
+ m_lines_count = 3;
m_multiline = true;
while (m_endlines.size() < 3)
m_endlines.push_back(m_endlines.back());
@@ -1058,8 +1064,19 @@ void NotificationManager::ProgressBarWithCancelNotification::render_bar(ImGuiWra
//------PrintHostUploadNotification----------------
void NotificationManager::PrintHostUploadNotification::init()
{
- ProgressBarNotification::init();
- if (m_state == EState::NotFading && m_uj_state == UploadJobState::PB_COMPLETED)
+ if (is_finished())
+ return;
+ // count_spaces before text - generate_text needs to know width of line
+ count_spaces();
+ generate_text();
+
+ if (m_uj_state == UploadJobState::PB_COMPLETED || m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) {
+ PopNotification::init();
+ m_multiline = m_more_hypertext_used;
+ } else
+ ProgressBarNotification::init();
+
+ if (m_state == EState::NotFading && (m_uj_state == UploadJobState::PB_COMPLETED || m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING))
m_state = EState::Shown;
}
void NotificationManager::PrintHostUploadNotification::count_spaces()
@@ -1068,40 +1085,147 @@ void NotificationManager::PrintHostUploadNotification::count_spaces()
m_line_height = ImGui::CalcTextSize("A").y;
m_left_indentation = m_line_height;
- if (m_uj_state == UploadJobState::PB_ERROR) {
+ if (m_uj_state == UploadJobState::PB_ERROR || m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) {
std::string text;
- text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker);
+ //text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); its always progressbar level (not error or warning)
+ text = (m_uj_state == UploadJobState::PB_ERROR ? 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_offset = m_line_height * 6;
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;
+ } else if (m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) {
+ ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
+ backcolor.x += 0.3f;
+ backcolor.y += 0.15f;
+ push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity);
+ return true;
}
return false;
}
+
+void NotificationManager::PrintHostUploadNotification::generate_text()
+{
+ auto shorten_to_line = [this](const std::string& text, bool dots) -> std::string {
+ std::string line = text;
+ bool did_shorten = false;
+ while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset) {
+ line = line.substr(0, line.length() - 1);
+ did_shorten = true;
+ }
+ if (did_shorten && dots) {
+ line = line.substr(0, line.length() - 2);
+ line += "...";
+ }
+ return line;
+ };
+
+ // whole text is no longer than 2 lines, filename is max 1 line long.
+ std::string rest = " -> " + (m_original_host == m_host ? m_host : m_host + " (" + m_original_host + ")");
+ std::string line1;
+ if (ImGui::CalcTextSize(m_filename.c_str()).x > m_window_width - m_window_width_offset) {
+ line1 = shorten_to_line(m_filename, true);
+ } else {
+ line1 = shorten_to_line(m_filename + rest, false);
+ int over = line1.size() - m_filename.size();
+ if (over < 0)
+ over = 0;
+ if (over < rest.size())
+ rest = rest.substr(over);
+ else if (over >= rest.size())
+ rest.clear();
+ }
+ std::string line2 = shorten_to_line(rest, true);
+
+ // ... if in total that makes more than 1 line, whole notification will behave as 3 line notification (as base height)
+ if (ImGui::CalcTextSize((line1 + line2).c_str()).x > m_window_width - m_window_width_offset)
+ m_normal_lines_count = 3;
+ else
+ m_normal_lines_count = 2;
+
+ if (m_uj_state == UploadJobState::PB_COMPLETED || m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING)
+ m_text1 = line1 + line2 + "\n" + _u8L("COMPLETED") + "\n" + m_status_message;
+ else
+ m_text1 = line1 + line2;
+}
+
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();
+ if (m_complete_on_100 && percent >= 1.0f) {
+ complete();
} else if (percent < 0.0f) {
error();
- } else {
+ } else if (m_uj_state != UploadJobState::PB_COMPLETED && m_uj_state != UploadJobState::PB_COMPLETED_WITH_WARNING){
+ if (m_percentage > 1.f)
+ m_percentage = 1.f;
m_uj_state = UploadJobState::PB_PROGRESS;
m_has_cancel_button = true;
}
}
+
+void NotificationManager::PrintHostUploadNotification::complete()
+{
+ m_uj_state = UploadJobState::PB_COMPLETED;
+ m_has_cancel_button = false;
+ init();
+}
+
+void NotificationManager::PrintHostUploadNotification::complete_with_warning()
+{
+ m_uj_state = UploadJobState::PB_COMPLETED_WITH_WARNING;
+ m_has_cancel_button = false;
+ init();
+}
+
+void NotificationManager::PrintHostUploadNotification::render_text(ImGuiWrapper& imgui,const float win_size_x, const float win_size_y,const float win_pos_x, const float win_pos_y)
+{
+ // If not completed, the text rendering is very similar to progressbar notification except it doesnt use m_multiline to decide.
+ // If completed, whole text is part of m_text_1 and is rendered by PopNotification function.
+
+ if (m_uj_state != UploadJobState::PB_COMPLETED && m_uj_state != UploadJobState::PB_COMPLETED_WITH_WARNING) {
+ // hypertext is not rendered at all. If it is needed, it needs to be added here.
+ // m_endlines should have endline for each line and then for hypertext thus m_endlines[1] should always be in m_text1
+ if (m_endlines[0] != m_endlines[1]) {
+ assert(m_text1.size() >= m_endlines[0] || m_text1.size() >= m_endlines[1]);
+ if (m_endlines[0] > m_text1.size() || m_endlines[1] > m_text1.size())
+ return;
+ // two lines text (what doesnt fit, wont show), one line bar
+ ImGui::SetCursorPosX(m_left_indentation);
+ ImGui::SetCursorPosY(m_line_height / 4);
+ imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
+ ImGui::SetCursorPosX(m_left_indentation);
+ ImGui::SetCursorPosY(m_line_height + m_line_height / 4);
+ std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
+ imgui.text(line.c_str());
+ // uncomment only if close and stop button should be next to each other
+ //if (m_has_cancel_button)
+ // render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
+ render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
+ }
+ else {
+ assert(m_text1.size() >= m_endlines[0]);
+ if (m_endlines[0] > m_text1.size())
+ return;
+ //one line text, one line bar
+ ImGui::SetCursorPosX(m_left_indentation);
+ ImGui::SetCursorPosY(/*win_size_y / 2 - win_size_y / 6 -*/ m_line_height / 4);
+ imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
+ if (m_has_cancel_button)
+ render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
+ render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
+ }
+ } else
+ PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
+}
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;
@@ -1117,6 +1241,11 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper&
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_RESOLVING:
+ text = _u8L("RESOLVING ADDRESS");
+ 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_ERROR:
text = _u8L("ERROR");
ImGui::SetCursorPosX(m_left_indentation);
@@ -1128,9 +1257,8 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper&
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));
+ case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED_WITH_WARNING:
+ // whole text with both "COMPLETED" and status message is generated in generate_text()
break;
}
@@ -1145,8 +1273,23 @@ void NotificationManager::PrintHostUploadNotification::render_left_sign(ImGuiWra
ImGui::SetCursorPosX(m_line_height / 3);
ImGui::SetCursorPosY(m_window_height / 2 - m_line_height);
imgui.text(text.c_str());
+ } else if (m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) {
+ std::string text;
+ text = ImGui::WarningMarker;
+ ImGui::SetCursorPosX(m_line_height / 3);
+ ImGui::SetCursorPosY(m_window_height / 2 - m_line_height);
+ imgui.text(text.c_str());
}
}
+
+void NotificationManager::PrintHostUploadNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
+{
+ if (m_has_cancel_button)
+ render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
+ else
+ ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
+}
+
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);
@@ -1157,6 +1300,56 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu
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 - win_size.x / 10.f, win_pos.y),
+ ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)),
+ 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;
+ }
+ 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 * 2.75f);
+ 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 * 2.35f);
+ ImGui::SetCursorPosY(0);
+ if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
+ {
+ wxGetApp().printhost_job_queue().cancel(m_job_id - 1);
+ }
+ ImGui::PopStyleColor(5);
+
+ // bellow is version where both close and stop button are rendered next to each other
+
+ /*
+ 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;
@@ -1197,6 +1390,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu
wxGetApp().printhost_job_queue().cancel(m_job_id - 1);
}
ImGui::PopStyleColor(5);
+ */
}
//------UpdatedItemsInfoNotification-------
void NotificationManager::UpdatedItemsInfoNotification::count_spaces()
@@ -1847,7 +2041,10 @@ void NotificationManager::push_upload_job_notification(int id, float filesize,
return;
}
}
- std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host);
+ // filename is created from boost::filesystem::path.string() which if created by path / "file" return \\ as folder division. But could also contain / as folder division. Lets unite this into "/" only.
+ std::string correct_filename(filename);
+ std::replace(correct_filename.begin(), correct_filename.end(), '\\', '/');
+ std::string text = correct_filename + " -> " + host;
NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, text };
push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0, id, filesize, filename, host), 0);
}
@@ -1879,6 +2076,62 @@ void NotificationManager::set_upload_job_notification_host(int id, const std::st
}
}
}
+
+void NotificationManager::set_upload_job_notification_status(int id, const std::string& status)
+{
+ for (std::unique_ptr& notification : m_pop_notifications) {
+ if (notification->get_type() == NotificationType::PrintHostUpload) {
+ PrintHostUploadNotification* phun = dynamic_cast(notification.get());
+ if (phun->compare_job_id(id)) {
+ phun->set_status(status);
+ wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
+ break;
+ }
+ }
+ }
+}
+
+void NotificationManager::set_upload_job_notification_comp_on_100(int id, bool comp)
+{
+ for (std::unique_ptr& notification : m_pop_notifications) {
+ if (notification->get_type() == NotificationType::PrintHostUpload) {
+ PrintHostUploadNotification* phun = dynamic_cast(notification.get());
+ if (phun->compare_job_id(id)) {
+ phun->set_complete_on_100(comp);
+ break;
+ }
+ }
+ }
+}
+
+void NotificationManager::set_upload_job_notification_completed(int id)
+{
+ for (std::unique_ptr& notification : m_pop_notifications) {
+ if (notification->get_type() == NotificationType::PrintHostUpload) {
+ PrintHostUploadNotification* phun = dynamic_cast(notification.get());
+ if (phun->compare_job_id(id)) {
+ phun->complete();
+ wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
+ break;
+ }
+ }
+ }
+}
+
+void NotificationManager::set_upload_job_notification_completed_with_warning(int id)
+{
+ for (std::unique_ptr& notification : m_pop_notifications) {
+ if (notification->get_type() == NotificationType::PrintHostUpload) {
+ PrintHostUploadNotification* phun = dynamic_cast(notification.get());
+ if (phun->compare_job_id(id)) {
+ phun->complete_with_warning();
+ 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& notification : m_pop_notifications) {
diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp
index 23d4d20b0..3d0e3fbee 100644
--- a/src/slic3r/GUI/NotificationManager.hpp
+++ b/src/slic3r/GUI/NotificationManager.hpp
@@ -206,6 +206,10 @@ public:
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 set_upload_job_notification_host(int id, const std::string& host);
+ void set_upload_job_notification_status(int id, const std::string& status);
+ void set_upload_job_notification_comp_on_100(int id, bool comp);
+ void set_upload_job_notification_completed(int id);
+ void set_upload_job_notification_completed_with_warning(int id);
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);
// Download App progress
@@ -352,7 +356,8 @@ private:
// Hypertext action, returns true if notification should close.
// Action is stored in NotificationData::callback as std::function
virtual bool on_text_click();
-
+ // "More" hypertext to show full message
+ virtual void on_more_hypertext_click();
// Part of init(), counts horizontal spacing like left indentation
virtual void count_spaces();
// Part of init(), counts end lines
@@ -413,6 +418,8 @@ private:
// True if minimized button is rendered, helps to decide where is area for invisible close button
bool m_minimize_b_visible { false };
size_t m_lines_count{ 1 };
+ // Number of lines to be shown when m_multiline = false. If m_lines_count = m_normal_lines_count + 1 -> all lines are shown,
+ size_t m_normal_lines_count { 2 };
// Target for wxWidgets events sent by clicking on the hyperlink available at some notifications.
wxEvtHandler* m_evt_handler;
};
@@ -506,7 +513,9 @@ private:
PB_PROGRESS,
PB_ERROR,
PB_CANCELLED,
- PB_COMPLETED
+ PB_COMPLETED,
+ PB_COMPLETED_WITH_WARNING,
+ PB_RESOLVING
};
PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize, const std::string& filename, const std::string& host)
:ProgressBarNotification(n, id_provider, evt_handler)
@@ -514,37 +523,57 @@ private:
, m_file_size(filesize)
, m_filename(filename)
, m_host(host)
+ , m_original_host(host)
{
m_has_cancel_button = true;
- set_percentage(percentage);
+ if (percentage != 0.f)
+ 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; }
- void set_host(const std::string& host) { m_host = host; update({ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, get_upload_job_text(m_id, m_filename, m_host)}); }
+ void set_host(const std::string& host) { m_host = host; init(); }
std::string get_host() const { return m_host; }
+ void set_status(const std::string& status) { m_status_message = status; init(); }
+ void set_complete_on_100(bool val) { m_complete_on_100 = val; }
+ void complete();
+ void complete_with_warning();
protected:
void init() override;
void count_spaces() override;
bool push_background_color() override;
+ virtual void render_text(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_bar(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
+ virtual void render_close_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_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;
+
+ void generate_text();
+ void on_more_hypertext_click() override { ProgressBarNotification::on_more_hypertext_click(); m_more_hypertext_used = true; }
+
// 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 };
+ UploadJobState m_uj_state{ UploadJobState::PB_RESOLVING };
std::string m_filename;
std::string m_host;
+ std::string m_original_host; // when hostname is resolved into ip address, we can still display original hostname (that user inserted)
+ std::string m_status_message;
+ bool m_more_hypertext_used { false };
+ // When m_complete_on_100 is set to false - percent >= 1 wont switch to PB_COMPLETED state.
+ bool m_complete_on_100 { true };
};
class SlicingProgressNotification : public ProgressBarNotification
diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp
index dfebad442..b6cd81997 100644
--- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp
+++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp
@@ -67,14 +67,15 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str
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 values in parent (PhysicalPrinterDialog)
+ }
- }
// update PrinterTechnology if it was changed
if (m_presets_list->set_printer_technology(preset->printer_technology()))
m_parent->set_printer_technology(preset->printer_technology());
+ else
+ m_parent->update(true);
update_full_printer_name();
});
@@ -155,8 +156,7 @@ void PresetForPrinter::on_sys_color_changed()
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())
+ m_printer("", wxGetApp().preset_bundle->physical_printers.default_config())
{
SetFont(wxGetApp().normal_font());
#ifndef _WIN32
@@ -241,6 +241,11 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_
m_printer_name->SelectAll();
}
+ const wxSize& bestsize = this->GetBestSize();
+ const wxSize& size = wxSize(bestsize.x, 1.1f * bestsize.y);
+ this->SetSize(size);
+ this->Layout();
+
this->CenterOnScreen();
}
@@ -480,19 +485,32 @@ void PhysicalPrinterDialog::update(bool 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");
+ }
+ }
+ }
}
}
@@ -514,7 +532,9 @@ void PhysicalPrinterDialog::update(bool printer_change)
update_printhost_buttons();
- this->SetSize(this->GetBestSize());
+ const wxSize& bestsize= this->GetBestSize();
+ const wxSize& size = wxSize( bestsize.x, 1.1f * bestsize.y);
+ this->SetSize(size);
this->Layout();
}
@@ -522,17 +542,32 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
{
if (m_presets.empty())
return;
- bool all_presets_are_from_mk3_family = true;
+ struct {
+ bool supported { true };
+ wxString label;
+ } link, connect;
+ // allowed models are: all MINI, all MK3 and newer, MK2.5 and MK2.5S
+ 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")
+ || boost::starts_with(model, "MK2.5")
+ //|| boost::starts_with(model, "MK2.5S")
+ );
+ };
+ // allowed models are: all MK3/S and MK2.5/S
+ auto model_supports_prusaconnect = [](const std::string& model) {
+ return model.size() >= 3 &&
+ (boost::starts_with(model, "MK3")
+ || boost::starts_with(model, "MK2.5")
+ );
+ };
+ // set all_presets_are_prusalink_supported
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"));
- };
+ std::string model_id = preset->config.opt_string("printer_model");
if (preset->vendor) {
if (preset->vendor->name == "Prusa Research") {
const std::vector& models = preset->vendor->models;
@@ -541,37 +576,73 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
if (it != models.end() && model_supports_prusalink(it->family))
continue;
}
- } else if (model_supports_prusalink(model_id))
+ }
+ else if (model_supports_prusalink(model_id))
continue;
}
- all_presets_are_from_mk3_family = false;
+ link.supported = false;
break;
}
- Field* ht = m_optgroup->get_field("host_type");
+ // set all_presets_are_prusaconnect_supported
+ for (PresetForPrinter* prstft : m_presets) {
+ std::string preset_name = prstft->get_preset_name();
+ Preset* preset = wxGetApp().preset_bundle->printers.find_preset(preset_name);
+ if (!preset) {
+ connect.supported = false;
+ break;
+ }
+ std::string model_id = preset->config.opt_string("printer_model");
+ if (preset->vendor && preset->vendor->name != "Prusa Research") {
+ connect.supported = false;
+ break;
+ }
+ if (preset->vendor && preset->vendor->name != "Prusa Research") {
+ connect.supported = false;
+ break;
+ }
+ // model id should be enough for this case
+ if (!model_supports_prusaconnect(model_id)) {
+ connect.supported = false;
+ break;
+ }
+ }
+ Field* ht = m_optgroup->get_field("host_type");
wxArrayString types;
+ int last_in_conf = m_config->option("host_type")->getInt(); // this is real position in last choice
+
+
// 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;
+ if (ht->m_opt.enum_values[i] == "prusalink"){
+ link.label = _(ht->m_opt.enum_labels[i]);
+ if (!link.supported)
+ continue;
+ }
+ if (ht->m_opt.enum_values[i] == "prusaconnect") {
+ connect.label = _(ht->m_opt.enum_labels[i]);
+ if (!connect.supported)
+ continue;
+ }
+
types.Add(_(ht->m_opt.enum_labels[i]));
}
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 ? 0 : last_in_conf);
+ choice->set_value(index_in_choice);
+ if (link.supported && link.label == _(ht->m_opt.enum_labels[index_in_choice]))
+ m_config->set_key_value("host_type", new ConfigOptionEnum(htPrusaLink));
+ else if (link.supported && link.label == _(ht->m_opt.enum_labels[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) || (!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>("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;
+ }
}
@@ -591,7 +662,7 @@ void PhysicalPrinterDialog::update_full_printer_names()
void PhysicalPrinterDialog::set_printer_technology(PrinterTechnology pt)
{
m_config->set_key_value("printer_technology", new ConfigOptionEnum(pt));
- update();
+ update(true);
}
PrinterTechnology PhysicalPrinterDialog::get_printer_technology()
diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp
index d8bb70d3c..bef74c22b 100644
--- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp
+++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp
@@ -99,7 +99,6 @@ protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override;
- bool had_all_mk3;
};
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index e73eedfeb..68a1bc01d 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -6469,12 +6469,31 @@ void Plater::send_gcode()
wxBusyCursor wait;
upload_job.printhost->get_groups(groups);
}
-
- 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;
+ {
+ wxBusyCursor wait;
+ try {
+ upload_job.printhost->get_storage(storage);
+ } 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);
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));
}
diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp
index 200cd8a30..1d3c275e8 100644
--- a/src/slic3r/GUI/PresetComboBoxes.cpp
+++ b/src/slic3r/GUI/PresetComboBoxes.cpp
@@ -344,6 +344,14 @@ void PresetComboBox::add_physical_printer()
update();
}
+void PresetComboBox::open_physical_printer_url()
+{
+ const PhysicalPrinter& pp = m_preset_bundle->physical_printers.get_selected_printer();
+ std::string host = pp.config.opt_string("print_host");
+ assert(!host.empty());
+ wxGetApp().open_browser_with_warning_dialog(host);
+}
+
bool PresetComboBox::del_physical_printer(const wxString& note_string/* = wxEmptyString*/)
{
const std::string& printer_name = m_preset_bundle->physical_printers.get_selected_full_printer_name();
@@ -752,6 +760,14 @@ void PlaterPresetComboBox::show_edit_menu()
append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "",
[this](wxCommandEvent&) { this->edit_physical_printer(); }, "cog", menu, []() { return true; }, wxGetApp().plater());
+ const PhysicalPrinter& pp = m_preset_bundle->physical_printers.get_selected_printer();
+ std::string host = pp.config.opt_string("print_host");
+ if (!host.empty()) {
+ append_menu_item(menu, wxID_ANY, _L("Open physical printer URL"), "",
+ [this](wxCommandEvent&) { this->open_physical_printer_url(); }, "open_browser", menu, []() { return true; }, wxGetApp().plater());
+ }
+
+
append_menu_item(menu, wxID_ANY, _L("Delete physical printer"), "",
[this](wxCommandEvent&) { this->del_physical_printer(); }, "cross", menu, []() { return true; }, wxGetApp().plater());
}
diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp
index c1408b295..d0abbe203 100644
--- a/src/slic3r/GUI/PresetComboBoxes.hpp
+++ b/src/slic3r/GUI/PresetComboBoxes.hpp
@@ -65,6 +65,7 @@ public:
void edit_physical_printer();
void add_physical_printer();
+ void open_physical_printer_url();
bool del_physical_printer(const wxString& note_string = wxEmptyString);
void show_modif_preset_separately() { m_show_modif_preset_separately = true; }
diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp
index ec425ae79..dab033cfb 100644
--- a/src/slic3r/GUI/PrintHostDialogs.cpp
+++ b/src/slic3r/GUI/PrintHostDialogs.cpp
@@ -27,6 +27,7 @@
#include "libslic3r/AppConfig.hpp"
#include "NotificationManager.hpp"
#include "ExtraRenderers.hpp"
+#include "format.hpp"
namespace fs = boost::filesystem;
@@ -35,12 +36,14 @@ namespace GUI {
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)
+PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage)
: 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)
, post_upload_action(PrintHostPostUploadAction::None)
{
#ifdef __APPLE__
@@ -65,6 +68,23 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
combo_groups->SetValue(recent_group);
}
+ if (combo_storage != nullptr) {
+ // PrusaLink specific: User needs to choose a 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());
+ 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){
+ // PrusaLink specific: Show which storage has been detected.
+ auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage: ") + storage.front());
+ content_sizer->Add(label_group);
+ m_preselected_storage = storage.front();
+ }
+
+
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 += '/';
@@ -97,6 +117,16 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
});
txt_filename->SetFocus();
+ if (post_actions.has(PrintHostPostUploadAction::QueuePrint)) {
+ auto* btn_print = add_button(wxID_ADD, false, _L("Upload to Queue"));
+ btn_print->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
+ if (validate_path(txt_filename->GetValue())) {
+ post_upload_action = PrintHostPostUploadAction::QueuePrint;
+ EndDialog(wxID_OK);
+ }
+ });
+ }
+
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&) {
@@ -162,6 +192,13 @@ std::string PrintHostSendDialog::group() const
}
}
+std::string PrintHostSendDialog::storage() const
+{
+ if (!combo_storage)
+ return GUI::format("%1%", m_preselected_storage);
+ return boost::nowide::narrow(combo_storage->GetValue());
+}
+
void PrintHostSendDialog::EndModal(int ret)
{
if (ret == wxID_OK) {
@@ -180,6 +217,10 @@ void PrintHostSendDialog::EndModal(int ret)
wxString group = combo_groups->GetValue();
app_config->set("recent", CONFIG_KEY_GROUP, into_u8(group));
}
+ if (combo_storage != nullptr) {
+ wxString storage = combo_storage->GetValue();
+ app_config->set("recent", CONFIG_KEY_STORAGE, into_u8(storage));
+ }
}
MsgDialog::EndModal(ret);
@@ -190,7 +231,7 @@ void PrintHostSendDialog::EndModal(int ret)
wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
-wxDEFINE_EVENT(EVT_PRINTHOST_RESOLVE, PrintHostQueueDialog::Event);
+wxDEFINE_EVENT(EVT_PRINTHOST_INFO, PrintHostQueueDialog::Event);
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id)
: wxEvent(winid, eventType)
@@ -206,7 +247,14 @@ PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error)
: wxEvent(winid, eventType)
, job_id(job_id)
- , error(std::move(error))
+ , status(std::move(error))
+{}
+
+PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString tag, wxString status)
+ : wxEvent(winid, eventType)
+ , job_id(job_id)
+ , tag(std::move(tag))
+ , status(std::move(status))
{}
wxEvent *PrintHostQueueDialog::Event::Clone() const
@@ -219,17 +267,17 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
, 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)
- , on_resolve_evt(this, EVT_PRINTHOST_RESOLVE, &PrintHostQueueDialog::on_resolve, this)
+ , on_info_evt(this, EVT_PRINTHOST_INFO, &PrintHostQueueDialog::on_info, this)
{
const auto em = GetTextExtent("m").x;
auto *topsizer = new wxBoxSizer(wxVERTICAL);
std::vector widths;
- widths.reserve(6);
+ widths.reserve(7);
if (!load_user_data(UDT_COLS, widths)) {
widths.clear();
- for (size_t i = 0; i < 6; i++)
+ for (size_t i = 0; i < 7; i++)
widths.push_back(-1);
}
@@ -252,7 +300,8 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
append_text_column(_L("Host"), widths[3]);
append_text_column(_CTX(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);
+ append_text_column(_L("Message"), widths[6]);
+ //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"));
@@ -423,7 +472,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.error.ToUTF8())).str());
+ auto errormsg = from_u8((boost::format("%1%\n%2%") % _utf8(L("Error uploading to print host:")) % std::string(evt.status.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
@@ -452,15 +501,27 @@ void PrintHostQueueDialog::on_cancel(Event &evt)
wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()));
}
-void PrintHostQueueDialog::on_resolve(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");
- // wxstring in event is called error, but it should contain new host string.
- wxVariant hst(evt.error);
- // todo: set variant
- job_list->SetValue(hst,evt.job_id,COL_HOST);
- wxGetApp().notification_manager()->set_upload_job_notification_host(evt.job_id + 1, boost::nowide::narrow(evt.error));
+ if (evt.tag == L"resolve") {
+ wxVariant hst(evt.status);
+ job_list->SetValue(hst, evt.job_id, COL_HOST);
+ wxGetApp().notification_manager()->set_upload_job_notification_host(evt.job_id + 1, boost::nowide::narrow(evt.status));
+ } else if (evt.tag == L"complete") {
+ wxVariant hst(evt.status);
+ job_list->SetValue(hst, evt.job_id, COL_ERRORMSG);
+ wxGetApp().notification_manager()->set_upload_job_notification_completed(evt.job_id + 1);
+ wxGetApp().notification_manager()->set_upload_job_notification_status(evt.job_id + 1, boost::nowide::narrow(evt.status));
+ } else if(evt.tag == L"complete_with_warning"){
+ wxVariant hst(evt.status);
+ job_list->SetValue(hst, evt.job_id, COL_ERRORMSG);
+ wxGetApp().notification_manager()->set_upload_job_notification_completed_with_warning(evt.job_id + 1);
+ wxGetApp().notification_manager()->set_upload_job_notification_status(evt.job_id + 1, boost::nowide::narrow(evt.status));
+ } 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)
@@ -474,7 +535,6 @@ void PrintHostQueueDialog::get_active_jobs(std::vectordata
}
void PrintHostQueueDialog::save_user_data(int udt)
{
@@ -526,7 +586,7 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector& vector)
}
if (udt & UserDataType::UDT_COLS)
{
- for (size_t i = 0; i < 6; i++)
+ for (size_t i = 0; i < 7; i++)
{
if (!hasget("print_host_queue_dialog_column_" + std::to_string(i), vector))
return false;
diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp
index 9173807b5..80e2a0f48 100644
--- a/src/slic3r/GUI/PrintHostDialogs.hpp
+++ b/src/slic3r/GUI/PrintHostDialogs.hpp
@@ -26,17 +26,20 @@ namespace GUI {
class PrintHostSendDialog : public GUI::MsgDialog
{
public:
- PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups);
+ PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage);
boost::filesystem::path filename() const;
PrintHostPostUploadAction post_action() const;
std::string group() const;
+ std::string storage() const;
virtual void EndModal(int ret) override;
private:
wxTextCtrl *txt_filename;
wxComboBox *combo_groups;
+ wxComboBox* combo_storage;
PrintHostPostUploadAction post_upload_action;
wxString m_valid_suffix;
+ wxString m_preselected_storage;
};
@@ -48,11 +51,13 @@ public:
public:
size_t job_id;
int progress = 0; // in percent
- wxString error;
+ wxString tag;
+ wxString status;
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);
+ Event(wxEventType eventType, int winid, size_t job_id, wxString tag, wxString status);
virtual wxEvent *Clone() const;
};
@@ -108,7 +113,7 @@ private:
EventGuard on_progress_evt;
EventGuard on_error_evt;
EventGuard on_cancel_evt;
- EventGuard on_resolve_evt;
+ EventGuard on_info_evt;
JobState get_state(int idx);
void set_state(int idx, JobState);
@@ -116,7 +121,7 @@ private:
void on_progress(Event&);
void on_error(Event&);
void on_cancel(Event&);
- void on_resolve(Event&);
+ void on_info(Event&);
// This vector keep adress and filename of uploads. It is used when checking for running uploads during exit.
std::vector> upload_names;
void save_user_data(int);
@@ -126,7 +131,7 @@ private:
wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
wxDECLARE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
-wxDECLARE_EVENT(EVT_PRINTHOST_RESOLVE, PrintHostQueueDialog::Event);
+wxDECLARE_EVENT(EVT_PRINTHOST_INFO, PrintHostQueueDialog::Event);
}}
#endif
diff --git a/src/slic3r/Utils/AstroBox.cpp b/src/slic3r/Utils/AstroBox.cpp
index b512e301a..a2b5bca04 100644
--- a/src/slic3r/Utils/AstroBox.cpp
+++ b/src/slic3r/Utils/AstroBox.cpp
@@ -92,7 +92,7 @@ wxString AstroBox::get_test_failed_msg (wxString &msg) const
% _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, ResolveFn resolve_fn) const
+bool AstroBox::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
const char *name = get_name();
diff --git a/src/slic3r/Utils/AstroBox.hpp b/src/slic3r/Utils/AstroBox.hpp
index 67fb86130..72ab27322 100644
--- a/src/slic3r/Utils/AstroBox.hpp
+++ b/src/slic3r/Utils/AstroBox.hpp
@@ -23,7 +23,7 @@ public:
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, ResolveFn resolve_fn) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_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; }
diff --git a/src/slic3r/Utils/Bonjour.cpp b/src/slic3r/Utils/Bonjour.cpp
index 11bbcdf5b..e35708381 100644
--- a/src/slic3r/Utils/Bonjour.cpp
+++ b/src/slic3r/Utils/Bonjour.cpp
@@ -963,10 +963,9 @@ void Bonjour::priv::resolve_perform()
};
std::shared_ptr< boost::asio::io_service > io_service(new boost::asio::io_service);
-
std::vector sockets;
-
- // resolve intefaces - from PR#6646
+
+ // resolve interfaces - from PR#6646
std::vector interfaces;
asio::ip::udp::resolver resolver(*io_service);
boost::system::error_code ec;
diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp
index e67e5f31e..229d0c950 100644
--- a/src/slic3r/Utils/Duet.cpp
+++ b/src/slic3r/Utils/Duet.cpp
@@ -54,7 +54,7 @@ wxString Duet::get_test_failed_msg (wxString &msg) const
% std::string(msg.ToUTF8())).str());
}
-bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, ResolveFn resolve_fn) const
+bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
wxString connect_msg;
auto connectionType = connect(connect_msg);
diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp
index 7980994ad..2a91aa853 100644
--- a/src/slic3r/Utils/Duet.hpp
+++ b/src/slic3r/Utils/Duet.hpp
@@ -22,7 +22,7 @@ public:
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, ResolveFn resolve_fn) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_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; }
diff --git a/src/slic3r/Utils/FlashAir.cpp b/src/slic3r/Utils/FlashAir.cpp
index 612c79eda..e54dca58f 100644
--- a/src/slic3r/Utils/FlashAir.cpp
+++ b/src/slic3r/Utils/FlashAir.cpp
@@ -76,7 +76,7 @@ wxString FlashAir::get_test_failed_msg (wxString &msg) const
% _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, ResolveFn resolve_fn) const
+bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
const char *name = get_name();
diff --git a/src/slic3r/Utils/FlashAir.hpp b/src/slic3r/Utils/FlashAir.hpp
index b961ccfa0..ba60644c0 100644
--- a/src/slic3r/Utils/FlashAir.hpp
+++ b/src/slic3r/Utils/FlashAir.hpp
@@ -23,7 +23,7 @@ public:
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, ResolveFn resolve_fn) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_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 {}; }
diff --git a/src/slic3r/Utils/MKS.cpp b/src/slic3r/Utils/MKS.cpp
index 268b291af..109283fc6 100644
--- a/src/slic3r/Utils/MKS.cpp
+++ b/src/slic3r/Utils/MKS.cpp
@@ -62,7 +62,7 @@ wxString MKS::get_test_failed_msg(wxString& msg) const
% std::string(msg.ToUTF8())).str());
}
-bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, ResolveFn resolve_fn) const
+bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
bool res = true;
diff --git a/src/slic3r/Utils/MKS.hpp b/src/slic3r/Utils/MKS.hpp
index 53f071fc3..79143fdd9 100644
--- a/src/slic3r/Utils/MKS.hpp
+++ b/src/slic3r/Utils/MKS.hpp
@@ -22,7 +22,7 @@ public:
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, ResolveFn resolve_fn) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_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; }
diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp
index 41deb83bb..48ad44033 100644
--- a/src/slic3r/Utils/OctoPrint.cpp
+++ b/src/slic3r/Utils/OctoPrint.cpp
@@ -9,6 +9,8 @@
#include
#include
#include
+#include
+#include
#include
@@ -16,7 +18,8 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
-#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/format.hpp"
#include "Http.hpp"
#include "libslic3r/AppConfig.hpp"
#include "Bonjour.hpp"
@@ -28,9 +31,9 @@ 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 {
+#ifdef WIN32
+ // 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)
{
// put ipv6 into [] brackets
@@ -125,8 +128,34 @@ std::string get_host_from_url(const std::string& url_in)
BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to allocate curl_url";
return out;
}
-} //namespace
#endif // WIN32
+std::string escape_string(const std::string& unescaped)
+{
+ std::string ret_val;
+ CURL* curl = curl_easy_init();
+ if (curl) {
+ char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size());
+ if (decoded) {
+ ret_val = std::string(decoded);
+ curl_free(decoded);
+ }
+ curl_easy_cleanup(curl);
+ }
+ return ret_val;
+}
+std::string escape_path_by_element(const boost::filesystem::path& path)
+{
+ std::string ret_val = escape_string(path.filename().string());
+ boost::filesystem::path parent(path.parent_path());
+ while (!parent.empty() && parent.string() != "/") // "/" check is for case "/file.gcode" was inserted. Then boost takes "/" as parent_path.
+ {
+ ret_val = escape_string(parent.filename().string()) + "/" + ret_val;
+ parent = parent.parent_path();
+ }
+ return ret_val;
+}
+} //namespace
+
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
m_host(config->opt_string("print_host")),
@@ -258,10 +287,10 @@ wxString OctoPrint::get_test_failed_msg (wxString &msg) const
% _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, ResolveFn resolve_fn) const
+bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
#ifndef WIN32
- return upload_inner_with_host(upload_data, prorgess_fn, error_fn, resolve_fn);
+ return upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
#else
std::string host = get_host_from_url(m_host);
@@ -271,13 +300,12 @@ 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"){
+ } else if ( GUI::get_app_config()->get("allow_ip_resolve") == "1" && boost::algorithm::ends_with(host, ".local")){
Bonjour("octoprint")
.set_hostname(host)
- .set_retries(10) // number of rounds of queries send
+ .set_retries(5) // number of rounds of queries send
.set_timeout(1) // after each timeout, if there is any answer, the resolving will stop
.on_resolve([&ra = resolved_addr](const std::vector& replies) {
- std::vector resolved_addr;
for (const auto & rpl : replies) {
boost::asio::ip::address ip(rpl.ip);
ra.emplace_back(ip);
@@ -289,21 +317,21 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
if (resolved_addr.empty()) {
// no resolved addresses - try system resolving
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer failed to resolve hostname " << m_host << " into the IP address. Starting upload with system resolving.";
- return upload_inner_with_host(upload_data, prorgess_fn, error_fn, resolve_fn);
+ return upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
} else if (resolved_addr.size() == 1) {
// one address resolved - upload there
- return upload_inner_with_resolved_ip(upload_data, prorgess_fn, error_fn, resolve_fn, resolved_addr.front());
+ return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr.front());
} else if (resolved_addr.size() == 2 && resolved_addr[0].is_v4() != resolved_addr[1].is_v4()) {
// there are just 2 addresses and 1 is ip_v4 and other is ip_v6
// try sending to both. (Then if both fail, show both error msg after second try)
wxString error_message;
- if (!upload_inner_with_resolved_ip(upload_data, prorgess_fn
+ if (!upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn
, [&msg = error_message, resolved_addr](wxString error) { msg = GUI::format_wxstr("%1%: %2%", resolved_addr.front(), error); }
- , resolve_fn, resolved_addr.front())
+ , info_fn, resolved_addr.front())
&&
- !upload_inner_with_resolved_ip(upload_data, prorgess_fn
+ !upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn
, [&msg = error_message, resolved_addr](wxString error) { msg += GUI::format_wxstr("\n%1%: %2%", resolved_addr.back(), error); }
- , resolve_fn, resolved_addr.back())
+ , info_fn, resolved_addr.back())
) {
error_fn(error_message);
@@ -315,16 +343,16 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
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(upload_data, prorgess_fn, error_fn, resolve_fn, resolved_addr[selected_index]);
+ return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr[selected_index]);
}
}
return false;
#endif // WIN32
}
#ifdef WIN32
-bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, ResolveFn resolve_fn, const boost::asio::ip::address& resolved_addr) const
+bool OctoPrint::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
{
- resolve_fn(boost::nowide::widen(resolved_addr.to_string()));
+ info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string()));
// If test fails, test_msg_or_host_ip contains the error message.
// Otherwise on Windows it contains the resolved IP address of the host.
@@ -341,7 +369,7 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr
std::string url = substitute_host(make_url("api/files/local"), resolved_addr.to_string());
bool result = true;
- resolve_fn(boost::nowide::widen(url));
+ info_fn(L"resolve", boost::nowide::widen(url));
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
% name
@@ -356,6 +384,7 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr
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;
})
@@ -379,7 +408,7 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr
}
#endif //WIN32
-bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, ResolveFn resolve_fn) const
+bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
const char* name = get_name();
@@ -414,7 +443,7 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p
// 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
- resolve_fn(test_msg_or_host_ip);
+ info_fn(L"resolve", test_msg_or_host_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;
}
@@ -529,11 +558,12 @@ void SL1Host::set_auth(Http &http) const
}
// PrusaLink
-PrusaLink::PrusaLink(DynamicPrintConfig* config) :
+PrusaLink::PrusaLink(DynamicPrintConfig* config, bool show_after_message) :
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"))
+ m_password(config->opt_string("printhost_password")),
+ m_show_after_message(show_after_message)
{
}
@@ -572,4 +602,528 @@ void PrusaLink::set_auth(Http& http) const
}
}
+#if 0
+bool PrusaLink::version_check(const boost::optional& version_text) const
+{
+ // version_text is in format OctoPrint 1.2.3
+ // true (= use PUT) should return:
+ // PrusaLink 0.7+
+
+ try {
+ if (!version_text)
+ throw Slic3r::RuntimeError("no version_text was given");
+
+ std::vector name_and_version;
+ boost::algorithm::split(name_and_version, *version_text, boost::is_any_of(" "));
+
+ if (name_and_version.size() != 2)
+ throw Slic3r::RuntimeError("invalid version_text");
+
+ Semver semver(name_and_version[1]); // throws Slic3r::RuntimeError when unable to parse
+ if (name_and_version.front() == "PrusaLink" && semver >= Semver(0, 7, 0))
+ return true;
+ } catch (const Slic3r::RuntimeError& ex) {
+ BOOST_LOG_TRIVIAL(error) << std::string("Print host version check failed: ") + ex.what();
+ }
+
+ return false;
+}
+#endif
+
+bool PrusaLink::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("api")) {
+ res = false;
+ return;
+ }
+
+ 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());
+ }
+ }
+ 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;
+}
+
+bool PrusaLink::get_storage(wxArrayString& output) const
+{
+ const char* name = get_name();
+
+ bool res = true;
+ auto url = make_url("api/v1/storage");
+ wxString error_msg;
+
+ struct StorageInfo{
+ wxString name;
+ bool read_only;
+ long long free_space;
+ };
+ std::vector storage;
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage 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 storage: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ error_msg = L"\n\n" + boost::nowide::widen(error);
+ res = false;
+ // If status is 0, the communication with the printer has failed completely (most likely a timeout), if the status is <= 400, it is an error returned by the pritner.
+ // If 0, we can show error to the user now, as we know the communication has failed. (res = true will do the trick.)
+ // if not 0, we must not show error, as not all printers support api/v1/storage endpoint.
+ // So we must be extra careful here, or we might be showing errors on perfectly fine communication.
+ if (status == 0)
+ res = true;
+
+ })
+ .on_complete([&, this](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got storage: %2%") % name % body;
+ try
+ {
+ std::stringstream ss(body);
+ pt::ptree ptree;
+ pt::read_json(ss, ptree);
+
+ // what if there is more structure added in the future? Enumerate all elements?
+ if (ptree.front().first != "storage_list") {
+ res = false;
+ return;
+ }
+ // each storage has own subtree of storage_list
+ for (const auto& section : ptree.front().second) {
+ 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 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.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space.
+ storage.emplace_back(std::move(si));
+ }
+ }
+ }
+ catch (const std::exception&)
+ {
+ res = false;
+ }
+ })
+#ifdef WIN32
+ .ssl_revoke_best_effort(m_ssl_revoke_best_effort)
+
+#endif // WIN32
+ .perform_sync();
+
+ for (const auto& si : storage) {
+ if (!si.read_only && si.free_space > 0)
+ output.push_back(si.name);
+ }
+
+ if (res && output.empty())
+ {
+ if (!storage.empty()) { // otherwise error_msg is already filled
+ 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";
+ }
+ }
+ std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%.%2%"), m_host, error_msg);
+ BOOST_LOG_TRIVIAL(error) << message;
+ throw Slic3r::IOError(message);
+ }
+
+ return res;
+}
+
+bool PrusaLink::test_with_method_check(wxString& msg, bool& use_put) 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("api")) {
+ res = false;
+ return;
+ }
+
+ 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());
+ use_put = false;
+ return;
+ }
+
+ // find capabilities subtree and read upload-by-put
+ for (const auto& section : ptree) {
+ if (section.first == "capabilities") {
+ const auto put_upload = section.second.get_optional("upload-by-put");
+ if (put_upload)
+ use_put = *put_upload;
+ break;
+ }
+ }
+
+ }
+ 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;
+}
+
+
+
+#ifdef WIN32
+bool PrusaLink::test_with_resolved_ip_and_method_check(wxString& msg, bool& use_put) 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;
+ // Msg contains ip string.
+ auto url = substitute_host(make_url("api/version"), GUI::into_u8(msg));
+ msg.Clear();
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
+
+ auto http = Http::get(url);//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 at %2% : %3%, HTTP %4%, body: `%5%`") % name % url % error % status % body;
+ res = false;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&, this](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(info) << 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("api")) {
+ res = false;
+ return;
+ }
+
+ 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());
+ use_put = false;
+ return;
+ }
+
+ // find capabilities subtree and read upload-by-put
+ for (const auto& section : ptree) {
+ if (section.first == "capabilities") {
+ const auto put_upload = section.second.get_optional("upload-by-put");
+ if (put_upload)
+ use_put = *put_upload;
+ break;
+ }
+ }
+
+ }
+ catch (const std::exception&) {
+ res = false;
+ msg = "Could not parse server response";
+ }
+
+ })
+ .ssl_revoke_best_effort(m_ssl_revoke_best_effort)
+ .perform_sync();
+
+ return res;
+}
+
+bool PrusaLink::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
+{
+ info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string()));
+
+ // If test fails, test_msg_or_host_ip contains the error message.
+ // Otherwise on Windows it contains the resolved IP address of the host.
+ // Test_msg already contains resolved ip and will be cleared on start of test().
+ wxString test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
+ bool use_put = false;
+ if (!test_with_resolved_ip_and_method_check(test_msg_or_host_ip, use_put)) {
+ error_fn(std::move(test_msg_or_host_ip));
+ return false;
+ }
+
+ 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();
+ std::string storage_path = (use_put ? "api/v1/files" : "api/files");
+ storage_path += (upload_data.storage.empty() ? "/local" : upload_data.storage);
+ std::string url = substitute_host(make_url(storage_path), resolved_addr.to_string());
+ bool result = true;
+ info_fn(L"resolve", boost::nowide::widen(url));
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, method: %7%")
+ % name
+ % upload_data.source_path
+ % url
+ % upload_filename.string()
+ % upload_parent_path.string()
+ % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
+ % (use_put ? "PUT" : "POST");
+
+ if (use_put)
+ return put_inner(std::move(upload_data), std::move(url), name, prorgess_fn, error_fn, info_fn);
+ return post_inner(std::move(upload_data), std::move(url), name, prorgess_fn, error_fn, info_fn);
+}
+
+#endif //WIN32
+
+bool PrusaLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_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;
+ bool use_put = false;
+ if (!test_with_method_check(test_msg_or_host_ip, use_put)) {
+ error_fn(std::move(test_msg_or_host_ip));
+ return false;
+ }
+
+ 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")
+#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(storage_path);
+ }
+#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
+ info_fn(L"resolve", test_msg_or_host_ip);
+ url = substitute_host(make_url(storage_path), 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%, method: %7%")
+ % name
+ % upload_data.source_path
+ % url
+ % upload_filename.string()
+ % upload_parent_path.string()
+ % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
+ % (use_put ? "PUT" : "POST");
+
+ if (use_put)
+ return put_inner(std::move(upload_data), std::move(url), name, prorgess_fn, error_fn, info_fn);
+ return post_inner(std::move(upload_data), std::move(url), name, prorgess_fn, error_fn, info_fn);
+}
+
+bool PrusaLink::put_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
+{
+ info_fn(L"set_complete_off", wxString());
+
+ 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));
+ 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.set_put_body(upload_data.source_path)
+ .header("Content-Type", "text/x.gcode")
+ .header("Overwrite", "?1")
+ .on_complete([&](std::string body, unsigned status) {
+ wxString widebody = wxString::FromUTF8(body);
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % widebody;
+ std::string message = m_show_after_message ? (boost::format("%1%") % widebody).str() : std::string();
+ info_fn(L"complete", message);
+ })
+ .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 PrusaLink::post_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
+{
+ info_fn(L"set_complete_off", wxString());
+ 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));
+ 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 ???
+ .form_add_file("file", upload_data.source_path.string(), upload_filename.string())
+ .on_complete([&](std::string body, unsigned status) {
+ if (m_show_after_message) {
+ // PrusaConnect message
+ wxString widebody = wxString::FromUTF8(body);
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % widebody;
+ std::string message = m_show_after_message ? (boost::format("%1%") % widebody).str() : std::string();
+ if (status == 202)
+ info_fn(L"complete_with_warning", message);
+ else
+ info_fn(L"complete", message);
+ } else {
+ // PrusaLink
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%") % name % status;
+ info_fn(L"complete", wxString());
+ }
+
+
+ })
+ .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;
+}
+
+void PrusaLink::set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const
+{
+ http.form_add("print", post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
+}
+
+PrusaConnect::PrusaConnect(DynamicPrintConfig* config)
+ : PrusaLink(config, true)
+{
+}
+
+void PrusaConnect::set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const
+{
+ // Language for accept message
+ wxString wlang = GUI::wxGetApp().current_language_code();
+ std::string lang = GUI::format(wlang.SubString(0, 1));
+ http.header("Accept-Language", lang);
+ // Post action
+ if (post_action == PrintHostPostUploadAction::StartPrint) {
+ http.form_add("to_print", "True");
+ } else if (post_action == PrintHostPostUploadAction::QueuePrint) {
+ http.form_add("to_queue", "True");
+ }
+
+}
+
}
diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp
index e1b83bd51..fadb5d924 100644
--- a/src/slic3r/Utils/OctoPrint.hpp
+++ b/src/slic3r/Utils/OctoPrint.hpp
@@ -23,10 +23,10 @@ public:
const char* get_name() const override;
- bool test(wxString &curl_msg) const override;
+ virtual 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, ResolveFn resolve_fn) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_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; }
@@ -35,14 +35,12 @@ public:
const std::string& get_cafile() const { return m_cafile; }
protected:
- virtual bool validate_version_text(const boost::optional &version_text) const;
#ifdef WIN32
- virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, ResolveFn resolve_fn, const boost::asio::ip::address& resolved_addr) const;
- virtual bool test_with_resolved_ip(wxString& curl_msg) const;
+ virtual 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;
#endif
- virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, ResolveFn resolve_fn) const;
+ virtual bool validate_version_text(const boost::optional &version_text) const;
+ virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
-private:
std::string m_host;
std::string m_apikey;
std::string m_cafile;
@@ -50,6 +48,11 @@ private:
virtual void set_auth(Http &http) const;
std::string make_url(const std::string &path) const;
+
+private:
+#ifdef WIN32
+ bool test_with_resolved_ip(wxString& curl_msg) const;
+#endif
};
class SL1Host: public OctoPrint
@@ -80,26 +83,57 @@ private:
class PrusaLink : public OctoPrint
{
public:
- PrusaLink(DynamicPrintConfig* config);
+ PrusaLink(DynamicPrintConfig* config) : PrusaLink(config, false) {}
+ PrusaLink(DynamicPrintConfig* config, bool show_after_message);
~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; }
+ 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;
protected:
+ bool test(wxString& curl_msg) const override;
bool validate_version_text(const boost::optional& version_text) const override;
+ bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
+
+ void set_auth(Http& http) const override;
+ virtual void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const;
+#ifdef WIN32
+ 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
private:
- void set_auth(Http& http) const override;
-
+ 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;
+ bool post_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
+#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
+ bool version_check(const boost::optional& version_text) const;
+#endif
+};
+
+class PrusaConnect : public PrusaLink
+{
+public:
+ PrusaConnect(DynamicPrintConfig* config);
+ ~PrusaConnect() override = default;
+ PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::QueuePrint; }
+ const char* get_name() const override { return "PrusaConnect"; }
+protected:
+ void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const override;
};
}
diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp
index 2a51fc300..c8f0e34bc 100644
--- a/src/slic3r/Utils/PrintHost.cpp
+++ b/src/slic3r/Utils/PrintHost.cpp
@@ -52,6 +52,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
case htAstroBox: return new AstroBox(config);
case htRepetier: return new Repetier(config);
case htPrusaLink: return new PrusaLink(config);
+ case htPrusaConnect: return new PrusaConnect(config);
case htMKS: return new MKS(config);
default: return nullptr;
}
@@ -93,12 +94,13 @@ struct PrintHostJobQueue::priv
void emit_progress(int progress);
void emit_error(wxString error);
void emit_cancel(size_t id);
- void emit_resolve(wxString host);
+ void emit_info(wxString tag, wxString status);
void start_bg_thread();
void stop_bg_thread();
void bg_thread_main();
void progress_fn(Http::Progress progress, bool &cancel);
void error_fn(wxString error);
+ void info_fn(wxString tag, wxString status);
void remove_source(const fs::path &path);
void remove_source();
void perform_job(PrintHostJob the_job);
@@ -127,19 +129,18 @@ void PrintHostJobQueue::priv::emit_error(wxString error)
wxQueueEvent(queue_dialog, evt);
}
+void PrintHostJobQueue::priv::emit_info(wxString tag, wxString status)
+{
+ auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_INFO, queue_dialog->GetId(), job_id, std::move(tag), std::move(status));
+ 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::emit_resolve(wxString host)
-{
- auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_RESOLVE, queue_dialog->GetId(), job_id, host);
- wxQueueEvent(queue_dialog, evt);
-}
-
-
void PrintHostJobQueue::priv::start_bg_thread()
{
if (bg_thread.joinable()) { return; }
@@ -271,6 +272,10 @@ void PrintHostJobQueue::priv::error_fn(wxString error)
emit_error(std::move(error));
}
+void PrintHostJobQueue::priv::info_fn(wxString tag, wxString status)
+{
+ emit_info(tag, status);
+}
void PrintHostJobQueue::priv::remove_source(const fs::path &path)
{
@@ -296,7 +301,7 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
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) { this->error_fn(std::move(error)); },
- [this](wxString host) { emit_resolve(std::move(host)); }
+ [this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
);
if (success) {
diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp
index 5a9fb70df..c39f86288 100644
--- a/src/slic3r/Utils/PrintHost.hpp
+++ b/src/slic3r/Utils/PrintHost.hpp
@@ -21,7 +21,8 @@ class DynamicPrintConfig;
enum class PrintHostPostUploadAction {
None,
StartPrint,
- StartSimulation
+ StartSimulation,
+ QueuePrint
};
using PrintHostPostUploadActions = enum_bitmask;
ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction);
@@ -32,7 +33,8 @@ struct PrintHostUpload
boost::filesystem::path upload_path;
std::string group;
-
+ std::string storage;
+
PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None };
};
@@ -43,14 +45,14 @@ public:
typedef Http::ProgressFn ProgressFn;
typedef std::function ErrorFn;
- typedef ErrorFn ResolveFn;
+ typedef std::function InfoFn;
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, ResolveFn resolve_fn) const = 0;
+ virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const = 0;
virtual bool has_auto_discovery() const = 0;
virtual bool can_test() const = 0;
virtual PrintHostPostUploadActions get_post_upload_actions() const = 0;
@@ -62,6 +64,9 @@ public:
// 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; }
+ // 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; }
static PrintHost* get_print_host(DynamicPrintConfig *config);
diff --git a/src/slic3r/Utils/Repetier.cpp b/src/slic3r/Utils/Repetier.cpp
index 17724c350..ad3c1a029 100644
--- a/src/slic3r/Utils/Repetier.cpp
+++ b/src/slic3r/Utils/Repetier.cpp
@@ -111,7 +111,7 @@ wxString Repetier::get_test_failed_msg (wxString &msg) const
% _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, ResolveFn resolve_fn) const
+bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
const char *name = get_name();
diff --git a/src/slic3r/Utils/Repetier.hpp b/src/slic3r/Utils/Repetier.hpp
index 0c2f7e507..00bc92975 100644
--- a/src/slic3r/Utils/Repetier.hpp
+++ b/src/slic3r/Utils/Repetier.hpp
@@ -23,7 +23,7 @@ public:
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, ResolveFn resolve_fn) const override;
+ bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_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; }