From b9b4edb129523f96d9ea9b6c80b6217cc6eeaafc Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 19 Dec 2022 14:17:10 +0100 Subject: [PATCH] Upload changes PrusaLink: Use PUT or POST based on information read during test connection (upload-by-put). If put - do additional GET for storage_list and let user to choose where to upload or show name if only 1 is possible. Allow PrusaLink for MK2.5 and MK2.5S. PrusaConnect: New host type PrusaConnect inherited from PrusaLink class with filled host address, disabled http diggest. After upload read header information - status message and pass it to notification and Printhost upload dialog via events, this message can be shown as warning notification and is recieved in localized lang. Pass accept-language shortcut in upload header. 3 option to upload. (upload, to queue, to print) Upload Notification: Showing status text, changes in text, not showing close button, Completed state on special call (not 100%) and other design changes. Right panel: Open URL button. --- resources/icons/open_browser.svg | 61 +++ src/libslic3r/PrintConfig.cpp | 5 +- src/libslic3r/PrintConfig.hpp | 2 +- src/slic3r/GUI/Field.cpp | 9 +- src/slic3r/GUI/NotificationManager.cpp | 311 ++++++++++-- src/slic3r/GUI/NotificationManager.hpp | 41 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 135 +++-- src/slic3r/GUI/PhysicalPrinterDialog.hpp | 1 - src/slic3r/GUI/Plater.cpp | 23 +- src/slic3r/GUI/PresetComboBoxes.cpp | 16 + src/slic3r/GUI/PresetComboBoxes.hpp | 1 + src/slic3r/GUI/PrintHostDialogs.cpp | 92 +++- src/slic3r/GUI/PrintHostDialogs.hpp | 15 +- src/slic3r/Utils/AstroBox.cpp | 2 +- src/slic3r/Utils/AstroBox.hpp | 2 +- src/slic3r/Utils/Bonjour.cpp | 5 +- src/slic3r/Utils/Duet.cpp | 2 +- src/slic3r/Utils/Duet.hpp | 2 +- src/slic3r/Utils/FlashAir.cpp | 2 +- src/slic3r/Utils/FlashAir.hpp | 2 +- src/slic3r/Utils/MKS.cpp | 2 +- src/slic3r/Utils/MKS.hpp | 2 +- src/slic3r/Utils/OctoPrint.cpp | 600 ++++++++++++++++++++++- src/slic3r/Utils/OctoPrint.hpp | 56 ++- src/slic3r/Utils/PrintHost.cpp | 23 +- src/slic3r/Utils/PrintHost.hpp | 13 +- src/slic3r/Utils/Repetier.cpp | 2 +- src/slic3r/Utils/Repetier.hpp | 2 +- 28 files changed, 1268 insertions(+), 161 deletions(-) create mode 100644 resources/icons/open_browser.svg 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 @@ + +image/svg+xml + + + + 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; }