diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 07a981ea16..bfc16549c9 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -898,7 +898,7 @@ static std::vector s_Preset_printer_options { "best_object_pos","head_wrap_detect_zone","printer_notes", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", //OrcaSlicer - "host_type", "print_host", "printhost_apikey", + "host_type", "print_host", "printhost_apikey", "bbl_use_printhost", "print_host_webui", "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", @@ -2490,7 +2490,7 @@ const std::string& PresetCollection::get_preset_name_by_alias(const std::string& it_preset->is_visible && (it_preset->is_compatible || size_t(it_preset - m_presets.begin()) == m_idx_selected)) return it_preset->name; } - + return alias; } @@ -3033,6 +3033,7 @@ static std::vector s_PhysicalPrinter_opts { "preset_name", // temporary option to compatibility with older Slicer "preset_names", "printer_technology", + "bbl_use_printhost", "host_type", "print_host", "printhost_apikey", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 0d8f557a68..867ae3375d 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -323,6 +323,47 @@ Semver PresetBundle::get_vendor_profile_version(std::string vendor_name) return result_ver; } +VendorType PresetBundle::get_current_vendor_type() +{ + auto t = VendorType::Unknown; + auto config = &printers.get_edited_preset().config; + std::string vendor_name; + for (auto vendor_profile : vendors) { + for (auto vendor_model : vendor_profile.second.models) + if (vendor_model.name == config->opt_string("printer_model")) { + vendor_name = vendor_profile.first; + break; + } + } + if (!vendor_name.empty()) + { + if(vendor_name.compare("BBL") == 0) + t = VendorType::Marlin_BBL; + } + return t; +} + +bool PresetBundle::use_bbl_network() +{ + const auto cfg = printers.get_edited_preset().config; + const bool use_bbl_network = is_bbl_vendor() && !cfg.opt_bool("bbl_use_printhost"); + return use_bbl_network; +} + +bool PresetBundle::use_bbl_device_tab() { + if (!is_bbl_vendor()) { + return false; + } + + if (use_bbl_network()) { + return true; + } + + const auto cfg = printers.get_edited_preset().config; + // Use bbl device tab if printhost webui url is not set + return cfg.opt_string("print_host_webui").empty(); +} + //BBS: load project embedded presets PresetsConfigSubstitutions PresetBundle::load_project_embedded_presets(std::vector project_presets, ForwardCompatibilitySubstitutionRule substitution_rule) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 88d2a6d431..6f7dff5d86 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -18,6 +18,13 @@ #define VALIDATE_PRESETS_MODIFIED_GCODES 3 +// define an enum class of vendor type +enum class VendorType { + Unknown = 0, + Klipper, + Marlin, + Marlin_BBL +}; namespace Slic3r { // Bundle of Print + Filament + Printer presets. @@ -80,6 +87,15 @@ class PresetBundle //BBS: get vendor's current version Semver get_vendor_profile_version(std::string vendor_name); + // Orca: get vendor type + VendorType get_current_vendor_type(); + // Vendor related handy functions + bool is_bbl_vendor() { return get_current_vendor_type() == VendorType::Marlin_BBL; } + // Whether using bbl network for print upload + bool use_bbl_network(); + // Whether using bbl's device tab + bool use_bbl_device_tab(); + //BBS: project embedded preset logic PresetsConfigSubstitutions load_project_embedded_presets(std::vector project_presets, ForwardCompatibilitySubstitutionRule substitution_rule); std::vector get_current_project_embedded_presets(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 98fa6a162e..44500616d2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -80,7 +80,8 @@ static t_config_enum_values s_keys_map_PrintHostType{ { "flashair", htFlashAir }, { "astrobox", htAstroBox }, { "repetier", htRepetier }, - { "mks", htMKS } + { "mks", htMKS }, + { "simplyprint", htSimplyPrint }, }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) @@ -449,6 +450,13 @@ void PrintConfigDef::init_common_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionStrings()); + def = this->add("bbl_use_printhost", coBool); + def->label = L("Use 3rd-party print host"); + def->tooltip = L("Allow controlling BambuLab's printer through 3rd party print hosts"); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("print_host", coString); def->label = L("Hostname, IP or URL"); def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " @@ -466,7 +474,6 @@ void PrintConfigDef::init_common_params() def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionString("")); - def = this->add("printhost_apikey", coString); def->label = L("API Key / Password"); def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " @@ -936,7 +943,6 @@ void PrintConfigDef::init_fff_params() def->enum_labels.emplace_back(L("Outer and inner brim")); #endif def->enum_labels.emplace_back(L("No-brim")); - def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(btAutoBrim)); @@ -2492,6 +2498,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("astrobox"); def->enum_values.push_back("repetier"); def->enum_values.push_back("mks"); + def->enum_values.push_back("simplyprint"); def->enum_labels.push_back("PrusaLink"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); @@ -2499,6 +2506,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("AstroBox"); def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("MKS"); + def->enum_labels.push_back("SimplyPrint"); def->mode = comAdvanced; def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 872cd83be0..757d098a6e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -44,7 +44,7 @@ enum class FuzzySkinType { }; enum PrintHostType { - htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS + htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htSimplyPrint }; enum AuthorizationType { @@ -225,7 +225,7 @@ enum BedType { // BBS enum LayerSeq { - flsAuto, + flsAuto, flsCutomize }; @@ -885,8 +885,8 @@ PRINT_CONFIG_CLASS_DEFINE( PRINT_CONFIG_CLASS_DEFINE( GCodeConfig, - ((ConfigOptionString, before_layer_change_gcode)) - ((ConfigOptionString, printing_by_object_gcode)) + ((ConfigOptionString, before_layer_change_gcode)) + ((ConfigOptionString, printing_by_object_gcode)) ((ConfigOptionFloats, deretraction_speed)) //BBS ((ConfigOptionBool, enable_arc_fitting)) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f67fd88bb8..4df1cef041 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -323,9 +323,13 @@ set(SLIC3R_GUI_SOURCES GUI/UpdateDialogs.cpp GUI/UpdateDialogs.hpp GUI/Jobs/Job.hpp - GUI/Jobs/Job.cpp - GUI/Jobs/PlaterJob.hpp - GUI/Jobs/PlaterJob.cpp + GUI/Jobs/Worker.hpp + GUI/Jobs/BoostThreadWorker.hpp + GUI/Jobs/BoostThreadWorker.cpp + GUI/Jobs/UIThreadWorker.hpp + GUI/Jobs/BusyCursorJob.hpp + GUI/Jobs/PlaterWorker.hpp + GUI/Jobs/WindowWorker.hpp GUI/Jobs/UpgradeNetworkJob.hpp GUI/Jobs/UpgradeNetworkJob.cpp GUI/Jobs/ArrangeJob.hpp @@ -347,6 +351,8 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/BindJob.cpp GUI/Jobs/NotificationProgressIndicator.hpp GUI/Jobs/NotificationProgressIndicator.cpp + GUI/Jobs/ThreadSafeQueue.hpp + GUI/Jobs/SLAImportDialog.hpp GUI/PhysicalPrinterDialog.hpp GUI/PhysicalPrinterDialog.cpp GUI/ProgressStatusBar.hpp @@ -502,6 +508,13 @@ set(SLIC3R_GUI_SOURCES Utils/FontConfigHelp.hpp Utils/FontUtils.cpp Utils/FontUtils.hpp + + GUI/OAuthDialog.cpp + GUI/OAuthDialog.hpp + GUI/Jobs/OAuthJob.cpp + GUI/Jobs/OAuthJob.hpp + Utils/SimplyPrint.cpp + Utils/SimplyPrint.hpp ) if (WIN32) diff --git a/src/slic3r/GUI/BBLStatusBarSend.cpp b/src/slic3r/GUI/BBLStatusBarSend.cpp index 3976ae7bb1..dcf876a33e 100644 --- a/src/slic3r/GUI/BBLStatusBarSend.cpp +++ b/src/slic3r/GUI/BBLStatusBarSend.cpp @@ -315,7 +315,7 @@ void BBLStatusBarSend::set_status_text(const char *txt) { this->set_status_text(wxString::FromUTF8(txt)); get_panel()->GetParent()->Layout(); - get_panel()->GetParent()->Update(); + // get_panel()->GetParent()->Update(); } void BBLStatusBarSend::msw_rescale() { diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 8be1494999..c444f35f06 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -835,17 +835,21 @@ void BackgroundSlicingProcess::prepare_upload() / boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%"); if (m_print == m_fff_print) { - m_print->set_status(95, _utf8(L("Running post-processing scripts"))); - std::string error_message; - if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) - throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); - m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); - // Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file - // (not here, but when the final target is a file). - std::string source_path_str = source_path.string(); - std::string output_name_str = m_upload_job.upload_data.upload_path.string(); - if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config())) - m_upload_job.upload_data.upload_path = output_name_str; + if (m_upload_job.upload_data.use_3mf) { + source_path = m_upload_job.upload_data.source_path; + } else { + m_print->set_status(95, _utf8(L("Running post-processing scripts"))); + std::string error_message; + if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) + throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); + m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); + // Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file + // (not here, but when the final target is a file). + std::string source_path_str = source_path.string(); + std::string output_name_str = m_upload_job.upload_data.upload_path.string(); + if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config())) + m_upload_job.upload_data.upload_path = output_name_str; + } } else { m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); ThumbnailsList thumbnails = this->render_thumbnails( diff --git a/src/slic3r/GUI/BindDialog.cpp b/src/slic3r/GUI/BindDialog.cpp index d0d6388974..b860473307 100644 --- a/src/slic3r/GUI/BindDialog.cpp +++ b/src/slic3r/GUI/BindDialog.cpp @@ -15,6 +15,8 @@ #include "GUI_App.hpp" #include "Plater.hpp" #include "Widgets/WebView.hpp" +#include "Jobs/WindowWorker.hpp" +#include "Jobs/BoostThreadWorker.hpp" namespace Slic3r { namespace GUI { @@ -668,6 +670,7 @@ PingCodeBindDialog::~PingCodeBindDialog() { m_simplebook->SetBackgroundColour(*wxWHITE); m_status_bar = std::make_shared(m_simplebook); + m_worker = std::make_unique>(this, m_status_bar, "bind_worker"); auto button_panel = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, BIND_DIALOG_BUTTON_PANEL_SIZE); button_panel->SetBackgroundColour(*wxWHITE); @@ -808,10 +811,7 @@ PingCodeBindDialog::~PingCodeBindDialog() { void BindMachineDialog::on_destroy() { - if (m_bind_job) { - m_bind_job->cancel(); - m_bind_job->join(); - } + m_worker->cancel_all(); } void BindMachineDialog::on_close(wxCloseEvent &event) @@ -867,18 +867,17 @@ PingCodeBindDialog::~PingCodeBindDialog() { agent->track_update_property("dev_ota_version", m_machine_info->get_ota_version()); m_simplebook->SetSelection(0); - m_bind_job = std::make_shared(m_status_bar, wxGetApp().plater(), - m_machine_info->dev_id, m_machine_info->dev_ip, m_machine_info->bind_sec_link, m_machine_info->bind_ssdp_version); + auto bind_job = std::make_unique(m_machine_info->dev_id, m_machine_info->dev_ip, m_machine_info->bind_sec_link, m_machine_info->bind_ssdp_version); if (m_machine_info && (m_machine_info->get_printer_series() == PrinterSeries::SERIES_X1)) { - m_bind_job->set_improved(false); + bind_job->set_improved(false); } else { - m_bind_job->set_improved(m_allow_notice); + bind_job->set_improved(m_allow_notice); } - m_bind_job->set_event_handle(this); - m_bind_job->start(); + bind_job->set_event_handle(this); + replace_job(*m_worker, std::move(bind_job)); } void BindMachineDialog::on_dpi_changed(const wxRect &suggested_rect) diff --git a/src/slic3r/GUI/BindDialog.hpp b/src/slic3r/GUI/BindDialog.hpp index 630516cef0..35eed23a3a 100644 --- a/src/slic3r/GUI/BindDialog.hpp +++ b/src/slic3r/GUI/BindDialog.hpp @@ -28,6 +28,7 @@ #include "Jobs/BindJob.hpp" #include "BBLStatusBar.hpp" #include "BBLStatusBarBind.hpp" +#include "Jobs/Worker.hpp" #define BIND_DIALOG_GREY200 wxColour(248, 248, 248) #define BIND_DIALOG_GREY800 wxColour(50, 58, 61) @@ -119,7 +120,7 @@ class BindMachineDialog : public DPIDialog std::shared_ptr m_tocken; MachineObject * m_machine_info{nullptr}; - std::shared_ptr m_bind_job; + std::unique_ptr m_worker; std::shared_ptr m_status_bar; public: diff --git a/src/slic3r/GUI/CalibrationWizardPresetPage.cpp b/src/slic3r/GUI/CalibrationWizardPresetPage.cpp index 972e935244..a927e89fe9 100644 --- a/src/slic3r/GUI/CalibrationWizardPresetPage.cpp +++ b/src/slic3r/GUI/CalibrationWizardPresetPage.cpp @@ -1490,13 +1490,7 @@ void CalibrationPresetPage::on_cali_finished_job() void CalibrationPresetPage::on_cali_cancel_job() { BOOST_LOG_TRIVIAL(info) << "CalibrationWizard::print_job: enter canceled"; - if (CalibUtils::print_job) { - if (CalibUtils::print_job->is_running()) { - BOOST_LOG_TRIVIAL(info) << "calibration_print_job: canceled"; - CalibUtils::print_job->cancel(); - } - CalibUtils::print_job->join(); - } + CalibUtils::m_print_worker->cancel_all(); m_sending_panel->reset(); m_sending_panel->Show(false); diff --git a/src/slic3r/GUI/CalibrationWizardSavePage.cpp b/src/slic3r/GUI/CalibrationWizardSavePage.cpp index b92835d779..9a675599aa 100644 --- a/src/slic3r/GUI/CalibrationWizardSavePage.cpp +++ b/src/slic3r/GUI/CalibrationWizardSavePage.cpp @@ -1384,13 +1384,7 @@ void CalibrationFlowCoarseSavePage::on_cali_finished_job() void CalibrationFlowCoarseSavePage::on_cali_cancel_job() { BOOST_LOG_TRIVIAL(info) << "CalibrationWizard::print_job: enter canceled"; - if (CalibUtils::print_job) { - if (CalibUtils::print_job->is_running()) { - BOOST_LOG_TRIVIAL(info) << "calibration_print_job: canceled"; - CalibUtils::print_job->cancel(); - } - CalibUtils::print_job->join(); - } + CalibUtils::m_print_worker->cancel_all(); m_sending_panel->reset(); m_sending_panel->Show(false); diff --git a/src/slic3r/GUI/DownloadProgressDialog.cpp b/src/slic3r/GUI/DownloadProgressDialog.cpp index 39cbb27988..f9d5102db7 100644 --- a/src/slic3r/GUI/DownloadProgressDialog.cpp +++ b/src/slic3r/GUI/DownloadProgressDialog.cpp @@ -20,6 +20,8 @@ #include "wxExtensions.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "GUI_App.hpp" +#include "Jobs/BoostThreadWorker.hpp" +#include "Jobs/WindowWorker.hpp" #define DESIGN_INPUT_SIZE wxSize(FromDIP(100), -1) @@ -59,7 +61,8 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title) m_panel_download->SetSize(wxSize(FromDIP(400), FromDIP(70))); m_panel_download->SetMinSize(wxSize(FromDIP(400), FromDIP(70))); m_panel_download->SetMaxSize(wxSize(FromDIP(400), FromDIP(70))); - + + m_worker = std::make_unique>(this, m_status_bar, "download_worker"); //mode Download Failed auto m_panel_download_failed = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); @@ -144,8 +147,8 @@ bool DownloadProgressDialog::Show(bool show) { if (show) { m_simplebook_status->SetSelection(0); - m_upgrade_job = make_job(m_status_bar); - m_upgrade_job->set_event_handle(this); + auto upgrade_job = make_job(); + upgrade_job->set_event_handle(this); m_status_bar->set_progress(0); Bind(EVT_UPGRADE_NETWORK_SUCCESS, [this](wxCommandEvent& evt) { m_status_bar->change_button_label(_L("Close")); @@ -182,23 +185,16 @@ bool DownloadProgressDialog::Show(bool show) }); m_status_bar->set_cancel_callback_fina([this]() { - if (m_upgrade_job) { - m_upgrade_job->cancel(); - //EndModal(wxID_CLOSE); - } - + m_worker->cancel_all(); }); - m_upgrade_job->start(); + replace_job(*m_worker, std::move(upgrade_job)); } return DPIDialog::Show(show); } void DownloadProgressDialog::on_close(wxCloseEvent& event) { - if (m_upgrade_job) { - m_upgrade_job->cancel(); - m_upgrade_job->join(); - } + m_worker->cancel_all(); event.Skip(); } @@ -208,7 +204,7 @@ void DownloadProgressDialog::on_dpi_changed(const wxRect &suggested_rect) {} void DownloadProgressDialog::update_release_note(std::string release_note, std::string version) {} -std::shared_ptr DownloadProgressDialog::make_job(std::shared_ptr pri) { return std::make_shared(pri); } +std::unique_ptr DownloadProgressDialog::make_job() { return std::make_unique(); } void DownloadProgressDialog::on_finish() { wxGetApp().restart_networking(); } diff --git a/src/slic3r/GUI/DownloadProgressDialog.hpp b/src/slic3r/GUI/DownloadProgressDialog.hpp index 938d2706a6..325d1d67ca 100644 --- a/src/slic3r/GUI/DownloadProgressDialog.hpp +++ b/src/slic3r/GUI/DownloadProgressDialog.hpp @@ -17,6 +17,7 @@ #include "BBLStatusBar.hpp" #include "BBLStatusBarSend.hpp" #include "Jobs/UpgradeNetworkJob.hpp" +#include "Jobs/Worker.hpp" class wxBoxSizer; class wxCheckBox; @@ -47,11 +48,11 @@ class DownloadProgressDialog : public DPIDialog wxSimplebook* m_simplebook_status{nullptr}; std::shared_ptr m_status_bar; - std::shared_ptr m_upgrade_job { nullptr }; + std::unique_ptr m_worker; wxPanel * m_panel_download; protected: - virtual std::shared_ptr make_job(std::shared_ptr pri); + virtual std::unique_ptr make_job(); virtual void on_finish(); }; diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 79857adee5..e150268cc6 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -9,6 +9,8 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/I18N.hpp" + // To show a message box if GUI initialization ends up with an exception thrown. #include diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 99439fd8e9..0473aada3e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -585,11 +585,12 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y}; ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x); - if (wxGetApp().plater()->is_any_job_running()) + if (!wxGetApp().plater()->get_ui_job_worker().is_idle()) imgui->disabled_begin(true); if ( imgui->button(btn_txt) ) { - wxGetApp().plater()->optimize_rotation(); + replace_job(wxGetApp().plater()->get_ui_job_worker(), + std::make_unique()); } imgui->disabled_end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 911a3765de..60d505215a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -2,7 +2,6 @@ #define slic3r_GLGizmoRotate_hpp_ #include "GLGizmoBase.hpp" -#include "../Jobs/RotoptimizeJob.hpp" //BBS: add size adjust related #include "GizmoObjectManipulation.hpp" diff --git a/src/slic3r/GUI/HttpServer.cpp b/src/slic3r/GUI/HttpServer.cpp index 44b6ffa4ea..9e45faf0b8 100644 --- a/src/slic3r/GUI/HttpServer.cpp +++ b/src/slic3r/GUI/HttpServer.cpp @@ -7,40 +7,184 @@ namespace Slic3r { namespace GUI { -static std::string parse_params(std::string url, std::string key) +std::string url_get_param(const std::string& url, const std::string& key) { size_t start = url.find(key); - if (start < 0) return ""; + if (start == std::string::npos) return ""; size_t eq = url.find('=', start); - if (eq < 0) return ""; + if (eq == std::string::npos) return ""; std::string key_str = url.substr(start, eq - start); if (key_str != key) return ""; start += key.size() + 1; size_t end = url.find('&', start); - if (end < 0) - return ""; + if (end == std::string::npos) end = url.length(); // Last param std::string result = url.substr(start, end - start); return result; } -std::string http_headers::get_response() +void session::start() +{ + read_first_line(); +} + +void session::stop() +{ + boost::system::error_code ignored_ec; + socket.shutdown(boost::asio::socket_base::shutdown_both, ignored_ec); + socket.close(ignored_ec); +} + +void session::read_first_line() +{ + auto self(shared_from_this()); + + async_read_until(socket, buff, '\r', [this, self](const boost::beast::error_code& e, std::size_t s) { + if (!e) { + std::string line, ignore; + std::istream stream{&buff}; + std::getline(stream, line, '\r'); + std::getline(stream, ignore, '\n'); + headers.on_read_request_line(line); + read_next_line(); + } else if (e != boost::asio::error::operation_aborted) { + server.stop(self); + } + }); +} + +void session::read_body() +{ + auto self(shared_from_this()); + + int nbuffer = 1000; + std::shared_ptr> bufptr = std::make_shared>(nbuffer); + async_read(socket, boost::asio::buffer(*bufptr, nbuffer), + [this, self](const boost::beast::error_code& e, std::size_t s) { server.stop(self); }); +} + +void session::read_next_line() +{ + auto self(shared_from_this()); + + async_read_until(socket, buff, '\r', [this, self](const boost::beast::error_code& e, std::size_t s) { + if (!e) { + std::string line, ignore; + std::istream stream{&buff}; + std::getline(stream, line, '\r'); + std::getline(stream, ignore, '\n'); + headers.on_read_header(line); + + if (line.length() == 0) { + if (headers.content_length() == 0) { + const std::string url_str = Http::url_decode(headers.get_url()); + const auto resp = server.server.m_request_handler(url_str); + std::stringstream ssOut; + resp->write_response(ssOut); + std::shared_ptr str = std::make_shared(ssOut.str()); + async_write(socket, boost::asio::buffer(str->c_str(), str->length()), + [this, self](const boost::beast::error_code& e, std::size_t s) { + std::cout << "done" << std::endl; + server.stop(self); + }); + } else { + read_body(); + } + } else { + read_next_line(); + } + } else if (e != boost::asio::error::operation_aborted) { + server.stop(self); + } + }); +} + +void HttpServer::IOServer::do_accept() +{ + acceptor.async_accept([this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { + if (!acceptor.is_open()) { + return; + } + + if (!ec) { + const auto ss = std::make_shared(*this, std::move(socket)); + start(ss); + } + + do_accept(); + }); +} + +void HttpServer::IOServer::start(std::shared_ptr session) +{ + sessions.insert(session); + session->start(); +} + +void HttpServer::IOServer::stop(std::shared_ptr session) +{ + sessions.erase(session); + session->stop(); +} + +void HttpServer::IOServer::stop_all() +{ + for (auto s : sessions) { + s->stop(); + } + sessions.clear(); +} + + +HttpServer::HttpServer(boost::asio::ip::port_type port) : port(port) {} + +void HttpServer::start() +{ + BOOST_LOG_TRIVIAL(info) << "start_http_service..."; + start_http_server = true; + m_http_server_thread = create_thread([this] { + set_current_thread_name("http_server"); + server_ = std::make_unique(*this); + server_->acceptor.listen(); + + server_->do_accept(); + + server_->io_service.run(); + }); +} + +void HttpServer::stop() +{ + start_http_server = false; + if (server_) { + server_->acceptor.close(); + server_->stop_all(); + } + if (m_http_server_thread.joinable()) + m_http_server_thread.join(); + server_.reset(); +} + +void HttpServer::set_request_handler(const std::function(const std::string&)>& request_handler) +{ + this->m_request_handler = request_handler; +} + +std::shared_ptr HttpServer::bbl_auth_handle_request(const std::string& url) { BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_response"; - std::stringstream ssOut; - std::string url_str = Http::url_decode(url); - if (boost::contains(url_str, "access_token")) { - std::string sHTML = "

redirect to url

"; - std::string redirect_url = parse_params(url_str, "redirect_url"); - std::string access_token = parse_params(url_str, "access_token"); - std::string refresh_token = parse_params(url_str, "refresh_token"); - std::string expires_in_str = parse_params(url_str, "expires_in"); - std::string refresh_expires_in_str = parse_params(url_str, "refresh_expires_in"); - NetworkAgent* agent = wxGetApp().getAgent(); + + if (boost::contains(url, "access_token")) { + std::string redirect_url = url_get_param(url, "redirect_url"); + std::string access_token = url_get_param(url, "access_token"); + std::string refresh_token = url_get_param(url, "refresh_token"); + std::string expires_in_str = url_get_param(url, "expires_in"); + std::string refresh_expires_in_str = url_get_param(url, "refresh_expires_in"); + NetworkAgent* agent = wxGetApp().getAgent(); unsigned int http_code; - std::string http_body; - int result = agent->get_my_profile(access_token, &http_code, &http_body); + std::string http_body; + int result = agent->get_my_profile(access_token, &http_code, &http_body); if (result == 0) { std::string user_id; std::string user_name; @@ -60,91 +204,50 @@ std::string http_headers::get_response() ; } json j; - j["data"]["refresh_token"] = refresh_token; - j["data"]["token"] = access_token; - j["data"]["expires_in"] = expires_in_str; + j["data"]["refresh_token"] = refresh_token; + j["data"]["token"] = access_token; + j["data"]["expires_in"] = expires_in_str; j["data"]["refresh_expires_in"] = refresh_expires_in_str; - j["data"]["user"]["uid"] = user_id; - j["data"]["user"]["name"] = user_name; - j["data"]["user"]["account"] = user_account; - j["data"]["user"]["avatar"] = user_avatar; + j["data"]["user"]["uid"] = user_id; + j["data"]["user"]["name"] = user_name; + j["data"]["user"]["account"] = user_account; + j["data"]["user"]["avatar"] = user_avatar; agent->change_user(j.dump()); if (agent->is_user_login()) { wxGetApp().request_user_login(1); } - GUI::wxGetApp().CallAfter([this] { - wxGetApp().ShowUserLogin(false); - }); - std::string location_str = (boost::format("Location: %1%?result=success") % redirect_url).str(); - ssOut << "HTTP/1.1 302 Found" << std::endl; - ssOut << location_str << std::endl; - ssOut << "content-type: text/html" << std::endl; - ssOut << "content-length: " << sHTML.length() << std::endl; - ssOut << std::endl; - ssOut << sHTML; + GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); }); + std::string location_str = (boost::format("%1%?result=success") % redirect_url).str(); + return std::make_shared(location_str); } else { - std::string error_str = "get_user_profile_error_" + std::to_string(result); - std::string location_str = (boost::format("Location: %1%?result=fail&error=%2%") % redirect_url % error_str).str(); - ssOut << "HTTP/1.1 302 Found" << std::endl; - ssOut << location_str << std::endl; - ssOut << "content-type: text/html" << std::endl; - ssOut << "content-length: " << sHTML.length() << std::endl; - ssOut << std::endl; - ssOut << sHTML; + std::string error_str = "get_user_profile_error_" + std::to_string(result); + std::string location_str = (boost::format("%1%?result=fail&error=%2%") % redirect_url % error_str).str(); + return std::make_shared(location_str); } } else { - std::string sHTML = "

404 Not Found

There's nothing here.

"; - ssOut << "HTTP/1.1 404 Not Found" << std::endl; - ssOut << "content-type: text/html" << std::endl; - ssOut << "content-length: " << sHTML.length() << std::endl; - ssOut << std::endl; - ssOut << sHTML; + return std::make_shared(); } - return ssOut.str(); -} - - -void accept_and_run(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& io_service) -{ - std::shared_ptr sesh = std::make_shared(io_service); - acceptor.async_accept(sesh->socket, - [sesh, &acceptor, &io_service](const boost::beast::error_code& accept_error) - { - accept_and_run(acceptor, io_service); - if (!accept_error) - { - session::interact(sesh); - } - }); } -HttpServer::HttpServer() +void HttpServer::ResponseNotFound::write_response(std::stringstream& ssOut) { - ; + const std::string sHTML = "

404 Not Found

There's nothing here.

"; + ssOut << "HTTP/1.1 404 Not Found" << std::endl; + ssOut << "content-type: text/html" << std::endl; + ssOut << "content-length: " << sHTML.length() << std::endl; + ssOut << std::endl; + ssOut << sHTML; } -void HttpServer::start() +void HttpServer::ResponseRedirect::write_response(std::stringstream& ssOut) { - BOOST_LOG_TRIVIAL(info) << "start_http_service..."; - start_http_server = true; - m_http_server_thread = Slic3r::create_thread( - [this] { - boost::asio::io_service io_service; - boost::asio::ip::tcp::endpoint endpoint{ boost::asio::ip::tcp::v4(), LOCALHOST_PORT}; - boost::asio::ip::tcp::acceptor acceptor { io_service, endpoint}; - acceptor.listen(); - accept_and_run(acceptor, io_service); - while (start_http_server) { - io_service.run(); - } - }); -} - -void HttpServer::stop() -{ - start_http_server = false; - if (m_http_server_thread.joinable()) - m_http_server_thread.join(); + const std::string sHTML = "

redirect to url

"; + ssOut << "HTTP/1.1 302 Found" << std::endl; + ssOut << "Location: " << location_str << std::endl; + ssOut << "content-type: text/html" << std::endl; + ssOut << "content-length: " << sHTML.length() << std::endl; + ssOut << std::endl; + ssOut << sHTML; } } // GUI diff --git a/src/slic3r/GUI/HttpServer.hpp b/src/slic3r/GUI/HttpServer.hpp index c545ab9b2a..99925019f2 100644 --- a/src/slic3r/GUI/HttpServer.hpp +++ b/src/slic3r/GUI/HttpServer.hpp @@ -13,14 +13,12 @@ #include #include -using namespace boost::system; -using namespace boost::asio; - #define LOCALHOST_PORT 13618 #define LOCALHOST_URL "http://localhost:" -namespace Slic3r { -namespace GUI { +namespace Slic3r { namespace GUI { + +class session; class http_headers { @@ -31,16 +29,14 @@ class http_headers std::map headers; public: - - std::string get_response(); + std::string get_url() { return url; } int content_length() { auto request = headers.find("content-length"); - if (request != headers.end()) - { + if (request != headers.end()) { std::stringstream ssLength(request->second); - int content_length; + int content_length; ssLength >> content_length; return content_length; } @@ -49,10 +45,10 @@ class http_headers void on_read_header(std::string line) { - //std::cout << "header: " << line << std::endl; + // std::cout << "header: " << line << std::endl; std::stringstream ssHeader(line); - std::string headerName; + std::string headerName; std::getline(ssHeader, headerName, ':'); std::string value; @@ -71,92 +67,92 @@ class http_headers } }; -class session +class HttpServer { - boost::asio::streambuf buff; - http_headers headers; + boost::asio::ip::port_type port; - static void read_body(std::shared_ptr pThis) +public: + class Response { - int nbuffer = 1000; - std::shared_ptr> bufptr = std::make_shared>(nbuffer); - boost::asio::async_read(pThis->socket, boost::asio::buffer(*bufptr, nbuffer), [pThis](const boost::beast::error_code& e, std::size_t s) - { - }); - } + public: + virtual ~Response() = default; + virtual void write_response(std::stringstream& ssOut) = 0; + }; - static void read_next_line(std::shared_ptr pThis) + class ResponseNotFound : public Response { - boost::asio::async_read_until(pThis->socket, pThis->buff, '\r', [pThis](const boost::beast::error_code& e, std::size_t s) - { - std::string line, ignore; - std::istream stream{ &pThis->buff }; - std::getline(stream, line, '\r'); - std::getline(stream, ignore, '\n'); - pThis->headers.on_read_header(line); - - if (line.length() == 0) - { - if (pThis->headers.content_length() == 0) - { - std::shared_ptr str = std::make_shared(pThis->headers.get_response()); - boost::asio::async_write(pThis->socket, boost::asio::buffer(str->c_str(), str->length()), [pThis, str](const boost::beast::error_code& e, std::size_t s) - { - std::cout << "done" << std::endl; - }); - } - else - { - pThis->read_body(pThis); - } - } - else - { - pThis->read_next_line(pThis); - } - }); - } + public: + ~ResponseNotFound() override = default; + void write_response(std::stringstream& ssOut) override; + }; - static void read_first_line(std::shared_ptr pThis) + class ResponseRedirect : public Response { - boost::asio::async_read_until(pThis->socket, pThis->buff, '\r', [pThis](const boost::beast::error_code& e, std::size_t s) - { - std::string line, ignore; - std::istream stream{ &pThis->buff }; - std::getline(stream, line, '\r'); - std::getline(stream, ignore, '\n'); - pThis->headers.on_read_request_line(line); - pThis->read_next_line(pThis); - }); - } + const std::string location_str; -public: - boost::asio::ip::tcp::socket socket; + public: + ResponseRedirect(const std::string& location) : location_str(location) {} + ~ResponseRedirect() override = default; + void write_response(std::stringstream& ssOut) override; + }; - session(io_service& io_service) - :socket(io_service) - { - } + HttpServer(boost::asio::ip::port_type port = LOCALHOST_PORT); + + boost::thread m_http_server_thread; + bool start_http_server = false; + + bool is_started() { return start_http_server; } + void start(); + void stop(); + void set_request_handler(const std::function(const std::string&)>& m_request_handler); + + static std::shared_ptr bbl_auth_handle_request(const std::string& url); - static void interact(std::shared_ptr pThis) +private: + class IOServer { - read_first_line(pThis); - } -}; + public: + HttpServer& server; + boost::asio::io_service io_service; + boost::asio::ip::tcp::acceptor acceptor; + std::set> sessions; -class HttpServer { -public: - HttpServer(); + IOServer(HttpServer& server) : server(server), acceptor(io_service, {boost::asio::ip::tcp::v4(), server.port}) {} + + void do_accept(); - boost::thread m_http_server_thread; - bool start_http_server = false; + void start(std::shared_ptr session); + void stop(std::shared_ptr session); + void stop_all(); + }; + friend class session; - bool is_started() { return start_http_server; } - void start(); - void stop(); + std::unique_ptr server_{nullptr}; + + std::function(const std::string&)> m_request_handler{&HttpServer::bbl_auth_handle_request}; }; -} +class session : public std::enable_shared_from_this +{ + HttpServer::IOServer& server; + boost::asio::ip::tcp::socket socket; + + boost::asio::streambuf buff; + http_headers headers; + + void read_first_line(); + void read_next_line(); + void read_body(); + +public: + session(HttpServer::IOServer& server, boost::asio::ip::tcp::socket socket) : server(server), socket(std::move(socket)) {} + + void start(); + void stop(); }; +std::string url_get_param(const std::string& url, const std::string& key); + +}}; + #endif diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 4c6f4f3731..e50ba0a9aa 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -188,7 +188,7 @@ void ArrangeJob::prepare_selected() { void ArrangeJob::prepare_all() { clear_input(); - PartPlateList& plate_list = m_plater->get_partplate_list(); + PartPlateList& plate_list = m_plater->get_partplate_list(); for (size_t i = 0; i < plate_list.get_plate_count(); i++) { PartPlate* plate = plate_list.get_plate(i); bool same_as_global_print_seq = true; @@ -519,21 +519,13 @@ void ArrangeJob::check_unprintable() } } -void ArrangeJob::on_exception(const std::exception_ptr &eptr) +void ArrangeJob::process(Ctl &ctl) { - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (libnest2d::GeometryException &) { - show_error(m_plater, _(L("Arrange failed. " - "Found some exceptions when processing object geometries."))); - } catch (std::exception &) { - PlaterJob::on_exception(eptr); - } -} + static const auto arrangestr = _u8L("Arranging"); + + ctl.update_status(0, arrangestr); + ctl.call_on_main_thread([this]{ prepare(); }).wait(); -void ArrangeJob::process() -{ auto & partplate_list = m_plater->get_partplate_list(); const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config(); @@ -551,10 +543,10 @@ void ArrangeJob::process() BOOST_LOG_TRIVIAL(debug) << "arrange bedpts:" << bedpts[0].transpose() << ", " << bedpts[1].transpose() << ", " << bedpts[2].transpose() << ", " << bedpts[3].transpose(); - params.stopcondition = [this]() { return was_canceled(); }; + params.stopcondition = [&ctl]() { return ctl.was_canceled(); }; - params.progressind = [this](unsigned num_finished, std::string str = "") { - update_status(num_finished, _L("Arranging") + " "+ wxString::FromUTF8(str)); + params.progressind = [this, &ctl](unsigned num_finished, std::string str = "") { + ctl.update_status(num_finished * 100 / status_range(), _u8L("Arranging") + str); }; { @@ -596,11 +588,13 @@ void ArrangeJob::process() } // finalize just here. - update_status(status_range(), - was_canceled() ? _(L("Arranging canceled.")) : - we_have_unpackable_items ? _(L("Arranging is done but there are unpacked items. Reduce spacing and try again.")) : _(L("Arranging done."))); + ctl.update_status(100, + ctl.was_canceled() ? _u8L("Arranging canceled.") : + we_have_unpackable_items ? _u8L("Arranging is done but there are unpacked items. Reduce spacing and try again.") : _u8L("Arranging done.")); } +ArrangeJob::ArrangeJob() : m_plater{wxGetApp().plater()} {} + static std::string concat_strings(const std::set &strings, const std::string &delim = "\n") { @@ -611,127 +605,137 @@ static std::string concat_strings(const std::set &strings, }); } -void ArrangeJob::finalize() { - // Ignore the arrange result if aborted. - if (!was_canceled()) { - - // Unprintable items go to the last virtual bed - int beds = 0; +void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { + try { + if (eptr) + std::rethrow_exception(eptr); + } catch (libnest2d::GeometryException &) { + show_error(m_plater, _(L("Arrange failed. " + "Found some exceptions when processing object geometries."))); + eptr = nullptr; + } catch(...) { + eptr = std::current_exception(); + } - //BBS: partplate - PartPlateList& plate_list = m_plater->get_partplate_list(); - //clear all the relations before apply the arrangement results - if (only_on_partplate) { - plate_list.clear(false, false, true, current_plate_index); - } - else - plate_list.clear(false, false, true, -1); - //BBS: adjust the bed_index, create new plates, get the max bed_index - for (ArrangePolygon& ap : m_selected) { - //if (ap.bed_idx < 0) continue; // bed_idx<0 means unarrangable - //BBS: partplate postprocess - if (only_on_partplate) - plate_list.postprocess_bed_index_for_current_plate(ap); - else - plate_list.postprocess_bed_index_for_selected(ap); + if (canceled) + m_plater->get_notification_manager()->push_notification(NotificationType::ArrangeOngoing, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging canceled.")); - beds = std::max(ap.bed_idx, beds); + if (canceled || eptr) + return; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": arrange selected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; - } + // Unprintable items go to the last virtual bed + int beds = 0; - //BBS: adjust the bed_index, create new plates, get the max bed_index - for (ArrangePolygon& ap : m_unselected) { - if (ap.is_virt_object) - continue; + //BBS: partplate + PartPlateList& plate_list = m_plater->get_partplate_list(); + //clear all the relations before apply the arrangement results + if (only_on_partplate) { + plate_list.clear(false, false, true, current_plate_index); + } + else + plate_list.clear(false, false, true, -1); + //BBS: adjust the bed_index, create new plates, get the max bed_index + for (ArrangePolygon& ap : m_selected) { + //if (ap.bed_idx < 0) continue; // bed_idx<0 means unarrangable + //BBS: partplate postprocess + if (only_on_partplate) + plate_list.postprocess_bed_index_for_current_plate(ap); + else + plate_list.postprocess_bed_index_for_selected(ap); - //BBS: partplate postprocess - if (!only_on_partplate) - plate_list.postprocess_bed_index_for_unselected(ap); + beds = std::max(ap.bed_idx, beds); - beds = std::max(ap.bed_idx, beds); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange unselected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; - } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": arrange selected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; + } - for (ArrangePolygon& ap : m_locked) { - beds = std::max(ap.bed_idx, beds); + //BBS: adjust the bed_index, create new plates, get the max bed_index + for (ArrangePolygon& ap : m_unselected) { + if (ap.is_virt_object) + continue; - plate_list.postprocess_arrange_polygon(ap, false); + //BBS: partplate postprocess + if (!only_on_partplate) + plate_list.postprocess_bed_index_for_unselected(ap); - ap.apply(); - } + beds = std::max(ap.bed_idx, beds); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange unselected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; + } - // Apply the arrange result to all selected objects - for (ArrangePolygon& ap : m_selected) { - //BBS: partplate postprocess - plate_list.postprocess_arrange_polygon(ap, true); + for (ArrangePolygon& ap : m_locked) { + beds = std::max(ap.bed_idx, beds); - ap.apply(); - } + plate_list.postprocess_arrange_polygon(ap, false); - // Apply the arrange result to unselected objects(due to the sukodu-style column changes, the position of unselected may also be modified) - for (ArrangePolygon& ap : m_unselected) { - if (ap.is_virt_object) - continue; + ap.apply(); + } - //BBS: partplate postprocess - plate_list.postprocess_arrange_polygon(ap, false); + // Apply the arrange result to all selected objects + for (ArrangePolygon& ap : m_selected) { + //BBS: partplate postprocess + plate_list.postprocess_arrange_polygon(ap, true); - ap.apply(); - } + ap.apply(); + } - // Move the unprintable items to the last virtual bed. - // Note ap.apply() moves relatively according to bed_idx, so we need to subtract the orignal bed_idx - for (ArrangePolygon& ap : m_unprintable) { - ap.bed_idx = beds + 1; - plate_list.postprocess_arrange_polygon(ap, true); + // Apply the arrange result to unselected objects(due to the sukodu-style column changes, the position of unselected may also be modified) + for (ArrangePolygon& ap : m_unselected) { + if (ap.is_virt_object) + continue; - ap.apply(); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange m_unprintable: name: %4%, bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; - } + //BBS: partplate postprocess + plate_list.postprocess_arrange_polygon(ap, false); - m_plater->update(); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); + ap.apply(); + } - if (!m_unarranged.empty()) { - std::set names; - for (ModelInstance* mi : m_unarranged) - names.insert(mi->get_object()->name); + // Move the unprintable items to the last virtual bed. + // Note ap.apply() moves relatively according to bed_idx, so we need to subtract the orignal bed_idx + for (ArrangePolygon& ap : m_unprintable) { + ap.bed_idx = beds + 1; + plate_list.postprocess_arrange_polygon(ap, true); - m_plater->get_notification_manager()->push_notification(GUI::format( - _L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"), - concat_strings(names, "\n"))); - } + ap.apply(); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange m_unprintable: name: %4%, bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; + } - // unlock the plates we just locked - for (int i : m_uncompatible_plates) { - PartPlate* plate = plate_list.get_plate(i); - if (plate) plate->lock(false); - } + m_plater->update(); + // BBS + //wxGetApp().obj_manipul()->set_dirty(); - //BBS: reload all objects due to arrange - if (only_on_partplate) { - plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true, current_plate_index); - } - else { - plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true); - } + if (!m_unarranged.empty()) { + std::set names; + for (ModelInstance* mi : m_unarranged) + names.insert(mi->get_object()->name); - // BBS: update slice context and gcode result. - m_plater->update_slicing_context_to_current_partplate(); + m_plater->get_notification_manager()->push_notification(GUI::format( + _L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"), + concat_strings(names, "\n"))); + } - wxGetApp().obj_list()->reload_all_plates(); + // unlock the plates we just locked + for (int i : m_uncompatible_plates) { + PartPlate* plate = plate_list.get_plate(i); + if (plate) plate->lock(false); + } - m_plater->update(); - m_plater->get_notification_manager()->push_notification(NotificationType::ArrangeOngoing, - NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging done.")); + //BBS: reload all objects due to arrange + if (only_on_partplate) { + plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true, current_plate_index); } else { - m_plater->get_notification_manager()->push_notification(NotificationType::ArrangeOngoing, - NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging canceled.")); + plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true); } - Job::finalize(); + + // BBS: update slice context and gcode result. + m_plater->update_slicing_context_to_current_partplate(); + + wxGetApp().obj_list()->reload_all_plates(); + + m_plater->update(); + m_plater->get_notification_manager()->push_notification(NotificationType::ArrangeOngoing, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging done.")); + m_plater->m_arrange_running.store(false); } diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 7df2d344f0..02e15a421a 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -1,7 +1,9 @@ #ifndef ARRANGEJOB_HPP #define ARRANGEJOB_HPP -#include "PlaterJob.hpp" +#include + +#include "Job.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/Arrange.hpp" #include "libslic3r/Model.hpp" @@ -12,7 +14,9 @@ class ModelInstance; namespace GUI { -class ArrangeJob : public PlaterJob +class Plater; + +class ArrangeJob : public Job { using ArrangePolygon = arrangement::ArrangePolygon; using ArrangePolygons = arrangement::ArrangePolygons; @@ -20,6 +24,7 @@ class ArrangeJob : public PlaterJob //BBS: add locked logic ArrangePolygons m_selected, m_unselected, m_unprintable, m_locked; std::vector m_unarranged; + Plater *m_plater; std::map m_selected_groups; // groups of selected items for sequential printing std::vector m_uncompatible_plates; // plate indices with different printing sequence than global @@ -27,6 +32,9 @@ class ArrangeJob : public PlaterJob int current_plate_index = 0; Polygon bed_poly; + //BBS: add flag for whether on current part plate + bool only_on_partplate{false}; + // clear m_selected and m_unselected, reserve space for next usage void clear_input(); @@ -41,29 +49,25 @@ class ArrangeJob : public PlaterJob void prepare_wipe_tower(); ArrangePolygon prepare_arrange_polygon(void* instance); - protected: - void prepare() override; - void check_unprintable(); - void on_exception(const std::exception_ptr &) override; +public: + + void prepare(); - void process() override; + void process(Ctl &ctl) override; -public: - ArrangeJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + ArrangeJob(); - int status_range() const override + int status_range() const { // ensure finalize() is called after all operations in process() is finished. return int(m_selected.size() + m_unprintable.size() + 1); } - void finalize() override; + void finalize(bool canceled, std::exception_ptr &e) override; }; std::optional get_wipe_tower_arrangepoly(const Plater &); diff --git a/src/slic3r/GUI/Jobs/BindJob.cpp b/src/slic3r/GUI/Jobs/BindJob.cpp index 80b856183d..3017868abd 100644 --- a/src/slic3r/GUI/Jobs/BindJob.cpp +++ b/src/slic3r/GUI/Jobs/BindJob.cpp @@ -12,13 +12,11 @@ wxDEFINE_EVENT(EVT_BIND_MACHINE_SUCCESS, wxCommandEvent); wxDEFINE_EVENT(EVT_BIND_MACHINE_FAIL, wxCommandEvent); -static wxString waiting_auth_str = _L("Logging in"); -static wxString login_failed_str = _L("Login failed"); +static auto waiting_auth_str = _u8L("Logging in"); +static auto login_failed_str = _u8L("Login failed"); -BindJob::BindJob(std::shared_ptr pri, Plater *plater, std::string dev_id, std::string dev_ip, std::string sec_link, - std::string ssdp_version) - : PlaterJob{std::move(pri), plater}, +BindJob::BindJob(std::string dev_id, std::string dev_ip, std::string sec_link, std::string ssdp_version) : m_dev_id(dev_id), m_dev_ip(dev_ip), m_sec_link(sec_link), @@ -27,37 +25,27 @@ BindJob::BindJob(std::shared_ptr pri, Plater *plater, std::st ; } -void BindJob::on_exception(const std::exception_ptr &eptr) -{ - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (std::exception &e) { - PlaterJob::on_exception(eptr); - } -} - void BindJob::on_success(std::function success) { m_success_fun = success; } -void BindJob::update_status(int st, const wxString &msg) +void BindJob::update_status(Ctl& ctl, int st, const std::string &msg) { - GUI::Job::update_status(st, msg); + ctl.update_status(st, msg); wxCommandEvent event(EVT_BIND_UPDATE_MESSAGE); event.SetString(msg); event.SetEventObject(m_event_handle); wxPostEvent(m_event_handle, event); } -void BindJob::process() +void BindJob::process(Ctl& ctl) { int result_code = 0; std::string result_info; /* display info */ - wxString msg = waiting_auth_str; + std::string msg = waiting_auth_str; int curr_percent = 0; NetworkAgent* m_agent = wxGetApp().getAgent(); @@ -70,40 +58,40 @@ void BindJob::process() m_agent->track_update_property("ssdp_version", m_ssdp_version, "string"); int result = m_agent->bind(m_dev_ip, m_dev_id, m_sec_link, timezone, m_improved, - [this, &curr_percent, &msg, &result_code, &result_info](int stage, int code, std::string info) { + [this, &curr_percent, &msg, &result_code, &result_info, &ctl](int stage, int code, std::string info) { result_code = code; result_info = info; if (stage == BBL::BindJobStage::LoginStageConnect) { curr_percent = 15; - msg = _L("Logging in"); + msg = _u8L("Logging in"); } else if (stage == BBL::BindJobStage::LoginStageLogin) { curr_percent = 30; - msg = _L("Logging in"); + msg = _u8L("Logging in"); } else if (stage == BBL::BindJobStage::LoginStageWaitForLogin) { curr_percent = 45; - msg = _L("Logging in"); + msg = _u8L("Logging in"); } else if (stage == BBL::BindJobStage::LoginStageGetIdentify) { curr_percent = 60; - msg = _L("Logging in"); + msg = _u8L("Logging in"); } else if (stage == BBL::BindJobStage::LoginStageWaitAuth) { curr_percent = 80; - msg = _L("Logging in"); + msg = _u8L("Logging in"); } else if (stage == BBL::BindJobStage::LoginStageFinished) { curr_percent = 100; - msg = _L("Logging in"); + msg = _u8L("Logging in"); } else { - msg = _L("Logging in"); + msg = _u8L("Logging in"); } if (code != 0) { - msg = _L("Login failed"); + msg = _u8L("Login failed"); if (code == BAMBU_NETWORK_ERR_TIMEOUT) { - msg += _L("Please check the printer network connection."); + msg += _u8L("Please check the printer network connection."); } } - update_status(curr_percent, msg); + update_status(ctl, curr_percent, msg); } ); @@ -135,17 +123,21 @@ void BindJob::process() } dev->update_user_machine_list_info(); - wxCommandEvent event(EVT_BIND_MACHINE_SUCCESS); - event.SetEventObject(m_event_handle); - wxPostEvent(m_event_handle, event); - return; + wxCommandEvent event(EVT_BIND_MACHINE_SUCCESS); + event.SetEventObject(m_event_handle); + wxPostEvent(m_event_handle, event); } -void BindJob::finalize() +void BindJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (was_canceled()) return; - - Job::finalize(); + try { + if (eptr) + std::rethrow_exception(eptr); + eptr = nullptr; + } catch (...) { + eptr = std::current_exception(); + } + if (canceled || eptr) return; } void BindJob::set_event_handle(wxWindow *hanle) diff --git a/src/slic3r/GUI/Jobs/BindJob.hpp b/src/slic3r/GUI/Jobs/BindJob.hpp index c29fb7a0e3..1096b19c76 100644 --- a/src/slic3r/GUI/Jobs/BindJob.hpp +++ b/src/slic3r/GUI/Jobs/BindJob.hpp @@ -3,14 +3,16 @@ #include #include -#include "PlaterJob.hpp" +#include "Job.hpp" namespace fs = boost::filesystem; namespace Slic3r { namespace GUI { -class BindJob : public PlaterJob +class Plater; + +class BindJob : public Job { wxWindow * m_event_handle{nullptr}; std::function m_success_fun{nullptr}; @@ -22,13 +24,10 @@ class BindJob : public PlaterJob int m_print_job_completed_id = 0; bool m_improved{false}; -protected: - void on_exception(const std::exception_ptr &) override; public: - BindJob(std::shared_ptr pri, Plater *plater, std::string dev_id, std::string dev_ip, - std::string sec_link, std::string ssdp_version); + BindJob(std::string dev_id, std::string dev_ip, std::string sec_link, std::string ssdp_version); - int status_range() const override + int status_range() const { return 100; } @@ -36,9 +35,9 @@ class BindJob : public PlaterJob bool is_finished() { return m_job_finished; } void on_success(std::function success); - void update_status(int st, const wxString &msg); - void process() override; - void finalize() override; + void update_status(Ctl &ctl, int st, const std::string &msg); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; void set_event_handle(wxWindow* hanle); void post_fail_event(int code, std::string info); void set_improved(bool improved){m_improved = improved;}; diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp new file mode 100644 index 0000000000..bcdbbb3249 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp @@ -0,0 +1,185 @@ +#include + +#include "BoostThreadWorker.hpp" + +namespace Slic3r { namespace GUI { + +void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner) +{ + switch(MsgType(get_type())) { + case Empty: break; + case Status: { + auto info = boost::get(m_data); + if (runner.get_pri()) { + runner.get_pri()->set_progress(info.status); + runner.get_pri()->set_status_text(info.msg.c_str()); + } + break; + } + case Finalize: { + auto& entry = boost::get(m_data); + entry.job->finalize(entry.canceled, entry.eptr); + + // Unhandled exceptions are rethrown without mercy. + if (entry.eptr) + std::rethrow_exception(entry.eptr); + + break; + } + case MainThreadCall: { + auto &calldata = boost::get(m_data); + calldata.fn(); + calldata.promise.set_value(); + + break; + } + } +} + +void BoostThreadWorker::run() +{ + bool stop = false; + + while (!stop) { + m_input_queue + .consume_one(BlockingWait{0}, [this, &stop](JobEntry &e) { + if (!e.job) + stop = true; + else { + m_canceled.store(false); + + try { + e.job->process(*this); + } catch (...) { + e.eptr = std::current_exception(); + } + + e.canceled = m_canceled.load(); + m_output_queue.push(std::move(e)); // finalization message + } + }); + }; +} + +void BoostThreadWorker::update_status(int st, const std::string &msg) +{ + m_output_queue.push(st, msg); +} + +std::future BoostThreadWorker::call_on_main_thread(std::function fn) +{ + MainThreadCallData cbdata{std::move(fn), {}}; + std::future future = cbdata.promise.get_future(); + + m_output_queue.push(std::move(cbdata)); + + return future; +} + +BoostThreadWorker::BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes &attribs, + const char *name) + : m_progress(std::move(pri)) + , m_input_queue{m_running} + , m_output_queue{m_running} + , m_name{name} +{ + if (m_progress) + m_progress->set_cancel_callback([this](){ cancel(); }); + + m_thread = create_thread(attribs, [this] { this->run(); }); + + std::string nm{name}; + if (!nm.empty()) set_thread_name(m_thread, name); +} + +constexpr int ABORT_WAIT_MAX_MS = 10000; + +BoostThreadWorker::~BoostThreadWorker() +{ + bool joined = false; + try { + cancel_all(); + wait_for_idle(ABORT_WAIT_MAX_MS); + m_input_queue.push(JobEntry{nullptr}); + joined = join(ABORT_WAIT_MAX_MS); + } catch(...) {} + + if (!joined) + BOOST_LOG_TRIVIAL(error) + << "Could not join worker thread '" << m_name << "'"; +} + +bool BoostThreadWorker::join(int timeout_ms) +{ + if (!m_thread.joinable()) + return true; + + if (timeout_ms <= 0) { + m_thread.join(); + } + else if (m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) { + return true; + } + else + return false; + + return true; +} + +void BoostThreadWorker::process_events() +{ + while (m_output_queue.consume_one([this](WorkerMessage &msg) { + msg.deliver(*this); + })); +} + +bool BoostThreadWorker::wait_for_current_job(unsigned timeout_ms) +{ + bool ret = true; + + if (!is_idle()) { + bool was_finish = false; + bool timeout_reached = false; + while (!timeout_reached && !was_finish) { + timeout_reached = + !m_output_queue.consume_one(BlockingWait{timeout_ms}, + [this, &was_finish]( + WorkerMessage &msg) { + msg.deliver(*this); + if (msg.get_type() == + WorkerMessage::Finalize) + was_finish = true; + }); + } + + ret = !timeout_reached; + } + + return ret; +} + +bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms) +{ + bool timeout_reached = false; + while (!timeout_reached && !is_idle()) { + timeout_reached = !m_output_queue + .consume_one(BlockingWait{timeout_ms}, + [this](WorkerMessage &msg) { + msg.deliver(*this); + }); + } + + return !timeout_reached; +} + +bool BoostThreadWorker::push(std::unique_ptr job) +{ + if (!job) + return false; + + m_input_queue.push(JobEntry{std::move(job)}); + return true; +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp new file mode 100644 index 0000000000..0a74cdb434 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp @@ -0,0 +1,204 @@ +#ifndef BOOSTTHREADWORKER_HPP +#define BOOSTTHREADWORKER_HPP + +#include + +#include "Worker.hpp" + +#include + +#include +#include + +#include "ThreadSafeQueue.hpp" + +namespace Slic3r { namespace GUI { + +// An implementation of the Worker interface which uses the boost::thread +// API and two thread safe message queues to communicate with the main thread +// back and forth. The queue from the main thread to the worker thread holds the +// job entries that will be performed on the worker. The other queue holds messages +// from the worker to the main thread. These messages include status updates, +// finishing operation and arbitrary functiors that need to be performed +// on the main thread during the jobs execution, like displaying intermediate +// results. +class BoostThreadWorker : public Worker, private Job::Ctl +{ + struct JobEntry // Goes into worker and also out of worker as a finalize msg + { + std::unique_ptr job; + bool canceled = false; + std::exception_ptr eptr = nullptr; + }; + + // A message data for status updates. Only goes from worker to main thread. + struct StatusInfo { int status; std::string msg; }; + + // An arbitrary callback to be called on the main thread. Only from worker + // to main thread. + struct MainThreadCallData + { + std::function fn; + std::promise promise; + }; + + struct EmptyMessage {}; + + class WorkerMessage + { + public: + enum MsgType { Empty, Status, Finalize, MainThreadCall }; + + private: + boost::variant m_data; + + public: + WorkerMessage() = default; + WorkerMessage(int s, std::string txt) + : m_data{StatusInfo{s, std::move(txt)}} + {} + WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {} + WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {} + + int get_type () const { return m_data.which(); } + + void deliver(BoostThreadWorker &runner); + }; + + // The m_running state flag needs special attention. Previously, it was set simply in the run() + // method whenever a new job was taken from the input queue and unset after the finalize message + // was pushed into the output queue. This was not correct. It must not be possible to consume + // the finalize message before the flag gets unset, these two operations must be done atomically + // So the underlying queues are here extended to support handling of this m_running flag. + template + class RawQueue: public std::deque { + std::atomic *m_running_ptr; + + public: + using std::deque::deque; + explicit RawQueue(std::atomic &rflag): m_running_ptr{&rflag} {} + + void set_running() { m_running_ptr->store(true); } + void set_stopped() { m_running_ptr->store(false); } + }; + + // The running flag is set if a job is popped from the queue + template + class RawJobQueue: public RawQueue { + public: + using RawQueue::RawQueue; + void pop_front() + { + RawQueue::pop_front(); + this->set_running(); + } + }; + + // The running flag is unset when the finalize message is pushed into the queue + template + class RawMsgQueue: public RawQueue { + public: + using RawQueue::RawQueue; + void push_back(El &&entry) + { + this->emplace_back(std::move(entry)); + } + + template + auto & emplace_back(EArgs&&...args) + { + auto &el = RawQueue::emplace_back(std::forward(args)...); + if (el.get_type() == WorkerMessage::Finalize) + this->set_stopped(); + + return el; + } + }; + + using JobQueue = ThreadSafeQueueSPSC; + using MessageQueue = ThreadSafeQueueSPSC; + + boost::thread m_thread; + std::atomic m_running{false}, m_canceled{false}; + std::shared_ptr m_progress; + JobQueue m_input_queue; // from main thread to worker + MessageQueue m_output_queue; // form worker to main thread + std::string m_name; + + void run(); + + bool join(int timeout_ms = 0); + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override; + + bool was_canceled() const override { return m_canceled.load(); } + + std::future call_on_main_thread(std::function fn) override; + +public: + explicit BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes & attr, + const char * name = ""); + + explicit BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes && attr, + const char * name = "") + : BoostThreadWorker{std::move(pri), attr, name} + {} + + explicit BoostThreadWorker(std::shared_ptr pri, + const char * name = "") + : BoostThreadWorker{std::move(pri), {}, name} + {} + + ~BoostThreadWorker(); + + BoostThreadWorker(const BoostThreadWorker &) = delete; + BoostThreadWorker(BoostThreadWorker &&) = delete; + BoostThreadWorker &operator=(const BoostThreadWorker &) = delete; + BoostThreadWorker &operator=(BoostThreadWorker &&) = delete; + + bool push(std::unique_ptr job) override; + + bool is_idle() const override + { + // The assumption is that jobs can only be queued from a single main + // thread from which this method is also called. And the output + // messages are also processed only in this calling thread. In that + // case, if the input queue is empty, it will remain so during this + // function call. If the worker thread is also not running and the + // output queue is already processed, we can safely say that the + // worker is dormant. + return m_input_queue.empty() && !m_running.load() && m_output_queue.empty(); + } + + void cancel() override { m_canceled.store(true); } + void cancel_all() override { m_input_queue.clear(); cancel(); } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } + + void clear_percent() override + { + if (m_progress) + m_progress->clear_percent(); + } + + void show_error_info(wxString msg, int code, wxString description, wxString extra) override + { + if (m_progress) + m_progress->show_error_info(msg, code, description, extra); + } + + void process_events() override; + bool wait_for_current_job(unsigned timeout_ms = 0) override; + bool wait_for_idle(unsigned timeout_ms = 0) override; + +}; + +}} // namespace Slic3r::GUI + +#endif // BOOSTTHREADWORKER_HPP diff --git a/src/slic3r/GUI/Jobs/BusyCursorJob.hpp b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp new file mode 100644 index 0000000000..530213b1d5 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp @@ -0,0 +1,48 @@ +#ifndef BUSYCURSORJOB_HPP +#define BUSYCURSORJOB_HPP + +#include "Job.hpp" + +#include + +namespace Slic3r { namespace GUI { + +struct CursorSetterRAII +{ + Job::Ctl &ctl; + CursorSetterRAII(Job::Ctl &c) : ctl{c} + { + ctl.call_on_main_thread([] { wxBeginBusyCursor(); }); + } + ~CursorSetterRAII() + { + ctl.call_on_main_thread([] { wxEndBusyCursor(); }); + } +}; + +template +class BusyCursored: public Job { + JobSubclass m_job; + +public: + template + BusyCursored(Args &&...args) : m_job{std::forward(args)...} + {} + + void process(Ctl &ctl) override + { + CursorSetterRAII cursor_setter{ctl}; + m_job.process(ctl); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + m_job.finalize(canceled, eptr); + } +}; + + +} +} + +#endif // BUSYCURSORJOB_HPP diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index ae5be42a8e..d997f8e266 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -3,6 +3,8 @@ #include "libslic3r/Model.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ModelArrange.hpp" + +#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" @@ -197,8 +199,10 @@ void FillBedJob::prepare() p.translation(X) -= p.bed_idx * stride;*/ } -void FillBedJob::process() +void FillBedJob::process(Ctl &ctl) { + ctl.call_on_main_thread([this]{ prepare(); }).wait(); + if (m_object_idx == -1 || m_selected.empty()) return; update_arrange_params(params, m_plater->config(), m_selected); @@ -209,18 +213,22 @@ void FillBedJob::process() const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config(); if (params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer")) partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES); - + update_selected_items_inflation(m_selected, m_plater->config(), params); update_unselected_items_inflation(m_unselected, m_plater->config(), params); bool do_stop = false; - params.stopcondition = [this, &do_stop]() { - return was_canceled() || do_stop; + params.stopcondition = [&ctl, &do_stop]() { + return ctl.was_canceled() || do_stop; }; - params.progressind = [this](unsigned st,std::string str="") { + auto statustxt = _u8L("Filling"); + + ctl.update_status(0, statustxt); + + params.progressind = [this, &ctl, &statustxt](unsigned st, std::string str="") { if (st > 0) - update_status(st, _L("Filling") + " " + wxString::FromUTF8(str)); + ctl.update_status(st * 100 / status_range(), statustxt + " " + str); }; params.on_packed = [&do_stop] (const ArrangePolygon &ap) { @@ -246,15 +254,18 @@ void FillBedJob::process() arrangement::arrange(m_selected, m_unselected, m_bedpts, params); // finalize just here. - update_status(m_status_range, was_canceled() ? - _L("Bed filling canceled.") : - _L("Bed filling done.")); + ctl.update_status(100, ctl.was_canceled() ? + _u8L("Bed filling canceled.") : + _u8L("Bed filling done.")); } -void FillBedJob::finalize() +FillBedJob::FillBedJob() : m_plater{wxGetApp().plater()} {} + +void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) { // Ignore the arrange result if aborted. - if (was_canceled()) return; + if (canceled || eptr) + return; if (m_object_idx == -1) return; @@ -317,8 +328,6 @@ void FillBedJob::finalize() m_plater->update(); } - - Job::finalize(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index f78b1f9f2c..0f5d58b300 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -7,9 +7,9 @@ namespace Slic3r { namespace GUI { class Plater; -class FillBedJob : public PlaterJob +class FillBedJob : public Job { - int m_object_idx = -1; + int m_object_idx = -1; using ArrangePolygon = arrangement::ArrangePolygon; using ArrangePolygons = arrangement::ArrangePolygons; @@ -24,23 +24,20 @@ class FillBedJob : public PlaterJob arrangement::ArrangeParams params; int m_status_range = 0; - -protected: - - void prepare() override; - void process() override; + Plater *m_plater; public: - FillBedJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + void prepare(); + void process(Ctl &ctl) override; + + FillBedJob(); - int status_range() const override + int status_range() const /*override*/ { return m_status_range; } - void finalize() override; + void finalize(bool canceled, std::exception_ptr &e) override; }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp deleted file mode 100644 index 19c93643a2..0000000000 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include -#include - -#include "Job.hpp" -#include -#include - -namespace Slic3r { - -void GUI::Job::run(std::exception_ptr &eptr) -{ - m_running.store(true); - try { - process(); - } catch (...) { - eptr = std::current_exception(); - } - - m_running.store(false); - - // ensure to call the last status to finalize the job - update_status(status_range(), ""); -} - -void GUI::Job::update_status(int st, const wxString &msg) -{ - auto evt = new wxThreadEvent(wxEVT_THREAD, m_thread_evt_id); - evt->SetInt(st); - evt->SetString(msg); - wxQueueEvent(this, evt); -} - -void GUI::Job::update_percent_finish() -{ - m_progress->clear_percent(); -} - -void GUI::Job::show_error_info(wxString msg, int code, wxString description, wxString extra) -{ - m_progress->show_error_info(msg, code, description, extra); -} - -GUI::Job::Job(std::shared_ptr pri) - : m_progress(std::move(pri)) -{ - m_thread_evt_id = wxNewId(); - - Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { - if (m_finalizing) return; - - auto msg = evt.GetString(); - if (!msg.empty() && !m_worker_error) - m_progress->set_status_text(msg.ToUTF8().data()); - - if (m_finalized) return; - - m_progress->set_progress(evt.GetInt()); - if (evt.GetInt() == status_range() || m_worker_error) { - // set back the original range and cancel callback - m_progress->set_range(m_range); - // Make sure progress indicators get the last value of their range - // to make sure they close, fade out, whathever - m_progress->set_progress(m_range); - m_progress->set_cancel_callback(); - wxEndBusyCursor(); - - if (m_worker_error) { - m_finalized = true; - m_progress->set_status_text(""); - m_progress->set_progress(m_range); - on_exception(m_worker_error); - } - else { - // This is an RAII solution to remember that finalization is - // running. The run method calls update_status(status_range(), "") - // at the end, which queues up a call to this handler in all cases. - // If process also calls update_status with maxed out status arg - // it will call this handler twice. It is not a problem unless - // yield is called inside the finilize() method, which would - // jump out of finalize and call this handler again. - struct Finalizing { - bool &flag; - Finalizing (bool &f): flag(f) { flag = true; } - ~Finalizing() { flag = false; } - } fin(m_finalizing); - - finalize(); - } - - // dont do finalization again for the same process - m_finalized = true; - } - }, m_thread_evt_id); -} - -void GUI::Job::start() -{ // Start the job. No effect if the job is already running - if (!m_running.load()) { - prepare(); - - // Save the current status indicatior range and push the new one - m_range = m_progress->get_range(); - m_progress->set_range(status_range()); - - // init cancellation flag and set the cancel callback - m_canceled.store(false); - m_progress->set_cancel_callback( - [this]() { m_canceled.store(true); }); - - m_finalized = false; - m_finalizing = false; - - // Changing cursor to busy - wxBeginBusyCursor(); - - try { // Execute the job - m_worker_error = nullptr; - m_thread = create_thread([this] { this->run(m_worker_error); }); - } catch (std::exception &) { - update_status(status_range(), - _(L("Error! Unable to create thread!"))); - } - - // The state changes will be undone when the process hits the - // last status value, in the status update handler (see ctor) - } -} - -bool GUI::Job::join(int timeout_ms) -{ - if (!m_thread.joinable()) return true; - - if (timeout_ms <= 0) - m_thread.join(); - else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) - return false; - - return true; -} - -void GUI::ExclusiveJobGroup::start(size_t jid) { - assert(jid < m_jobs.size()); - stop_all(); - m_jobs[jid]->start(); -} - -void GUI::ExclusiveJobGroup::join_all(int wait_ms) -{ - std::vector aborted(m_jobs.size(), false); - - for (size_t jid = 0; jid < m_jobs.size(); ++jid) - aborted[jid] = m_jobs[jid]->join(wait_ms); - - if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; })) - BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; -} - -bool GUI::ExclusiveJobGroup::is_any_running() const -{ - return std::any_of(m_jobs.begin(), m_jobs.end(), - [](const std::unique_ptr &j) { - return j->is_running(); - }); -} - -} - diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index a92dfbfbd1..eba62d1760 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -3,128 +3,64 @@ #include #include +#include #include "libslic3r/libslic3r.h" - -#include - #include "ProgressIndicator.hpp" -#include - -#include - namespace Slic3r { namespace GUI { -// A class to handle UI jobs like arranging and optimizing rotation. -// These are not instant jobs, the user has to be informed about their -// state in the status progress indicator. On the other hand they are -// separated from the background slicing process. Ideally, these jobs should -// run when the background process is not running. -// -// TODO: A mechanism would be useful for blocking the plater interactions: -// objects would be frozen for the user. In case of arrange, an animation -// could be shown, or with the optimize orientations, partial results -// could be displayed. -class Job : public wxEvtHandler -{ - int m_range = 100; - int m_thread_evt_id = wxID_ANY; - boost::thread m_thread; - std::atomic m_running{false}, m_canceled{false}; - bool m_finalized = false, m_finalizing = false; - std::shared_ptr m_progress; - std::exception_ptr m_worker_error = nullptr; - - void run(std::exception_ptr &); - -protected: - // status range for a particular job - virtual int status_range() const { return 100; } - - // status update, to be used from the work thread (process() method) - void update_status(int st, const wxString &msg = ""); - - void update_percent_finish(); - - void show_error_info(wxString msg, int code, wxString description, wxString extra); - - bool was_canceled() const { return m_canceled.load(); } - - // Launched just before start(), a job can use it to prepare internals - virtual void prepare() {} - - // The method where the actual work of the job should be defined. - virtual void process() = 0; - - // Launched when the job is finished. It refreshes the 3Dscene by def. - virtual void finalize() { m_finalized = true; } - - // Exceptions occuring in process() are redirected from the worker thread - // into the main (UI) thread. This method is called from the main thread and - // can be overriden to handle these exceptions. - virtual void on_exception(const std::exception_ptr &eptr) - { - if (eptr) std::rethrow_exception(eptr); - } - +// A class representing a job that is to be run in the background, not blocking +// the main thread. Running it is up to a Worker object (see Worker interface) +class Job { public: + enum JobPrepareState { PREPARE_STATE_DEFAULT = 0, PREPARE_STATE_MENU = 1, }; - Job(std::shared_ptr pri); - - bool is_finalized() const { return m_finalized; } - - Job(const Job &) = delete; - Job(Job &&) = delete; - Job &operator=(const Job &) = delete; - Job &operator=(Job &&) = delete; - - void start(); - - // To wait for the running job and join the threads. False is - // returned if the timeout has been reached and the job is still - // running. Call cancel() before this fn if you want to explicitly - // end the job. - bool join(int timeout_ms = 0); - - bool is_running() const { return m_running.load(); } - void cancel() { m_canceled.store(true); } -}; + // A controller interface that informs the job about cancellation and + // makes it possible for the job to advertise its status. + class Ctl { + public: + virtual ~Ctl() = default; -// Jobs defined inside the group class will be managed so that only one can -// run at a time. Also, the background process will be stopped if a job is -// started. -class ExclusiveJobGroup -{ - static const int ABORT_WAIT_MAX_MS = 10000; - - std::vector> m_jobs; - -protected: - virtual void before_start() {} - -public: - virtual ~ExclusiveJobGroup() = default; - - size_t add_job(std::unique_ptr &&job) - { - m_jobs.emplace_back(std::move(job)); - return m_jobs.size() - 1; - } - - void start(size_t jid); - - void cancel_all() { for (auto& j : m_jobs) j->cancel(); } - - void join_all(int wait_ms = 0); - - void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } - - bool is_any_running() const; + // status update, to be used from the work thread (process() method) + virtual void update_status(int st, const std::string &msg = "") = 0; + + // clear the percentage on the ProgressIndicator + virtual void clear_percent() = 0; + + // show the provided error information on the ProgressIndicator + virtual void show_error_info(wxString msg, int code, wxString description, wxString extra) = 0; + + // Returns true if the job was asked to cancel itself. + virtual bool was_canceled() const = 0; + + // Execute a functor on the main thread. Note that the exact time of + // execution is hard to determine. This can be used to make modifications + // on the UI, like displaying some intermediate results or modify the + // cursor. + // This function returns a std::future object which enables the + // caller to optionally wait for the main thread to finish the function call. + virtual std::future call_on_main_thread(std::function fn) = 0; + }; + + virtual ~Job() = default; + + // The method where the actual work of the job should be defined. This is + // run on the worker thread. + virtual void process(Ctl &ctl) = 0; + + // Launched when the job is finished on the UI thread. + // If the job was cancelled, the first parameter will have a true value. + // Exceptions occuring in process() are redirected from the worker thread + // into the main (UI) thread. This method receives the exception and can + // handle it properly. Assign nullptr to this second argument before + // function return to prevent further action. Leaving it with a non-null + // value will result in rethrowing by the worker. + virtual void finalize(bool /*canceled*/, std::exception_ptr &) {} }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp index 13470f7ad4..bbe5898736 100644 --- a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp @@ -22,11 +22,15 @@ void NotificationProgressIndicator::set_range(int range) void NotificationProgressIndicator::set_cancel_callback(CancelFn fn) { - m_nm->progress_indicator_set_cancel_callback(std::move(fn)); + m_cancelfn = std::move(fn); + m_nm->progress_indicator_set_cancel_callback(m_cancelfn); } void NotificationProgressIndicator::set_progress(int pr) { + if (!pr) + set_cancel_callback(m_cancelfn); + m_nm->progress_indicator_set_progress(pr); } diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp index ba0e899165..cb1dac1f7c 100644 --- a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp @@ -9,7 +9,7 @@ class NotificationManager; class NotificationProgressIndicator: public ProgressIndicator { NotificationManager *m_nm = nullptr; - + CancelFn m_cancelfn; public: explicit NotificationProgressIndicator(NotificationManager *nm); diff --git a/src/slic3r/GUI/Jobs/OAuthJob.cpp b/src/slic3r/GUI/Jobs/OAuthJob.cpp new file mode 100644 index 0000000000..d47fe819f4 --- /dev/null +++ b/src/slic3r/GUI/Jobs/OAuthJob.cpp @@ -0,0 +1,129 @@ +#include "OAuthJob.hpp" + +#include "Http.hpp" +#include "ThreadSafeQueue.hpp" +#include "slic3r/GUI/I18N.hpp" + +#include + +namespace pt = boost::property_tree; +namespace jp = boost::property_tree::json_parser; + +namespace Slic3r { +namespace GUI { + +wxDEFINE_EVENT(EVT_OAUTH_COMPLETE_MESSAGE, wxCommandEvent); + +OAuthJob::OAuthJob(const OAuthData& input) : local_authorization_server(input.params.callback_port), _data(input) {} + +void OAuthJob::parse_token_response(const std::string& body, bool error, OAuthResult& result) +{ + pt::ptree j; + std::stringstream ss(body); + try { + jp::read_json(ss, j); + } catch (pt::json_parser_error& err) { + BOOST_LOG_TRIVIAL(warning) << "Invalid or no JSON data on token response: JSON Body = " << body + << ", Reason = " << err.what(); + error = true; + } + + if (error) { + result.error_message = j.get("error_description", _u8L("Unknown error")); + } else { + result.access_token = j.get("access_token"); + result.refresh_token = j.get("refresh_token"); + result.success = true; + } +} + + +void OAuthJob::process(Ctl& ctl) +{ + // Prepare auth process + ThreadSafeQueueSPSC queue; + + // Setup auth server to receive OAuth code from callback url + local_authorization_server.set_request_handler([this, &queue](const std::string& url) -> std::shared_ptr { + if (boost::contains(url, "/callback")) { + const auto code = url_get_param(url, "code"); + const auto state = url_get_param(url, "state"); + + const auto handle_auth_fail = [this, &queue](const std::string& message) -> std::shared_ptr { + queue.push(OAuthResult{false, message}); + return std::make_shared(this->_data.params.auth_fail_redirect_url); + }; + + if (state != _data.params.state) { + BOOST_LOG_TRIVIAL(warning) << "The provided state was not correct. Got " << state << " and expected " << _data.params.state; + return handle_auth_fail(_u8L("The provided state is not correct.")); + } + + if (code.empty()) { + const auto error_code = url_get_param(url, "error_code"); + if (error_code == "user_denied") { + BOOST_LOG_TRIVIAL(debug) << "User did not give the required permission when authorizing this application"; + return handle_auth_fail(_u8L("Please give the required permissions when authorizing this application.")); + } + + BOOST_LOG_TRIVIAL(warning) << "Unexpected error when logging in. Error_code: " << error_code << ", State: " << state; + return handle_auth_fail(_u8L("Something unexpected happened when trying to log in, please try again.")); + } + + + OAuthResult r; + // Request the access token from the authorization server. + auto http = Http::post(_data.params.token_url); + http.timeout_connect(5) + .timeout_max(5) + .form_add("client_id", _data.params.client_id) + .form_add("redirect_uri", _data.params.callback_url) + .form_add("grant_type", "authorization_code") + .form_add("code", code) + .form_add("code_verifier", _data.params.verification_code) + .form_add("scope", _data.params.scope) + .on_complete([&](std::string body, unsigned status) { parse_token_response(body, false, r); }) + .on_error([&](std::string body, std::string error, unsigned status) { parse_token_response(body, true, r); }) + .perform_sync(); + + queue.push(r); + return std::make_shared(r.success ? _data.params.auth_success_redirect_url : + _data.params.auth_fail_redirect_url); + } else { + queue.push(OAuthResult{false}); + return std::make_shared(); + } + }); + + // Run the local server + local_authorization_server.start(); + + // Wait until we received the result + bool received = false; + while (!ctl.was_canceled() && !received ) { + queue.consume_one(BlockingWait{1000}, [this, &received](const OAuthResult& result) { + *_data.result = result; + received = true; + }); + } + + // Handle timeout + if (!received && !ctl.was_canceled()) { + BOOST_LOG_TRIVIAL(debug) << "Timeout when authenticating with the account server."; + _data.result->error_message = _u8L("Timeout when authenticating with the account server."); + } else if (ctl.was_canceled()) { + _data.result->error_message = _u8L("User cancelled."); + } +} + +void OAuthJob::finalize(bool canceled, std::exception_ptr& e) +{ + // Make sure it's stopped + local_authorization_server.stop(); + + wxCommandEvent event(EVT_OAUTH_COMPLETE_MESSAGE); + event.SetEventObject(m_event_handle); + wxPostEvent(m_event_handle, event); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/OAuthJob.hpp b/src/slic3r/GUI/Jobs/OAuthJob.hpp new file mode 100644 index 0000000000..dd64e966be --- /dev/null +++ b/src/slic3r/GUI/Jobs/OAuthJob.hpp @@ -0,0 +1,62 @@ +#ifndef __OAuthJob_HPP__ +#define __OAuthJob_HPP__ + +#include "Job.hpp" +#include "slic3r/GUI/HttpServer.hpp" + +namespace Slic3r { +namespace GUI { + +class Plater; + +struct OAuthParams +{ + std::string login_url; + std::string client_id; + boost::asio::ip::port_type callback_port; + std::string callback_url; + std::string scope; + std::string response_type; + std::string auth_success_redirect_url; + std::string auth_fail_redirect_url; + std::string token_url; + std::string verification_code; + std::string state; +}; + +struct OAuthResult +{ + bool success{false}; + std::string error_message{""}; + std::string access_token{""}; + std::string refresh_token{""}; +}; + +struct OAuthData +{ + OAuthParams params; + std::shared_ptr result; +}; + +class OAuthJob : public Job +{ + HttpServer local_authorization_server; + OAuthData _data; + wxWindow* m_event_handle{nullptr}; + +public: + explicit OAuthJob(const OAuthData& input); + + void process(Ctl& ctl) override; + void finalize(bool canceled, std::exception_ptr& e) override; + + void set_event_handle(wxWindow* hanle) { m_event_handle = hanle; } + + static void parse_token_response(const std::string& body, bool error, OAuthResult& result); +}; + +wxDECLARE_EVENT(EVT_OAUTH_COMPLETE_MESSAGE, wxCommandEvent); + +}} // namespace Slic3r::GUI + +#endif // OAUTHJOB_HPP diff --git a/src/slic3r/GUI/Jobs/OrientJob.cpp b/src/slic3r/GUI/Jobs/OrientJob.cpp index 98a1891d48..19bdb18219 100644 --- a/src/slic3r/GUI/Jobs/OrientJob.cpp +++ b/src/slic3r/GUI/Jobs/OrientJob.cpp @@ -140,29 +140,23 @@ void OrientJob::prepare() int state = m_plater->get_prepare_state(); m_plater->get_notification_manager()->bbl_close_plateinfo_notification(); if (state == Job::JobPrepareState::PREPARE_STATE_DEFAULT) { - only_on_partplate = false; + // only_on_partplate = false; prepare_selected(); } else if (state == Job::JobPrepareState::PREPARE_STATE_MENU) { - only_on_partplate = true; // only arrange items on current plate + // only_on_partplate = true; // only arrange items on current plate prepare_partplate(); } } -void OrientJob::on_exception(const std::exception_ptr &eptr) +void OrientJob::process(Ctl& ctl) { - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (std::exception &) { - PlaterJob::on_exception(eptr); - } -} + static const auto arrangestr = _u8L("Orienting..."); + + ctl.update_status(0, arrangestr); + ctl.call_on_main_thread([this]{ prepare(); }).wait(); -void OrientJob::process() -{ auto start = std::chrono::steady_clock::now(); - static const auto arrangestr = _(L("Orienting...")); const GLCanvas3D::OrientSettings& settings = m_plater->canvas3D()->get_orient_settings(); @@ -177,11 +171,11 @@ void OrientJob::process() } auto count = unsigned(m_selected.size() + m_unprintable.size()); - params.stopcondition = [this]() { return was_canceled(); }; + params.stopcondition = [&ctl]() { return ctl.was_canceled(); }; - params.progressind = [this, count](unsigned st, std::string orientstr) { + params.progressind = [this, count, &ctl](unsigned st, std::string orientstr) { st += m_unprintable.size(); - if (st > 0) update_status(int(st / float(count) * 100), _L("Orienting") + " " + orientstr); + if (st > 0) ctl.update_status(int(st / float(count) * 100), _u8L("Orienting") + " " + orientstr); }; orientation::orient(m_selected, m_unselected, params); @@ -194,24 +188,33 @@ void OrientJob::process() << "Orientation: " << m_selected.back().orientation.transpose() << "; v,phi: " << m_selected.back().axis.transpose() << ", " << m_selected.back().angle << "; euler: " << m_selected.back().euler_angles.transpose(); // finalize just here. - //update_status(int(count), - // was_canceled() ? _(L("Orienting canceled.")) - // : _(L(ss.str().c_str()))); - wxGetApp().plater()->show_status_message(was_canceled() ? "Orienting canceled." : ss.str()); + ctl.update_status(100, + ctl.was_canceled() ? _u8L("Orienting canceled.") + : _u8L(ss.str().c_str())); + wxGetApp().plater()->show_status_message(ctl.was_canceled() ? "Orienting canceled." : ss.str()); } -void OrientJob::finalize() { +OrientJob::OrientJob() : m_plater(wxGetApp().plater()) {} + + void OrientJob::finalize(bool canceled, std::exception_ptr& eptr) { + try { + if (eptr) + std::rethrow_exception(eptr); + eptr = nullptr; + } catch (...) { + eptr = std::current_exception(); + } + // Ignore the arrange result if aborted. - if (!was_canceled()) { - for (OrientMesh& mesh : m_selected) { - mesh.apply(); - } - m_plater->update(); + if (canceled || eptr) return; - // BBS - //wxGetApp().obj_manipul()->set_dirty(); + for (OrientMesh& mesh : m_selected) { + mesh.apply(); } - Job::finalize(); + m_plater->update(); + + // BBS + //wxGetApp().obj_manipul()->set_dirty(); } orientation::OrientMesh OrientJob::get_orient_mesh(ModelInstance* instance) diff --git a/src/slic3r/GUI/Jobs/OrientJob.hpp b/src/slic3r/GUI/Jobs/OrientJob.hpp index 88907b6b99..3d82b34aba 100644 --- a/src/slic3r/GUI/Jobs/OrientJob.hpp +++ b/src/slic3r/GUI/Jobs/OrientJob.hpp @@ -1,7 +1,7 @@ #ifndef ORIENTJOB_HPP #define ORIENTJOB_HPP -#include "PlaterJob.hpp" +#include "Job.hpp" #include "libslic3r/Orient.hpp" namespace Slic3r { @@ -10,12 +10,15 @@ class ModelObject; namespace GUI { -class OrientJob : public PlaterJob +class Plater; + +class OrientJob : public Job { using OrientMesh = orientation::OrientMesh; using OrientMeshs = orientation::OrientMeshs; OrientMeshs m_selected, m_unselected, m_unprintable; + Plater* m_plater; // clear m_selected and m_unselected, reserve space for next usage void clear_input(); @@ -31,17 +34,14 @@ class OrientJob : public PlaterJob void prepare_partplate(); protected: - void prepare() override; - void on_exception(const std::exception_ptr &) override; + void prepare(); public: - OrientJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + OrientJob(); - void process() override; + void process(Ctl& ctl) override; - void finalize() override; + void finalize(bool canceled, std::exception_ptr& eptr) override; #if 0 static orientation::OrientMesh get_orient_mesh(ModelObject* obj, const Plater* plater) diff --git a/src/slic3r/GUI/Jobs/PlaterJob.cpp b/src/slic3r/GUI/Jobs/PlaterJob.cpp deleted file mode 100644 index 902cc57388..0000000000 --- a/src/slic3r/GUI/Jobs/PlaterJob.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "PlaterJob.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/Plater.hpp" - -namespace Slic3r { namespace GUI { - -void PlaterJob::on_exception(const std::exception_ptr &eptr) -{ - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (std::exception &e) { - show_error(m_plater, _(L("Exception")) + ": "+ e.what()); - } -} - -}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/PlaterJob.hpp b/src/slic3r/GUI/Jobs/PlaterJob.hpp deleted file mode 100644 index fa8a19c1e6..0000000000 --- a/src/slic3r/GUI/Jobs/PlaterJob.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef PLATERJOB_HPP -#define PLATERJOB_HPP - -#include "Job.hpp" - -namespace Slic3r { namespace GUI { - -class Plater; - -class PlaterJob : public Job { -protected: - Plater *m_plater; - //BBS: add flag for whether on current part plate - bool only_on_partplate{false}; - - void on_exception(const std::exception_ptr &) override; - -public: - - PlaterJob(std::shared_ptr pri, Plater *plater): - Job{std::move(pri)}, m_plater{plater} {} -}; - -}} // namespace Slic3r::GUI - -#endif // PLATERJOB_HPP diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp new file mode 100644 index 0000000000..62eef9e482 --- /dev/null +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -0,0 +1,158 @@ +#ifndef PLATERWORKER_HPP +#define PLATERWORKER_HPP + +#include +#include + +#include "Worker.hpp" +#include "BusyCursorJob.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +template +class PlaterWorker: public Worker { + WorkerSubclass m_w; + Plater *m_plater; + + class PlaterJob : public Job { + std::unique_ptr m_job; + Plater *m_plater; + long long m_process_duration; // [ms] + + public: + void process(Ctl &c) override + { + // Ensure that wxWidgets processing wakes up to handle outgoing + // messages in plater's wxIdle handler. Otherwise it might happen + // that the message will only be processed when an event like mouse + // move comes along which might be too late. + struct WakeUpCtl: Ctl { + Ctl &ctl; + WakeUpCtl(Ctl &c) : ctl{c} {} + + void update_status(int st, const std::string &msg = "") override + { + ctl.update_status(st, msg); + wxWakeUpIdle(); + } + + void clear_percent() override + { + ctl.clear_percent(); + wxWakeUpIdle(); + } + + void show_error_info(wxString msg, int code, wxString description, wxString extra) override + { + ctl.show_error_info(msg, code, description, extra); + wxWakeUpIdle(); + } + + bool was_canceled() const override { return ctl.was_canceled(); } + + std::future call_on_main_thread(std::function fn) override + { + auto ftr = ctl.call_on_main_thread(std::move(fn)); + wxWakeUpIdle(); + + return ftr; + } + + } wctl{c}; + + CursorSetterRAII busycursor{wctl}; + + using namespace std::chrono; + steady_clock::time_point process_start = steady_clock::now(); + m_job->process(wctl); + steady_clock::time_point process_end = steady_clock::now(); + m_process_duration = duration_cast(process_end - process_start).count(); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + using namespace std::chrono; + steady_clock::time_point finalize_start = steady_clock::now(); + m_job->finalize(canceled, eptr); + steady_clock::time_point finalize_end = steady_clock::now(); + long long finalize_duration = duration_cast(finalize_end - finalize_start).count(); + + BOOST_LOG_TRIVIAL(info) + << std::fixed // do not use scientific notations + << "Job '" << typeid(*m_job).name() << "' " + << "spend " << m_process_duration + finalize_duration << "ms " + << "(process " << m_process_duration << "ms + finalize " << finalize_duration << "ms)"; + + if (eptr) try { + std::rethrow_exception(eptr); + } catch (std::exception &e) { + show_error(m_plater, _L("An unexpected error occured: ") + e.what()); + eptr = nullptr; + } + } + + PlaterJob(Plater *p, std::unique_ptr j) + : m_job{std::move(j)}, m_plater{p} + { + // TODO: decide if disabling slice button during UI job is what we + // want. + // if (m_plater) + // m_plater->sidebar().enable_buttons(false); + } + + ~PlaterJob() override + { + // TODO: decide if disabling slice button during UI job is what we want. + + // Reload scene ensures that the slice button gets properly + // enabled or disabled after the job finishes, depending on the + // state of slicing. This might be an overkill but works for now. + // if (m_plater) + // m_plater->canvas3D()->reload_scene(false); + } + }; + +public: + + template + PlaterWorker(Plater *plater, WorkerArgs &&...args) + : m_w{std::forward(args)...}, m_plater{plater} + { + // Ensure that messages from the worker thread to the UI thread are + // processed continuously. + plater->Bind(wxEVT_IDLE, [this](wxIdleEvent &) { + process_events(); + }); + } + + // Always package the job argument into a PlaterJob + bool push(std::unique_ptr job) override + { + return m_w.push(std::make_unique(m_plater, std::move(job))); + } + + bool is_idle() const override { return m_w.is_idle(); } + void cancel() override { m_w.cancel(); } + void cancel_all() override { m_w.cancel_all(); } + void process_events() override { m_w.process_events(); } + bool wait_for_current_job(unsigned timeout_ms = 0) override + { + return m_w.wait_for_current_job(timeout_ms); + } + bool wait_for_idle(unsigned timeout_ms = 0) override + { + return m_w.wait_for_idle(timeout_ms); + } +}; + +}} // namespace Slic3r::GUI + +#endif // PLATERJOB_HPP diff --git a/src/slic3r/GUI/Jobs/PrintJob.cpp b/src/slic3r/GUI/Jobs/PrintJob.cpp index 6c50ca0c09..a646f192d7 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.cpp +++ b/src/slic3r/GUI/Jobs/PrintJob.cpp @@ -7,39 +7,40 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/MainFrame.hpp" +#include "slic3r/GUI/format.hpp" #include "bambu_networking.hpp" namespace Slic3r { namespace GUI { -static wxString check_gcode_failed_str = _L("Abnormal print file data. Please slice again."); -static wxString printjob_cancel_str = _L("Task canceled."); -static wxString timeout_to_upload_str = _L("Upload task timed out. Please check the network status and try again."); -static wxString failed_in_cloud_service_str = _L("Cloud service connection failed. Please try again."); -static wxString file_is_not_exists_str = _L("Print file not found. please slice again."); -static wxString file_over_size_str = _L("The print file exceeds the maximum allowable size (1GB). Please simplify the model and slice again."); -static wxString print_canceled_str = _L("Task canceled."); -static wxString send_print_failed_str = _L("Failed to send the print job. Please try again."); -static wxString upload_ftp_failed_str = _L("Failed to upload file to ftp. Please try again."); - -static wxString desc_network_error = _L("Check the current status of the bambu server by clicking on the link above."); -static wxString desc_file_too_large = _L("The size of the print file is too large. Please adjust the file size and try again."); -static wxString desc_fail_not_exist = _L("Print file not found, Please slice it again and send it for printing."); -static wxString desc_upload_ftp_failed = _L("Failed to upload print file to FTP. Please check the network status and try again."); - -static wxString sending_over_lan_str = _L("Sending print job over LAN"); -static wxString sending_over_cloud_str = _L("Sending print job through cloud service"); - -static wxString wait_sending_finish = _L("Print task sending times out."); -//static wxString desc_wait_sending_finish = _L("The printer timed out while receiving a print job. Please check if the network is functioning properly and send the print again."); -//static wxString desc_wait_sending_finish = _L("The printer timed out while receiving a print job. Please check if the network is functioning properly."); - -PrintJob::PrintJob(std::shared_ptr pri, Plater* plater, std::string dev_id) -: PlaterJob{ std::move(pri), plater }, +static auto check_gcode_failed_str = _u8L("Abnormal print file data. Please slice again."); +static auto printjob_cancel_str = _u8L("Task canceled."); +static auto timeout_to_upload_str = _u8L("Upload task timed out. Please check the network status and try again."); +static auto failed_in_cloud_service_str = _u8L("Cloud service connection failed. Please try again."); +static auto file_is_not_exists_str = _u8L("Print file not found. please slice again."); +static auto file_over_size_str = _u8L("The print file exceeds the maximum allowable size (1GB). Please simplify the model and slice again."); +static auto print_canceled_str = _u8L("Task canceled."); +static auto send_print_failed_str = _u8L("Failed to send the print job. Please try again."); +static auto upload_ftp_failed_str = _u8L("Failed to upload file to ftp. Please try again."); + +static auto desc_network_error = _u8L("Check the current status of the bambu server by clicking on the link above."); +static auto desc_file_too_large = _u8L("The size of the print file is too large. Please adjust the file size and try again."); +static auto desc_fail_not_exist = _u8L("Print file not found, Please slice it again and send it for printing."); +static auto desc_upload_ftp_failed = _u8L("Failed to upload print file to FTP. Please check the network status and try again."); + +static auto sending_over_lan_str = _u8L("Sending print job over LAN"); +static auto sending_over_cloud_str = _u8L("Sending print job through cloud service"); + +static auto wait_sending_finish = _u8L("Print task sending times out."); +//static auto desc_wait_sending_finish = _u8L("The printer timed out while receiving a print job. Please check if the network is functioning properly and send the print again."); +//static auto desc_wait_sending_finish = _u8L("The printer timed out while receiving a print job. Please check if the network is functioning properly."); + +PrintJob::PrintJob(std::string dev_id) +: m_plater(wxGetApp().plater()), m_dev_id(dev_id), m_is_calibration_task(false) { - m_print_job_completed_id = plater->get_print_finished_event(); + m_print_job_completed_id = m_plater->get_print_finished_event(); } void PrintJob::prepare() @@ -54,16 +55,6 @@ void PrintJob::prepare() } } -void PrintJob::on_exception(const std::exception_ptr &eptr) -{ - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (std::exception &e) { - PlaterJob::on_exception(eptr); - } -} - void PrintJob::on_success(std::function success) { m_success_fun = success; @@ -139,22 +130,25 @@ wxString PrintJob::get_http_error_msg(unsigned int status, std::string body) return wxEmptyString; } -void PrintJob::process() +void PrintJob::process(Ctl& ctl) { /* display info */ - wxString msg; + std::string msg; wxString error_str; int curr_percent = 10; NetworkAgent* m_agent = wxGetApp().getAgent(); AppConfig* config = wxGetApp().app_config; if (this->connection_type == "lan") { - msg = _L("Sending print job over LAN"); + msg = _u8L("Sending print job over LAN"); } else { - msg = _L("Sending print job through cloud service"); + msg = _u8L("Sending print job through cloud service"); } + ctl.update_status(0, msg); + ctl.call_on_main_thread([this] { prepare(); }).wait(); + int result = -1; unsigned int http_code; std::string http_body; @@ -170,12 +164,12 @@ void PrintJob::process() /* check gcode is valid */ if (!plate->is_valid_gcode_file() && m_print_type == "from_normal") { - update_status(curr_percent, check_gcode_failed_str); + ctl.update_status(curr_percent, check_gcode_failed_str); return; } - if (was_canceled()) { - update_status(curr_percent, printjob_cancel_str); + if (ctl.was_canceled()) { + ctl.update_status(curr_percent, printjob_cancel_str); return; } } @@ -357,7 +351,7 @@ void PrintJob::process() } wxString error_text; - wxString msg_text; + std::string msg_text; const int StagePercentPoint[(int)PrintingStageFinished + 1] = { @@ -380,54 +374,55 @@ void PrintJob::process() &error_str, &curr_percent, &error_text, - StagePercentPoint + StagePercentPoint, + &ctl ](int stage, int code, std::string info) { if (stage == BBL::SendingPrintJobStage::PrintingStageCreate && !is_try_lan_mode_failed) { if (this->connection_type == "lan") { - msg = _L("Sending print job over LAN"); + msg = _u8L("Sending print job over LAN"); } else { - msg = _L("Sending print job through cloud service"); + msg = _u8L("Sending print job through cloud service"); } } else if (stage == BBL::SendingPrintJobStage::PrintingStageUpload && !is_try_lan_mode_failed) { if (code >= 0 && code <= 100 && !info.empty()) { if (this->connection_type == "lan") { - msg = _L("Sending print job over LAN"); + msg = _u8L("Sending print job over LAN"); } else { - msg = _L("Sending print job through cloud service"); + msg = _u8L("Sending print job through cloud service"); } - msg += wxString::Format("(%s)", info); + msg += format("(%s)", info); } } else if (stage == BBL::SendingPrintJobStage::PrintingStageWaiting) { if (this->connection_type == "lan") { - msg = _L("Sending print job over LAN"); + msg = _u8L("Sending print job over LAN"); } else { - msg = _L("Sending print job through cloud service"); + msg = _u8L("Sending print job through cloud service"); } } else if (stage == BBL::SendingPrintJobStage::PrintingStageRecord && !is_try_lan_mode) { - msg = _L("Sending print configuration"); + msg = _u8L("Sending print configuration"); } else if (stage == BBL::SendingPrintJobStage::PrintingStageSending && !is_try_lan_mode) { if (this->connection_type == "lan") { - msg = _L("Sending print job over LAN"); + msg = _u8L("Sending print job over LAN"); } else { - msg = _L("Sending print job through cloud service"); + msg = _u8L("Sending print job through cloud service"); } } else if (stage == BBL::SendingPrintJobStage::PrintingStageFinished) { - msg = wxString::Format(_L("Successfully sent. Will automatically jump to the device page in %ss"), info); + msg = format(_u8L("Successfully sent. Will automatically jump to the device page in %ss"), info); if (m_print_job_completed_id == wxGetApp().plater()->get_send_calibration_finished_event()) { - msg = wxString::Format(_L("Successfully sent. Will automatically jump to the next page in %ss"), info); + msg = format(_u8L("Successfully sent. Will automatically jump to the next page in %ss"), info); } - this->update_percent_finish(); + ctl.clear_percent(); } else { if (this->connection_type == "lan") { - msg = _L("Sending print job over LAN"); + msg = _u8L("Sending print job over LAN"); } else { - msg = _L("Sending print job through cloud service"); + msg = _u8L("Sending print job through cloud service"); } } @@ -444,22 +439,22 @@ void PrintJob::process() //get errors if (code > 100 || code < 0 || stage == BBL::SendingPrintJobStage::PrintingStageERROR) { if (code == BAMBU_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE || code == BAMBU_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE) { - m_plater->update_print_error_info(code, desc_file_too_large.ToStdString(), info); + m_plater->update_print_error_info(code, desc_file_too_large, info); }else if (code == BAMBU_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || code == BAMBU_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST){ - m_plater->update_print_error_info(code, desc_fail_not_exist.ToStdString(), info); + m_plater->update_print_error_info(code, desc_fail_not_exist, info); }else if (code == BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED || code == BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED) { - m_plater->update_print_error_info(code, desc_upload_ftp_failed.ToStdString(), info); + m_plater->update_print_error_info(code, desc_upload_ftp_failed, info); }else { - m_plater->update_print_error_info(code, desc_network_error.ToStdString(), info); + m_plater->update_print_error_info(code, desc_network_error, info); } } else { - this->update_status(curr_percent, msg); + ctl.update_status(curr_percent, msg); } }; - auto cancel_fn = [this]() { - return was_canceled(); + auto cancel_fn = [&ctl]() { + return ctl.was_canceled(); }; @@ -528,7 +523,7 @@ void PrintJob::process() //use ftp only if (m_print_type == "from_sdcard_view") { BOOST_LOG_TRIVIAL(info) << "print_job: try to send with cloud, model is sdcard view"; - this->update_status(curr_percent, _L("Sending print job through cloud service")); + ctl.update_status(curr_percent, _u8L("Sending print job through cloud service")); result = m_agent->start_sdcard_print(params, update_fn, cancel_fn); } else if (!wxGetApp().app_config->get("lan_mode_only").empty() && wxGetApp().app_config->get("lan_mode_only") == "1") { @@ -539,7 +534,7 @@ void PrintJob::process() } else { BOOST_LOG_TRIVIAL(info) << "print_job: use ftp send print only"; - this->update_status(curr_percent, _L("Sending print job over LAN")); + ctl.update_status(curr_percent, _u8L("Sending print job over LAN")); is_try_lan_mode = true; result = m_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn); if (result < 0) { @@ -556,7 +551,7 @@ void PrintJob::process() && this->has_sdcard) { // try to send local with record BOOST_LOG_TRIVIAL(info) << "print_job: try to start local print with record"; - this->update_status(curr_percent, _L("Sending print job over LAN")); + ctl.update_status(curr_percent, _u8L("Sending print job over LAN")); result = m_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn); if (result == 0) { params.comments = ""; @@ -571,22 +566,22 @@ void PrintJob::process() is_try_lan_mode_failed = true; // try to send with cloud BOOST_LOG_TRIVIAL(warning) << "print_job: try to send with cloud"; - this->update_status(curr_percent, _L("Sending print job through cloud service")); + ctl.update_status(curr_percent, _u8L("Sending print job through cloud service")); result = m_agent->start_print(params, update_fn, cancel_fn, wait_fn); } } else { BOOST_LOG_TRIVIAL(info) << "print_job: send with cloud"; - this->update_status(curr_percent, _L("Sending print job through cloud service")); + ctl.update_status(curr_percent, _u8L("Sending print job through cloud service")); result = m_agent->start_print(params, update_fn, cancel_fn, wait_fn); } } } else { if (this->has_sdcard) { - this->update_status(curr_percent, _L("Sending print job over LAN")); + ctl.update_status(curr_percent, _u8L("Sending print job over LAN")); result = m_agent->start_local_print(params, update_fn, cancel_fn); } else { - this->update_status(curr_percent, _L("An SD card needs to be inserted before printing via LAN.")); + ctl.update_status(curr_percent, _u8L("An SD card needs to be inserted before printing via LAN.")); return; } } @@ -606,13 +601,13 @@ void PrintJob::process() msg_text = upload_ftp_failed_str; } else if (result == BAMBU_NETWORK_ERR_CANCELED) { msg_text = print_canceled_str; - this->update_status(0, msg_text); + ctl.update_status(0, msg_text); } else { msg_text = send_print_failed_str; } if (result != BAMBU_NETWORK_ERR_CANCELED) { - this->show_error_info(msg_text, 0, "", ""); + ctl.show_error_info(msg_text, 0, "", ""); } BOOST_LOG_TRIVIAL(error) << "print_job: failed, result = " << result; @@ -638,10 +633,16 @@ void PrintJob::process() } } -void PrintJob::finalize() { - if (was_canceled()) return; +void PrintJob::finalize(bool canceled, std::exception_ptr& eptr) { + try { + if (eptr) + std::rethrow_exception(eptr); + eptr = nullptr; + } catch (...) { + eptr = std::current_exception(); + } - Job::finalize(); + if (canceled || eptr) return; } void PrintJob::set_project_name(std::string name) @@ -665,10 +666,10 @@ void PrintJob::on_check_ip_address_success(std::function func) m_enter_ip_address_fun_success = func; } -void PrintJob::connect_to_local_mqtt() -{ - this->update_status(0, wxEmptyString); -} +// void PrintJob::connect_to_local_mqtt() +// { +// this->update_status(0, wxEmptyString); +// } void PrintJob::set_calibration_task(bool is_calibration) { diff --git a/src/slic3r/GUI/Jobs/PrintJob.hpp b/src/slic3r/GUI/Jobs/PrintJob.hpp index 88318c1384..8a416bca24 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.hpp +++ b/src/slic3r/GUI/Jobs/PrintJob.hpp @@ -4,13 +4,15 @@ #include #include #include "libslic3r/PrintConfig.hpp" -#include "PlaterJob.hpp" +#include "Job.hpp" namespace fs = boost::filesystem; namespace Slic3r { namespace GUI { +class Plater; + #define PRINT_JOB_SENDING_TIMEOUT 25 class PrintPrepareData @@ -35,7 +37,7 @@ class PlateListData BedType bed_type = BedType::btDefault; }; -class PrintJob : public PlaterJob +class PrintJob : public Job { std::function m_success_fun{nullptr}; std::string m_dev_id; @@ -44,16 +46,16 @@ class PrintJob : public PlaterJob wxString m_completed_evt_data; std::function m_enter_ip_address_fun_fail{ nullptr }; std::function m_enter_ip_address_fun_success{ nullptr }; + Plater* m_plater; public: PrintPrepareData job_data; PlateListData plate_data; protected: - void prepare() override; - void on_exception(const std::exception_ptr &) override; + void prepare(); public: - PrintJob(std::shared_ptr pri, Plater *plater, std::string dev_id = ""); + PrintJob(std::string dev_id = ""); std::string m_project_name; std::string m_dev_ip; @@ -69,7 +71,7 @@ class PrintJob : public PlaterJob bool m_is_calibration_task = false; int m_print_from_sdc_plate_idx = 0; - + bool m_local_use_ssl_for_mqtt { true }; bool m_local_use_ssl_for_ftp { true }; bool task_bed_leveling; @@ -81,7 +83,7 @@ class PrintJob : public PlaterJob bool has_sdcard { false }; bool task_use_ams { true }; - void set_print_config(std::string bed_type, bool bed_leveling, bool flow_cali, bool vabration_cali, bool record_timelapse, bool layer_inspect) + void set_print_config(std::string bed_type, bool bed_leveling, bool flow_cali, bool vabration_cali, bool record_timelapse, bool layer_inspect) { task_bed_type = bed_type; task_bed_leveling = bed_leveling; @@ -91,7 +93,7 @@ class PrintJob : public PlaterJob task_layer_inspect = layer_inspect; } - int status_range() const override + int status_range() const { return 100; } @@ -102,13 +104,13 @@ class PrintJob : public PlaterJob m_completed_evt_data = evt_data; } void on_success(std::function success); - void process() override; - void finalize() override; + void process(Ctl& ctl) override; + void finalize(bool canceled, std::exception_ptr& eptr) override; void set_project_name(std::string name); void set_dst_name(std::string path); void on_check_ip_address_fail(std::function func); void on_check_ip_address_success(std::function func); - void connect_to_local_mqtt(); + // void connect_to_local_mqtt(); wxString get_http_error_msg(unsigned int status, std::string body); std::string truncate_string(const std::string& str, size_t maxLength); void set_calibration_task(bool is_calibration); diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 8cb5dc6c33..53c89662fb 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -12,6 +12,8 @@ #include "slic3r/GUI/GUI_App.hpp" #include "libslic3r/AppConfig.hpp" +#include + namespace Slic3r { namespace GUI { void RotoptimizeJob::prepare() @@ -45,21 +47,23 @@ void RotoptimizeJob::prepare() } } -void RotoptimizeJob::process() +void RotoptimizeJob::process(Ctl &ctl) { int prev_status = 0; + auto statustxt = _u8L("Searching for optimal orientation"); + ctl.update_status(0, statustxt); + auto params = sla::RotOptimizeParams{} .accuracy(m_accuracy) .print_config(&m_default_print_cfg) - .statucb([this, &prev_status](int s) + .statucb([this, &prev_status, &ctl](int s) { - if (s > 0 && s < 100) - ; - // update_status(prev_status + s / m_selected_object_ids.size(), - // _(L("Searching for optimal orientation..."))); + // if (s > 0 && s < 100) + // ctl.update_status(prev_status + s / m_selected_object_ids.size(), + // statustxt); - return !was_canceled(); + return !ctl.was_canceled(); }); @@ -72,16 +76,20 @@ void RotoptimizeJob::process() prev_status += 100 / m_selected_object_ids.size(); - if (was_canceled()) break; + if (ctl.was_canceled()) break; } - // update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : - // _(L("Orientation found."))); + ctl.update_status(100, ctl.was_canceled() ? + _u8L("Orientation search canceled.") : + _u8L("Orientation found.")); } -void RotoptimizeJob::finalize() +RotoptimizeJob::RotoptimizeJob() : m_plater{wxGetApp().plater()} { prepare(); } + +void RotoptimizeJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (was_canceled()) return; + if (canceled || eptr) + return; for (const ObjRot &objrot : m_selected_object_ids) { ModelObject *o = m_plater->model().objects[size_t(objrot.idx)]; @@ -112,10 +120,8 @@ void RotoptimizeJob::finalize() // m_plater->find_new_position(o->instances); } - if (!was_canceled()) + if (!canceled) m_plater->update(); - - Job::finalize(); } }} diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index cdb367f23a..71a28deb7c 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -1,16 +1,17 @@ #ifndef ROTOPTIMIZEJOB_HPP #define ROTOPTIMIZEJOB_HPP -#include "PlaterJob.hpp" +#include "Job.hpp" #include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/I18N.hpp" -namespace Slic3r { +namespace Slic3r { namespace GUI { -namespace GUI { +class Plater; -class RotoptimizeJob : public PlaterJob +class RotoptimizeJob : public Job { using FindFn = std::function; @@ -44,19 +45,16 @@ class RotoptimizeJob : public PlaterJob }; std::vector m_selected_object_ids; - -protected: - - void prepare() override; - void process() override; + Plater *m_plater; public: - RotoptimizeJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + void prepare(); + void process(Ctl &ctl) override; + + RotoptimizeJob(); - void finalize() override; + void finalize(bool canceled, std::exception_ptr &) override; static constexpr size_t get_methods_count() { return std::size(Methods); } diff --git a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp new file mode 100644 index 0000000000..7dbecff2ae --- /dev/null +++ b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp @@ -0,0 +1,114 @@ +#ifndef SLAIMPORTDIALOG_HPP +#define SLAIMPORTDIALOG_HPP + +#include "SLAImportJob.hpp" + +#include +#include +#include +#include +#include + +#include "libslic3r/AppConfig.hpp" +#include "slic3r/GUI/I18N.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" + +//#include "libslic3r/Model.hpp" +//#include "libslic3r/PresetBundle.hpp" + +namespace Slic3r { namespace GUI { + +class SLAImportDialog: public wxDialog, public SLAImportJobView { + wxFilePickerCtrl *m_filepicker; + wxComboBox *m_import_dropdown, *m_quality_dropdown; + +public: + SLAImportDialog(Plater *plater) + : wxDialog{plater, wxID_ANY, "Import SLA archive"} + { + auto szvert = new wxBoxSizer{wxVERTICAL}; + auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; + + m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, + from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), + "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", + wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); + szfilepck->Add(m_filepicker, 1); + szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); + + auto szchoices = new wxBoxSizer{wxHORIZONTAL}; + + static const std::vector inp_choices = { + _(L("Import model and profile")), + _(L("Import profile only")), + _(L("Import model only")) + }; + + m_import_dropdown = new wxComboBox( + this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, + inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + + szchoices->Add(m_import_dropdown); + szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5); + + static const std::vector qual_choices = { + _(L("Accurate")), + _(L("Balanced")), + _(L("Quick")) + }; + + m_quality_dropdown = new wxComboBox( + this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, + qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + szchoices->Add(m_quality_dropdown); + + m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { + if (get_selection() == Sel::profileOnly) + m_quality_dropdown->Disable(); + else m_quality_dropdown->Enable(); + }); + + szvert->Add(szchoices, 0, wxALL, 5); + szvert->AddStretchSpacer(1); + auto szbtn = new wxBoxSizer(wxHORIZONTAL); + szbtn->Add(new wxButton{this, wxID_CANCEL}); + szbtn->Add(new wxButton{this, wxID_OK}); + szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); + + SetSizerAndFit(szvert); + } + + Sel get_selection() const override + { + int sel = m_import_dropdown->GetSelection(); + return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); + } + + Vec2i get_marchsq_windowsize() const override + { + enum { Accurate, Balanced, Fast}; + + switch(m_quality_dropdown->GetSelection()) + { + case Fast: return {8, 8}; + case Balanced: return {4, 4}; + default: + case Accurate: + return {2, 2}; + } + } + + std::string get_path() const override + { + return m_filepicker->GetPath().ToUTF8().data(); + } +}; + +}} // namespace Slic3r::GUI + +#endif // SLAIMPORTDIALOG_HPP diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 0d42cec2d3..1bb8cdf6c6 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -3,7 +3,6 @@ #include "libslic3r/Format/SL1.hpp" #include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/NotificationManager.hpp" @@ -11,104 +10,10 @@ #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" -#include -#include -#include #include -#include namespace Slic3r { namespace GUI { -enum class Sel { modelAndProfile, profileOnly, modelOnly}; - -class ImportDlg: public wxDialog { - wxFilePickerCtrl *m_filepicker; - wxComboBox *m_import_dropdown, *m_quality_dropdown; - -public: - ImportDlg(Plater *plater) - : wxDialog{plater, wxID_ANY, "Import SLA archive"} - { - auto szvert = new wxBoxSizer{wxVERTICAL}; - auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; - - m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, - from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), - "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", - wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); - szfilepck->Add(m_filepicker, 1); - szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); - - auto szchoices = new wxBoxSizer{wxHORIZONTAL}; - - static const std::vector inp_choices = { - _(L("Import model and profile")), - _(L("Import profile only")), - _(L("Import model only")) - }; - - m_import_dropdown = new wxComboBox( - this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, - inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); - - szchoices->Add(m_import_dropdown); - szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5); - - static const std::vector qual_choices = { - _(L("Accurate")), - _(L("Balanced")), - _(L("Quick")) - }; - - m_quality_dropdown = new wxComboBox( - this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, - qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); - szchoices->Add(m_quality_dropdown); - - m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { - if (get_selection() == Sel::profileOnly) - m_quality_dropdown->Disable(); - else m_quality_dropdown->Enable(); - }); - - szvert->Add(szchoices, 0, wxALL, 5); - szvert->AddStretchSpacer(1); - auto szbtn = new wxBoxSizer(wxHORIZONTAL); - szbtn->Add(new wxButton{this, wxID_CANCEL}); - szbtn->Add(new wxButton{this, wxID_OK}); - szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); - - SetSizerAndFit(szvert); - } - - Sel get_selection() const - { - int sel = m_import_dropdown->GetSelection(); - return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); - } - - Vec2i get_marchsq_windowsize() const - { - enum { Accurate, Balanced, Fast}; - - switch(m_quality_dropdown->GetSelection()) - { - case Fast: return {8, 8}; - case Balanced: return {4, 4}; - default: - case Accurate: - return {2, 2}; - } - } - - wxString get_path() const - { - return m_filepicker->GetPath(); - } -}; - class SLAImportJob::priv { public: Plater *plater; @@ -122,23 +27,28 @@ class SLAImportJob::priv { std::string err; ConfigSubstitutions config_substitutions; - ImportDlg import_dlg; + const SLAImportJobView * import_dlg; - priv(Plater *plt) : plater{plt}, import_dlg{plt} {} + priv(Plater *plt, const SLAImportJobView *view) : plater{plt}, import_dlg{view} {} }; -SLAImportJob::SLAImportJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater}, p{std::make_unique(plater)} -{} +SLAImportJob::SLAImportJob(const SLAImportJobView *view) + : p{std::make_unique(wxGetApp().plater(), view)} +{ + prepare(); +} SLAImportJob::~SLAImportJob() = default; -void SLAImportJob::process() +void SLAImportJob::process(Ctl &ctl) { - auto progr = [this](int s) { + auto statustxt = _u8L("Importing SLA archive"); + ctl.update_status(0, statustxt); + + auto progr = [&ctl, &statustxt](int s) { if (s < 100) - update_status(int(s), _(L("Importing SLA archive"))); - return !was_canceled(); + ctl.update_status(int(s), statustxt); + return !ctl.was_canceled(); }; if (p->path.empty()) return; @@ -161,15 +71,15 @@ void SLAImportJob::process() p->err = ex.what(); } - update_status(100, was_canceled() ? _(L("Importing canceled.")) : - _(L("Importing done."))); + ctl.update_status(100, ctl.was_canceled() ? _u8L("Importing canceled.") : + _u8L("Importing done.")); } void SLAImportJob::reset() { p->sel = Sel::modelAndProfile; p->mesh = {}; - p->profile = m_plater->sla_print().full_print_config(); + p->profile = p->plater->sla_print().full_print_config(); p->win = {2, 2}; p->path.Clear(); } @@ -178,22 +88,19 @@ void SLAImportJob::prepare() { reset(); - if (p->import_dlg.ShowModal() == wxID_OK) { - auto path = p->import_dlg.get_path(); - auto nm = wxFileName(path); - p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); - p->sel = p->import_dlg.get_selection(); - p->win = p->import_dlg.get_marchsq_windowsize(); - p->config_substitutions.clear(); - } else { - p->path = ""; - } + auto path = p->import_dlg->get_path(); + auto nm = wxFileName(path); + p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); + p->sel = p->import_dlg->get_selection(); + p->win = p->import_dlg->get_marchsq_windowsize(); + p->config_substitutions.clear(); } -void SLAImportJob::finalize() +void SLAImportJob::finalize(bool canceled, std::exception_ptr &eptr) { // Ignore the arrange result if aborted. - if (was_canceled()) return; + if (canceled || eptr) + return; if (!p->err.empty()) { show_error(p->plater, p->err); @@ -204,7 +111,7 @@ void SLAImportJob::finalize() std::string name = wxFileName(p->path).GetName().ToUTF8().data(); if (p->profile.empty()) { - m_plater->get_notification_manager()->push_notification( + p->plater->get_notification_manager()->push_notification( NotificationType::CustomNotification, NotificationManager::NotificationLevel::WarningNotificationLevel, _L("The imported SLA archive did not contain any presets. " @@ -213,7 +120,7 @@ void SLAImportJob::finalize() if (p->sel != Sel::modelOnly) { if (p->profile.empty()) - p->profile = m_plater->sla_print().full_print_config(); + p->profile = p->plater->sla_print().full_print_config(); const ModelObjectPtrs& objects = p->plater->model().objects; for (auto object : objects) diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp index c2ca10ef69..b2aea8bf89 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp @@ -1,22 +1,37 @@ #ifndef SLAIMPORTJOB_HPP #define SLAIMPORTJOB_HPP -#include "PlaterJob.hpp" +#include "Job.hpp" + +#include "libslic3r/Point.hpp" namespace Slic3r { namespace GUI { -class SLAImportJob : public PlaterJob { +class SLAImportJobView { +public: + enum Sel { modelAndProfile, profileOnly, modelOnly}; + + virtual ~SLAImportJobView() = default; + + virtual Sel get_selection() const = 0; + virtual Vec2i get_marchsq_windowsize() const = 0; + virtual std::string get_path() const = 0; +}; + +class Plater; + +class SLAImportJob : public Job { class priv; std::unique_ptr p; - -protected: - void prepare() override; - void process() override; - void finalize() override; + using Sel = SLAImportJobView::Sel; public: - SLAImportJob(std::shared_ptr pri, Plater *plater); + void prepare(); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &) override; + + SLAImportJob(const SLAImportJobView *); ~SLAImportJob(); void reset(); diff --git a/src/slic3r/GUI/Jobs/SendJob.cpp b/src/slic3r/GUI/Jobs/SendJob.cpp index 602abfb148..fdfe45f8b6 100644 --- a/src/slic3r/GUI/Jobs/SendJob.cpp +++ b/src/slic3r/GUI/Jobs/SendJob.cpp @@ -5,33 +5,34 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/format.hpp" namespace Slic3r { namespace GUI { -static wxString check_gcode_failed_str = _L("Abnormal print file data. Please slice again."); -static wxString printjob_cancel_str = _L("Task canceled."); -static wxString timeout_to_upload_str = _L("Upload task timed out. Please check the network status and try again."); -static wxString failed_in_cloud_service_str = _L("Cloud service connection failed. Please try again."); -static wxString file_is_not_exists_str = _L("Print file not found. please slice again."); -static wxString file_over_size_str = _L("The print file exceeds the maximum allowable size (1GB). Please simplify the model and slice again."); -static wxString print_canceled_str = _L("Task canceled."); -static wxString send_print_failed_str = _L("Failed to send the print job. Please try again."); -static wxString upload_ftp_failed_str = _L("Failed to upload file to ftp. Please try again."); - -static wxString desc_network_error = _L("Check the current status of the bambu server by clicking on the link above."); -static wxString desc_file_too_large = _L("The size of the print file is too large. Please adjust the file size and try again."); -static wxString desc_fail_not_exist = _L("Print file not found, Please slice it again and send it for printing."); -static wxString desc_upload_ftp_failed = _L("Failed to upload print file to FTP. Please check the network status and try again."); - -static wxString sending_over_lan_str = _L("Sending print job over LAN"); -static wxString sending_over_cloud_str = _L("Sending print job through cloud service"); - -SendJob::SendJob(std::shared_ptr pri, Plater* plater, std::string dev_id) -: PlaterJob{ std::move(pri), plater }, +static auto check_gcode_failed_str = _u8L("Abnormal print file data. Please slice again."); +static auto printjob_cancel_str = _u8L("Task canceled."); +static auto timeout_to_upload_str = _u8L("Upload task timed out. Please check the network status and try again."); +static auto failed_in_cloud_service_str = _u8L("Cloud service connection failed. Please try again."); +static auto file_is_not_exists_str = _u8L("Print file not found. please slice again."); +static auto file_over_size_str = _u8L("The print file exceeds the maximum allowable size (1GB). Please simplify the model and slice again."); +static auto print_canceled_str = _u8L("Task canceled."); +static auto send_print_failed_str = _u8L("Failed to send the print job. Please try again."); +static auto upload_ftp_failed_str = _u8L("Failed to upload file to ftp. Please try again."); + +static auto desc_network_error = _u8L("Check the current status of the bambu server by clicking on the link above."); +static auto desc_file_too_large = _u8L("The size of the print file is too large. Please adjust the file size and try again."); +static auto desc_fail_not_exist = _u8L("Print file not found, Please slice it again and send it for printing."); +static auto desc_upload_ftp_failed = _u8L("Failed to upload print file to FTP. Please check the network status and try again."); + +static auto sending_over_lan_str = _u8L("Sending print job over LAN"); +static auto sending_over_cloud_str = _u8L("Sending print job through cloud service"); + +SendJob::SendJob(std::string dev_id) +: m_plater(wxGetApp().plater()), m_dev_id(dev_id) { - m_print_job_completed_id = plater->get_send_finished_event(); + m_print_job_completed_id = m_plater->get_send_finished_event(); } void SendJob::prepare() @@ -45,16 +46,6 @@ void SendJob::prepare() } } -void SendJob::on_exception(const std::exception_ptr &eptr) -{ - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (std::exception &e) { - PlaterJob::on_exception(eptr); - } -} - wxString SendJob::get_http_error_msg(unsigned int status, std::string body) { int code = 0; @@ -112,10 +103,10 @@ inline std::string get_transform_string(int bytes) return buffer; } -void SendJob::process() +void SendJob::process(Ctl& ctl) { BBL::PrintParams params; - wxString msg; + std::string msg; int curr_percent = 10; NetworkAgent* m_agent = wxGetApp().getAgent(); AppConfig* config = wxGetApp().app_config; @@ -154,14 +145,17 @@ void SendJob::process() /* display info */ - msg = _L("Sending gcode file over LAN"); + msg = _u8L("Sending gcode file over LAN"); /* if (this->connection_type == "lan") { - msg = _L("Sending gcode file over LAN"); + msg = _u8L("Sending gcode file over LAN"); } else { - msg = _L("Sending gcode file through cloud service"); + msg = _u8L("Sending gcode file through cloud service"); }*/ + ctl.update_status(0, msg); + ctl.call_on_main_thread([this]{ prepare(); }).wait(); + int total_plate_num = m_plater->get_partplate_list().get_plate_count(); PartPlate* plate = m_plater->get_partplate_list().get_plate(job_data.plate_idx); @@ -183,19 +177,19 @@ void SendJob::process() } if (plate == nullptr) { BOOST_LOG_TRIVIAL(error) << "can not find plate with valid gcode file when sending to print, plate_index="<< job_data.plate_idx; - update_status(curr_percent, check_gcode_failed_str); + ctl.update_status(curr_percent, check_gcode_failed_str); return; } } /* check gcode is valid */ if (!plate->is_valid_gcode_file()) { - update_status(curr_percent, check_gcode_failed_str); + ctl.update_status(curr_percent, check_gcode_failed_str); return; } - if (was_canceled()) { - update_status(curr_percent, printjob_cancel_str); + if (ctl.was_canceled()) { + ctl.update_status(curr_percent, printjob_cancel_str); return; } @@ -235,36 +229,36 @@ void SendJob::process() 100 // PrintingStageFinished }; - auto update_fn = [this, &msg, &curr_percent, &error_text, StagePercentPoint](int stage, int code, std::string info) { + auto update_fn = [this, &msg, &curr_percent, &error_text, StagePercentPoint, &ctl](int stage, int code, std::string info) { if (stage == SendingPrintJobStage::PrintingStageCreate) { if (this->connection_type == "lan") { - msg = _L("Sending gcode file over LAN"); + msg = _u8L("Sending gcode file over LAN"); } else { - msg = _L("Sending gcode file to sdcard"); + msg = _u8L("Sending gcode file to sdcard"); } } else if (stage == SendingPrintJobStage::PrintingStageUpload) { if (code >= 0 && code <= 100 && !info.empty()) { if (this->connection_type == "lan") { - msg = _L("Sending gcode file over LAN"); + msg = _u8L("Sending gcode file over LAN"); } else { - msg = _L("Sending gcode file to sdcard"); + msg = _u8L("Sending gcode file to sdcard"); } if (!info.empty()) { - msg += wxString::Format("(%s)", info); + msg += format("(%s)", info); } } } else if (stage == SendingPrintJobStage::PrintingStageFinished) { - msg = wxString::Format(_L("Successfully sent. Close current page in %s s"), info); + msg = format(_u8L("Successfully sent. Close current page in %s s"), info); } else { if (this->connection_type == "lan") { - msg = _L("Sending gcode file over LAN"); + msg = _u8L("Sending gcode file over LAN"); } else { - msg = _L("Sending gcode file over LAN"); + msg = _u8L("Sending gcode file over LAN"); } } @@ -280,26 +274,26 @@ void SendJob::process() //get errors if (code > 100 || code < 0 || stage == BBL::SendingPrintJobStage::PrintingStageERROR) { if (code == BAMBU_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE || code == BAMBU_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE) { - m_plater->update_print_error_info(code, desc_file_too_large.ToStdString(), info); + m_plater->update_print_error_info(code, desc_file_too_large, info); } else if (code == BAMBU_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || code == BAMBU_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST) { - m_plater->update_print_error_info(code, desc_fail_not_exist.ToStdString(), info); + m_plater->update_print_error_info(code, desc_fail_not_exist, info); } else if (code == BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED || code == BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED) { - m_plater->update_print_error_info(code, desc_upload_ftp_failed.ToStdString(), info); + m_plater->update_print_error_info(code, desc_upload_ftp_failed, info); } else { - m_plater->update_print_error_info(code, desc_network_error.ToStdString(), info); + m_plater->update_print_error_info(code, desc_network_error, info); } } else { - this->update_status(curr_percent, msg); + ctl.update_status(curr_percent, msg); } }; - auto cancel_fn = [this]() { - return was_canceled(); - }; + auto cancel_fn = [&ctl]() { + return ctl.was_canceled(); + }; if (params.connection_type != "lan") { @@ -317,7 +311,7 @@ void SendJob::process() && this->has_sdcard) { // try to send local with record BOOST_LOG_TRIVIAL(info) << "send_job: try to send gcode to printer"; - this->update_status(curr_percent, _L("Sending gcode file over LAN")); + ctl.update_status(curr_percent, _u8L("Sending gcode file over LAN")); result = m_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr); if (result == BAMBU_NETWORK_ERR_FTP_UPLOAD_FAILED) { params.comments = "upload_failed"; @@ -327,24 +321,24 @@ void SendJob::process() if (result < 0) { // try to send with cloud BOOST_LOG_TRIVIAL(info) << "send_job: try to send gcode file to printer"; - this->update_status(curr_percent, _L("Sending gcode file over LAN")); + ctl.update_status(curr_percent, _u8L("Sending gcode file over LAN")); } } else { BOOST_LOG_TRIVIAL(info) << "send_job: try to send gcode file to printer"; - this->update_status(curr_percent, _L("Sending gcode file over LAN")); + ctl.update_status(curr_percent, _u8L("Sending gcode file over LAN")); } } else { if (this->has_sdcard) { - this->update_status(curr_percent, _L("Sending gcode file over LAN")); + ctl.update_status(curr_percent, _u8L("Sending gcode file over LAN")); result = m_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr); } else { - this->update_status(curr_percent, _L("An SD card needs to be inserted before sending to printer.")); + ctl.update_status(curr_percent, _u8L("An SD card needs to be inserted before sending to printer.")); return; } } - if (was_canceled()) { - update_status(curr_percent, printjob_cancel_str); + if (ctl.was_canceled()) { + ctl.update_status(curr_percent, printjob_cancel_str); return; } @@ -374,7 +368,7 @@ void SendJob::process() } if (result != BAMBU_NETWORK_ERR_CANCELED) { - this->show_error_info(msg_text, 0, "", ""); + ctl.show_error_info(msg_text, 0, "", ""); } BOOST_LOG_TRIVIAL(error) << "send_job: failed, result = " << result; @@ -404,10 +398,16 @@ void SendJob::on_check_ip_address_success(std::function func) } -void SendJob::finalize() { - if (was_canceled()) return; +void SendJob::finalize(bool canceled, std::exception_ptr& eptr) { + try { + if (eptr) + std::rethrow_exception(eptr); + eptr = nullptr; + } catch (...) { + eptr = std::current_exception(); + } - Job::finalize(); + if (canceled || eptr) return; } void SendJob::set_project_name(std::string name) diff --git a/src/slic3r/GUI/Jobs/SendJob.hpp b/src/slic3r/GUI/Jobs/SendJob.hpp index 179d4f9476..e1862c0dc8 100644 --- a/src/slic3r/GUI/Jobs/SendJob.hpp +++ b/src/slic3r/GUI/Jobs/SendJob.hpp @@ -3,7 +3,7 @@ #include #include -#include "PlaterJob.hpp" +#include "Job.hpp" #include "PrintJob.hpp" namespace fs = boost::filesystem; @@ -11,10 +11,12 @@ namespace fs = boost::filesystem; namespace Slic3r { namespace GUI { +class Plater; + typedef std::function OnUpdateStatusFn; typedef std::function WasCancelledFn; -class SendJob : public PlaterJob +class SendJob : public Job { PrintPrepareData job_data; std::string m_dev_id; @@ -25,14 +27,13 @@ class SendJob : public PlaterJob std::function m_success_fun{nullptr}; std::function m_enter_ip_address_fun_fail{nullptr}; std::function m_enter_ip_address_fun_success{nullptr}; + Plater* m_plater; protected: + void prepare(); - void prepare() override; - - void on_exception(const std::exception_ptr &) override; public: - SendJob(std::shared_ptr pri, Plater *plater, std::string dev_id = ""); + explicit SendJob(std::string dev_id = ""); std::string m_project_name; std::string m_dev_ip; @@ -49,7 +50,7 @@ class SendJob : public PlaterJob wxWindow* m_parent{nullptr}; - int status_range() const override + int status_range() const { return 100; } @@ -58,11 +59,11 @@ class SendJob : public PlaterJob void set_check_mode() {m_is_check_mode = true;}; void check_and_continue() {m_check_and_continue = true;}; bool is_finished() { return m_job_finished; } - void process() override; + void process(Ctl& ctl) override; void on_success(std::function success); void on_check_ip_address_fail(std::function func); void on_check_ip_address_success(std::function func); - void finalize() override; + void finalize(bool canceled, std::exception_ptr& eptr) override; void set_project_name(std::string name); }; diff --git a/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp new file mode 100644 index 0000000000..db47b3be6b --- /dev/null +++ b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp @@ -0,0 +1,128 @@ +#ifndef THREADSAFEQUEUE_HPP +#define THREADSAFEQUEUE_HPP + +#include +#include +#include +#include +#include + +namespace Slic3r { namespace GUI { + +// Helper structure for overloads of ThreadSafeQueueSPSC::consume_one() +// to block if the queue is empty. +struct BlockingWait +{ + // Timeout to wait for the arrival of new element into the queue. + unsigned timeout_ms = 0; +}; + +// A thread safe queue for one producer and one consumer. +template class Container = std::deque, + class... ContainerArgs> +class ThreadSafeQueueSPSC +{ + std::queue> m_queue; + mutable std::mutex m_mutex; + std::condition_variable m_cond_var; + +public: + + // Forward arguments to the underlying queue + template + ThreadSafeQueueSPSC(Qargs &&...qargs) + : m_queue{Container{std::forward(qargs)...}} {} + + ThreadSafeQueueSPSC(const ThreadSafeQueueSPSC&) = delete; + ThreadSafeQueueSPSC(ThreadSafeQueueSPSC&&) = delete; + ThreadSafeQueueSPSC& operator=(const ThreadSafeQueueSPSC&) = delete; + ThreadSafeQueueSPSC& operator=(ThreadSafeQueueSPSC &&) = delete; + + // Consume one element, block if the queue is empty. + template + bool consume_one(const BlockingWait &blkw, Fn &&fn) + { + static_assert(!std::is_reference_v, ""); + static_assert(std::is_default_constructible_v, ""); + static_assert(std::is_move_assignable_v || std::is_copy_assignable_v, ""); + + T el; + { + std::unique_lock lk{m_mutex}; + + auto pred = [this]{ return !m_queue.empty(); }; + if (blkw.timeout_ms > 0) { + auto timeout = std::chrono::milliseconds(blkw.timeout_ms); + if (!m_cond_var.wait_for(lk, timeout, pred)) + return false; + } + else + m_cond_var.wait(lk, pred); + + if constexpr (std::is_move_assignable_v) + el = std::move(m_queue.front()); + else + el = m_queue.front(); + + m_queue.pop(); + } + + fn(el); + + return true; + } + + // Consume one element, return true if consumed, false if queue was empty. + template bool consume_one(Fn &&fn) + { + T el; + { + std::unique_lock lk{m_mutex}; + if (!m_queue.empty()) { + if constexpr (std::is_move_assignable_v) + el = std::move(m_queue.front()); + else + el = m_queue.front(); + + m_queue.pop(); + } else + return false; + } + + fn(el); + + return true; + } + + // Push element into the queue. + template + void push(TArgs&&...el) + { + std::lock_guard lk{m_mutex}; + m_queue.emplace(std::forward(el)...); + m_cond_var.notify_one(); + } + + bool empty() const + { + std::lock_guard lk{m_mutex}; + return m_queue.empty(); + } + + size_t size() const + { + std::lock_guard lk{m_mutex}; + return m_queue.size(); + } + + void clear() + { + std::lock_guard lk{m_mutex}; + while (!m_queue.empty()) m_queue.pop(); + } +}; + +}} // namespace Slic3r::GUI + +#endif // THREADSAFEQUEUE_HPP diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp new file mode 100644 index 0000000000..58616eca30 --- /dev/null +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -0,0 +1,134 @@ +#ifndef UITHREADWORKER_HPP +#define UITHREADWORKER_HPP + +#include +#include + +#include "Worker.hpp" +#include "ProgressIndicator.hpp" + +namespace Slic3r { namespace GUI { + +// Implementation of a worker which does not create any additional threads. +class UIThreadWorker : public Worker, private Job::Ctl { + std::queue, std::deque>> m_jobqueue; + std::shared_ptr m_progress; + bool m_running = false; + bool m_canceled = false; + + void process_front() + { + std::unique_ptr job; + + if (!m_jobqueue.empty()) { + job = std::move(m_jobqueue.front()); + m_jobqueue.pop(); + } + + if (job) { + std::exception_ptr eptr; + m_running = true; + + try { + job->process(*this); + } catch (...) { + eptr= std::current_exception(); + } + + m_running = false; + + job->finalize(m_canceled, eptr); + + m_canceled = false; + } + } + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override + { + if (m_progress) { + m_progress->set_progress(st); + m_progress->set_status_text(msg.c_str()); + } + } + + bool was_canceled() const override { return m_canceled; } + + std::future call_on_main_thread(std::function fn) override + { + std::future ftr = std::async(std::launch::deferred, [fn]{ fn(); }); + + // So, it seems that the destructor of std::future will not call the + // packaged function. The future needs to be accessed at least ones + // or waited upon. Calling wait() instead of get() will keep the + // returned future's state valid. + ftr.wait(); + + return ftr; + } + +public: + explicit UIThreadWorker(std::shared_ptr pri, + const std::string & /*name*/ = "") + : m_progress{pri} + {} + + UIThreadWorker() = default; + + bool push(std::unique_ptr job) override + { + m_canceled = false; + m_jobqueue.push(std::move(job)); + + return bool(job); + } + + bool is_idle() const override { return !m_running; } + + void cancel() override { m_canceled = true; } + + void cancel_all() override + { + m_canceled = true; + process_front(); + while (!m_jobqueue.empty()) m_jobqueue.pop(); + } + + void process_events() override { + while (!m_jobqueue.empty()) + process_front(); + } + + bool wait_for_current_job(unsigned /*timeout_ms*/ = 0) override { + process_front(); + + return true; + } + + bool wait_for_idle(unsigned /*timeout_ms*/ = 0) override { + process_events(); + + return true; + } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } + + void clear_percent() override + { + if (m_progress) + m_progress->clear_percent(); + } + + void show_error_info(wxString msg, int code, wxString description, wxString extra) override + { + if (m_progress) + m_progress->show_error_info(msg, code, description, extra); + } +}; + +}} // namespace Slic3r::GUI + +#endif // UITHREADWORKER_HPP diff --git a/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp b/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp index 000edf8542..1794631c7d 100644 --- a/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp +++ b/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp @@ -13,39 +13,28 @@ wxDEFINE_EVENT(EVT_DOWNLOAD_NETWORK_FAILED, wxCommandEvent); wxDEFINE_EVENT(EVT_INSTALL_NETWORK_FAILED, wxCommandEvent); -UpgradeNetworkJob::UpgradeNetworkJob(std::shared_ptr pri) - : Job{std::move(pri)} +UpgradeNetworkJob::UpgradeNetworkJob() { name = "plugins"; package_name = "networking_plugins.zip"; } -void UpgradeNetworkJob::on_exception(const std::exception_ptr &eptr) -{ - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (std::exception &e) { - UpgradeNetworkJob::on_exception(eptr); - } -} - void UpgradeNetworkJob::on_success(std::function success) { m_success_fun = success; } -void UpgradeNetworkJob::update_status(int st, const wxString &msg) +void UpgradeNetworkJob::update_status(Ctl& ctl, int st, const std::string &msg) { BOOST_LOG_TRIVIAL(info) << "UpgradeNetworkJob: percent = " << st << "msg = " << msg; - GUI::Job::update_status(st, msg); + ctl.update_status(st, msg); wxCommandEvent event(EVT_UPGRADE_UPDATE_MESSAGE); event.SetString(msg); event.SetEventObject(m_event_handle); wxPostEvent(m_event_handle, event); } -void UpgradeNetworkJob::process() +void UpgradeNetworkJob::process(Ctl& ctl) { // downloading int result = 0; @@ -64,24 +53,24 @@ void UpgradeNetworkJob::process() BOOST_LOG_TRIVIAL(info) << "UpgradeNetworkJob: save netowrk_plugin to " << tmp_path.string(); - auto cancel_fn = [this]() { - return was_canceled(); + auto cancel_fn = [&ctl]() { + return ctl.was_canceled(); }; int curr_percent = 0; result = wxGetApp().download_plugin(name, package_name, - [this, &curr_percent](int state, int percent, bool &cancel) { + [this, &curr_percent, &ctl](int state, int percent, bool &cancel) { if (state == InstallStatusNormal) { - update_status(percent, _L("Downloading")); + update_status(ctl, percent, _u8L("Downloading")); } else if (state == InstallStatusDownloadFailed) { - update_status(percent, _L("Download failed")); + update_status(ctl, percent, _u8L("Download failed")); } else { - update_status(percent, _L("Downloading")); + update_status(ctl, percent, _u8L("Downloading")); } curr_percent = percent; }, cancel_fn); - if (was_canceled()) { - update_status(0, _L("Cancelled")); + if (ctl.was_canceled()) { + update_status(ctl, 0, _u8L("Cancelled")); wxCommandEvent event(wxEVT_CLOSE_WINDOW); event.SetEventObject(m_event_handle); wxPostEvent(m_event_handle, event); @@ -89,7 +78,7 @@ void UpgradeNetworkJob::process() } if (result < 0) { - update_status(0, _L("Download failed")); + update_status(ctl, 0, _u8L("Download failed")); wxCommandEvent event(EVT_DOWNLOAD_NETWORK_FAILED); event.SetEventObject(m_event_handle); wxPostEvent(m_event_handle, event); @@ -98,16 +87,16 @@ void UpgradeNetworkJob::process() result = wxGetApp().install_plugin( name, package_name, - [this](int state, int percent, bool &cancel) { + [this, &ctl](int state, int percent, bool &cancel) { if (state == InstallStatusInstallCompleted) { - update_status(percent, _L("Install successfully.")); + update_status(ctl, percent, _u8L("Install successfully.")); } else { - update_status(percent, _L("Installing")); + update_status(ctl, percent, _u8L("Installing")); } }, cancel_fn); - if (was_canceled()) { - update_status(0, _L("Cancelled")); + if (ctl.was_canceled()) { + update_status(ctl, 0, _u8L("Cancelled")); wxCommandEvent event(wxEVT_CLOSE_WINDOW); event.SetEventObject(m_event_handle); wxPostEvent(m_event_handle, event); @@ -115,7 +104,7 @@ void UpgradeNetworkJob::process() } if (result != 0) { - update_status(0, _L("Install failed")); + update_status(ctl, 0, _u8L("Install failed")); wxCommandEvent event(EVT_INSTALL_NETWORK_FAILED); event.SetEventObject(m_event_handle); wxPostEvent(m_event_handle, event); @@ -126,14 +115,19 @@ void UpgradeNetworkJob::process() event.SetEventObject(m_event_handle); wxPostEvent(m_event_handle, event); BOOST_LOG_TRIVIAL(info) << "[UpgradeNetworkJob process]: exit"; - return; } -void UpgradeNetworkJob::finalize() +void UpgradeNetworkJob::finalize(bool canceled, std::exception_ptr& eptr) { - if (was_canceled()) return; + try { + if (eptr) + std::rethrow_exception(eptr); + eptr = nullptr; + } catch (...) { + eptr = std::current_exception(); + } - Job::finalize(); + if (canceled || eptr) return; } void UpgradeNetworkJob::set_event_handle(wxWindow *hanle) diff --git a/src/slic3r/GUI/Jobs/UpgradeNetworkJob.hpp b/src/slic3r/GUI/Jobs/UpgradeNetworkJob.hpp index 18cbbda31b..826fad57fb 100644 --- a/src/slic3r/GUI/Jobs/UpgradeNetworkJob.hpp +++ b/src/slic3r/GUI/Jobs/UpgradeNetworkJob.hpp @@ -34,11 +34,10 @@ class UpgradeNetworkJob : public Job std::string name; std::string package_name; - void on_exception(const std::exception_ptr &) override; public: - UpgradeNetworkJob(std::shared_ptr pri); + UpgradeNetworkJob(); - int status_range() const override + int status_range() const { return 100; } @@ -46,9 +45,9 @@ class UpgradeNetworkJob : public Job bool is_finished() { return m_job_finished; } void on_success(std::function success); - void update_status(int st, const wxString &msg); - void process() override; - void finalize() override; + void update_status(Ctl& ctl, int st, const std::string &msg); + void process(Ctl& ctl) override; + void finalize(bool canceled, std::exception_ptr& eptr) override; void set_event_handle(wxWindow* hanle); }; diff --git a/src/slic3r/GUI/Jobs/WindowWorker.hpp b/src/slic3r/GUI/Jobs/WindowWorker.hpp new file mode 100644 index 0000000000..2f3ca89f79 --- /dev/null +++ b/src/slic3r/GUI/Jobs/WindowWorker.hpp @@ -0,0 +1,143 @@ +#ifndef WINDOWWORKER_HPP +#define WINDOWWORKER_HPP + +#include +#include + +#include "Worker.hpp" +#include "BusyCursorJob.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" + +namespace Slic3r { namespace GUI { + +template +class WindowWorker: public Worker { + WorkerSubclass m_w; + wxWindow *m_window; + + class WindowJob : public Job { + std::unique_ptr m_job; + wxWindow *m_window; + long long m_process_duration; // [ms] + + public: + void process(Ctl &c) override + { + // Ensure that wxWidgets processing wakes up to handle outgoing + // messages in window's wxIdle handler. Otherwise it might happen + // that the message will only be processed when an event like mouse + // move comes along which might be too late. + struct WakeUpCtl: Ctl { + Ctl &ctl; + WakeUpCtl(Ctl &c) : ctl{c} {} + + void update_status(int st, const std::string &msg = "") override + { + ctl.update_status(st, msg); + wxWakeUpIdle(); + } + + void clear_percent() override + { + ctl.clear_percent(); + wxWakeUpIdle(); + } + + void show_error_info(wxString msg, int code, wxString description, wxString extra) override + { + ctl.show_error_info(msg, code, description, extra); + wxWakeUpIdle(); + } + + bool was_canceled() const override { return ctl.was_canceled(); } + + std::future call_on_main_thread(std::function fn) override + { + auto ftr = ctl.call_on_main_thread(std::move(fn)); + wxWakeUpIdle(); + + return ftr; + } + + } wctl{c}; + + CursorSetterRAII busycursor{wctl}; + + using namespace std::chrono; + steady_clock::time_point process_start = steady_clock::now(); + m_job->process(wctl); + steady_clock::time_point process_end = steady_clock::now(); + m_process_duration = duration_cast(process_end - process_start).count(); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + using namespace std::chrono; + steady_clock::time_point finalize_start = steady_clock::now(); + m_job->finalize(canceled, eptr); + steady_clock::time_point finalize_end = steady_clock::now(); + long long finalize_duration = duration_cast(finalize_end - finalize_start).count(); + + BOOST_LOG_TRIVIAL(info) + << std::fixed // do not use scientific notations + << "Job '" << typeid(*m_job).name() << "' " + << "spend " << m_process_duration + finalize_duration << "ms " + << "(process " << m_process_duration << "ms + finalize " << finalize_duration << "ms)"; + + if (eptr) try { + std::rethrow_exception(eptr); + } catch (std::exception &e) { + show_error(m_window, _L("An unexpected error occured: ") + e.what()); + eptr = nullptr; + } + } + + WindowJob(wxWindow *w, std::unique_ptr j) + : m_job{std::move(j)}, m_window{w} + {} + + ~WindowJob() override + {} + }; + +public: + + template + WindowWorker(wxWindow *window, WorkerArgs &&...args) + : m_w{std::forward(args)...}, m_window{window} + { + // Ensure that messages from the worker thread to the UI thread are + // processed continuously. + window->Bind(wxEVT_IDLE, [this](wxIdleEvent &) { + process_events(); + }); + } + + // Always package the job argument into a WindowJob + bool push(std::unique_ptr job) override + { + return m_w.push(std::make_unique(m_window, std::move(job))); + } + + bool is_idle() const override { return m_w.is_idle(); } + void cancel() override { m_w.cancel(); } + void cancel_all() override { m_w.cancel_all(); } + void process_events() override { m_w.process_events(); } + bool wait_for_current_job(unsigned timeout_ms = 0) override + { + return m_w.wait_for_current_job(timeout_ms); + } + bool wait_for_idle(unsigned timeout_ms = 0) override + { + return m_w.wait_for_idle(timeout_ms); + } +}; + +}} // namespace Slic3r::GUI + +#endif // WINDOWWORKER_HPP \ No newline at end of file diff --git a/src/slic3r/GUI/Jobs/Worker.hpp b/src/slic3r/GUI/Jobs/Worker.hpp new file mode 100644 index 0000000000..605bda216e --- /dev/null +++ b/src/slic3r/GUI/Jobs/Worker.hpp @@ -0,0 +1,118 @@ +#ifndef PRUSALSICER_WORKER_HPP +#define PRUSALSICER_WORKER_HPP + +#include + +#include "Job.hpp" + +namespace Slic3r { namespace GUI { + +// An interface of a worker that runs jobs on a dedicated worker thread, one +// after the other. It is assumed that every method of this class is called +// from the same main thread. +class Worker { +public: + // Queue up a new job after the current one. This call does not block. + // Returns false if the job gets discarded. + virtual bool push(std::unique_ptr job) = 0; + + // Returns true if no job is running, the job queue is empty and no job + // message is left to be processed. This means that nothing is left to + // finalize or take care of in the main thread. + virtual bool is_idle() const = 0; + + // Ask the current job gracefully to cancel. This call is not blocking and + // the job may or may not cancel eventually, depending on its + // implementation. Note that it is not trivial to kill a thread forcefully + // and we don't need that. + virtual void cancel() = 0; + + // This method will delete the queued jobs and cancel the current one. + virtual void cancel_all() = 0; + + // Needs to be called continuously to process events (like status update + // or finalizing of jobs) in the main thread. This can be done e.g. in a + // wxIdle handler. + virtual void process_events() = 0; + + // Wait until the current job finishes. Timeout will only be considered + // if not zero. Returns false if timeout is reached but the job has not + // finished. + virtual bool wait_for_current_job(unsigned timeout_ms = 0) = 0; + + // Wait until the whole job queue finishes. Timeout will only be considered + // if not zero. Returns false only if timeout is reached but the worker has + // not reached the idle state. + virtual bool wait_for_idle(unsigned timeout_ms = 0) = 0; + + // The destructor shall properly close the worker thread. + virtual ~Worker() = default; +}; + +template constexpr bool IsProcessFn = std::is_invocable_v; +template constexpr bool IsFinishFn = std::is_invocable_v; + +// Helper function to use the worker with arbitrary functors. +template>, + class = std::enable_if_t> > +bool queue_job(Worker &w, ProcessFn fn, FinishFn finishfn) +{ + struct LambdaJob: Job { + ProcessFn fn; + FinishFn finishfn; + + LambdaJob(ProcessFn pfn, FinishFn ffn) + : fn{std::move(pfn)}, finishfn{std::move(ffn)} + {} + + void process(Ctl &ctl) override { fn(ctl); } + void finalize(bool canceled, std::exception_ptr &eptr) override + { + finishfn(canceled, eptr); + } + }; + + auto j = std::make_unique(std::move(fn), std::move(finishfn)); + return w.push(std::move(j)); +} + +template>> +bool queue_job(Worker &w, ProcessFn fn) +{ + return queue_job(w, std::move(fn), [](bool, std::exception_ptr &) {}); +} + +inline bool queue_job(Worker &w, std::unique_ptr j) +{ + return w.push(std::move(j)); +} + +// Replace the current job queue with a new job. This cancels all jobs and +// will not wait. The new job will begin after the queue cancels properly. +// Note that this can be called from the UI thread and will not block it if the +// jobs take longer to cancel. +template bool replace_job(Worker &w, Args&& ...args) +{ + w.cancel_all(); + return queue_job(w, std::forward(args)...); +} + +// Cancel the current job and wait for it to actually be stopped. +inline void stop_current_job(Worker &w, unsigned timeout_ms = 0) +{ + w.cancel(); + w.wait_for_current_job(timeout_ms); +} + +// Cancel all pending jobs including current one and wait until the worker +// becomes idle. +inline void stop_queue(Worker &w, unsigned timeout_ms = 0) +{ + w.cancel_all(); + w.wait_for_idle(timeout_ms); +} + +}} // namespace Slic3r::GUI + +#endif // WORKER_HPP diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 752bf61345..3da270704c 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -181,7 +181,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ set_miniaturizable(GetHandle()); #endif - if (!wxGetApp().app_config->has("user_mode")) { + if (!wxGetApp().app_config->has("user_mode")) { wxGetApp().app_config->set("user_mode", "simple"); wxGetApp().app_config->set_bool("developer_mode", false); wxGetApp().app_config->save(); @@ -574,7 +574,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); event.Skip(); }); -#endif +#endif update_ui_from_settings(); // FIXME (?) @@ -637,12 +637,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ return; } else if (evt.CmdDown() && evt.GetKeyCode() == 'G') { if (can_export_gcode()) { wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_SLICED_FILE)); } evt.Skip(); return; } - if (evt.CmdDown() && evt.GetKeyCode() == 'J') { m_printhost_queue_dlg->Show(); return; } + if (evt.CmdDown() && evt.GetKeyCode() == 'J') { m_printhost_queue_dlg->Show(); return; } if (evt.CmdDown() && evt.GetKeyCode() == 'N') { m_plater->new_project(); return;} if (evt.CmdDown() && evt.GetKeyCode() == 'O') { m_plater->load_project(); return;} if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (can_save_as()) m_plater->save_project(true); return;} else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;} - if (evt.CmdDown() && evt.GetKeyCode() == 'F') { + if (evt.CmdDown() && evt.GetKeyCode() == 'F') { if (m_plater && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview)) { m_plater->sidebar().can_search(); } @@ -880,7 +880,7 @@ void MainFrame::shutdown() #endif // _WIN32 if (m_plater != nullptr) { - m_plater->stop_jobs(); + m_plater->get_ui_job_worker().cancel_all(); // Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC, // when closing the application using Command+Q, a mouse event is triggered after this lambda is completed, @@ -1682,7 +1682,7 @@ wxBoxSizer* MainFrame::create_side_tools() SidePopup* p = new SidePopup(this); if (wxGetApp().preset_bundle - && !wxGetApp().preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(wxGetApp().preset_bundle)) { + && !wxGetApp().preset_bundle->is_bbl_vendor()) { // ThirdParty Buttons SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), ""); export_gcode_btn->SetCornerRadius(0); @@ -1795,10 +1795,32 @@ wxBoxSizer* MainFrame::create_side_tools() p->Dismiss(); }); + bool support_send = true; + bool support_print_all = true; + + const auto preset_bundle = wxGetApp().preset_bundle; + if (preset_bundle) { + if (preset_bundle->use_bbl_network()) { + // BBL network support everything + } else { + support_send = false; // All 3rd print hosts do not have the send options + + auto cfg = preset_bundle->printers.get_edited_preset().config; + const auto host_type = cfg.option>("host_type")->value; + + // Only simply print support uploading all plates + support_print_all = host_type == PrintHostType::htSimplyPrint; + } + } + p->append_button(print_plate_btn); - p->append_button(print_all_btn); - p->append_button(send_to_printer_btn); - p->append_button(send_to_printer_all_btn); + if (support_print_all) { + p->append_button(print_all_btn); + } + if (support_send) { + p->append_button(send_to_printer_btn); + p->append_button(send_to_printer_all_btn); + } p->append_button(export_sliced_file_btn); p->append_button(export_all_sliced_file_btn); if (enable_multi_machine) { @@ -1981,7 +2003,7 @@ void MainFrame::update_side_button_style() m_slice_btn->SetExtraSize(wxSize(FromDIP(38), FromDIP(10))); m_slice_btn->SetBottomColour(wxColour(0x3B4446));*/ StateColor m_btn_bg_enable = StateColor( - std::pair(wxColour(27, 136, 68), StateColor::Pressed), + std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(48, 221, 112), StateColor::Hovered), std::pair(wxColour(0, 174, 66), StateColor::Normal) ); @@ -2364,7 +2386,7 @@ void MainFrame::init_menubar_as_editor() m_plater->add_file(); } }, "menu_import", nullptr, [this](){return can_add_models(); }, this); -#else + #else append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/SVG/OBJ/AMF") + dots + "\t" + ctrl + "I", _L("Load a model"), [this](wxCommandEvent&) { if (m_plater) { m_plater->add_model(); } }, "", nullptr, [this](){return can_add_models(); }, this); @@ -2375,7 +2397,6 @@ void MainFrame::init_menubar_as_editor() append_submenu(fileMenu, import_menu, wxID_ANY, _L("Import"), ""); - wxMenu* export_menu = new wxMenu(); // BBS export as STL append_menu_item(export_menu, wxID_ANY, _L("Export all objects as one STL") + dots, _L("Export all objects as one STL"), @@ -2540,7 +2561,7 @@ void MainFrame::init_menubar_as_editor() #if 0 // BBS Delete selected append_menu_item(editMenu, wxID_ANY, _L("Delete selected") + "\tBackSpace", - _L("Deletes the current selection"),[this](wxCommandEvent&) { + _L("Deletes the current selection"),[this](wxCommandEvent&) { m_plater->remove_selected(); }, "", nullptr, [this](){return can_delete(); }, this); @@ -2576,7 +2597,7 @@ void MainFrame::init_menubar_as_editor() // BBS Select All append_menu_item(editMenu, wxID_ANY, _L("Select all") + "\t" + ctrl + "A", - _L("Selects all objects"), [this, handle_key_event](wxCommandEvent&) { + _L("Selects all objects"), [this, handle_key_event](wxCommandEvent&) { wxKeyEvent e; e.SetEventType(wxEVT_KEY_DOWN); e.SetControlDown(true); @@ -2684,7 +2705,7 @@ void MainFrame::init_menubar_as_editor() wxWindowID bambu_studio_id_base = wxWindow::NewControlId(int(2)); wxMenu* parent_menu = m_menubar->OSXGetAppleMenu(); //auto preference_item = new wxMenuItem(parent_menu, BambuStudioMenuPreferences + bambu_studio_id_base, _L("Preferences") + "\tCtrl+,", ""); - + std::string app_items[] = { L("Services"), L("Hide BambuStudio"), @@ -2855,7 +2876,7 @@ void MainFrame::init_menubar_as_editor() append_menu_item(flowrate_menu, wxID_ANY, _L("Pass 2"), _L("Flow rate test - Pass 2"), [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(2); }, "", nullptr, [this]() {return m_plater->is_view3D_shown();; }, this); - m_topbar->GetCalibMenu()->AppendSubMenu(flowrate_menu, _L("Flow rate")); + m_topbar->GetCalibMenu()->AppendSubMenu(flowrate_menu, _L("Flow rate") ); append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Pressure advance"), _L("Pressure advance"), [this](wxCommandEvent&) { //if (!m_pa_calib_dlg) @@ -2902,7 +2923,7 @@ void MainFrame::init_menubar_as_editor() m_topbar->GetCalibMenu()->AppendSubMenu(advance_menu, _L("More...")); } - // help + // help append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Tutorial"), _L("Calibration help"), [this](wxCommandEvent&) { try { @@ -3048,7 +3069,7 @@ void MainFrame::init_menubar_as_editor() ; }, this); - + m_menubar->Append(new wxMenu(), L("Window")); std::string window_items[] = { L("Minimize"), @@ -3221,7 +3242,7 @@ void MainFrame::export_config() { ExportConfigsDialog export_configs_dlg(nullptr); export_configs_dlg.ShowModal(); - return; + return; // Generate a cummulative configuration for the selected print, filaments and printer. wxDirDialog dlg(this, _L("Choose a directory"), @@ -3739,9 +3760,9 @@ void MainFrame::load_printer_url(wxString url) void MainFrame::load_printer_url() { PresetBundle &preset_bundle = *wxGetApp().preset_bundle; - if (preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle)) + if (preset_bundle.use_bbl_device_tab()) return; - + auto cfg = preset_bundle.printers.get_edited_preset().config; wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui"); @@ -3768,9 +3789,9 @@ void MainFrame::RunScript(wxString js) m_webview->RunScript(js); } -void MainFrame::RunScriptLeft(wxString js) +void MainFrame::RunScriptLeft(wxString js) { - if (m_webview != nullptr) + if (m_webview != nullptr) m_webview->RunScriptLeft(js); } diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 00d8082c5c..ee585aa3e9 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -530,14 +530,17 @@ void MediaPlayCtrl::ToggleStream() DownloadProgressDialog2(MediaPlayCtrl *ctrl) : DownloadProgressDialog(_L("Downloading Virtual Camera Tools")), ctrl(ctrl) {} struct UpgradeNetworkJob2 : UpgradeNetworkJob { - UpgradeNetworkJob2(std::shared_ptr pri) : UpgradeNetworkJob(pri) { + UpgradeNetworkJob2() { name = "cameratools"; package_name = "camera_tools.zip"; } }; - std::shared_ptr make_job(std::shared_ptr pri) override - { return std::make_shared(pri); } - void on_finish() override + std::unique_ptr make_job() override + { + return std::make_unique(); + } + + void on_finish() override { ctrl->CallAfter([ctrl = this->ctrl] { ctrl->ToggleStream(); }); EndModal(wxID_CLOSE); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 73525be585..737003391e 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -147,9 +147,7 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, , m_evt_handler (evt_handler) , m_notification_start (GLCanvas3D::timestamp_now()) { - m_is_dark = wxGetApp().plater()->get_current_canvas3D()->get_dark_mode_status(); - - m_ErrorColor = ImVec4(0.9, 0.36, 0.36, 1); + m_ErrorColor = ImVec4(0.9, 0.36, 0.36, 1); m_WarnColor = ImVec4(0.99, 0.69, 0.455, 1); m_NormalColor = ImVec4(0.03, 0.6, 0.18, 1); @@ -158,17 +156,31 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, m_WindowBkgColor = ImVec4(1, 1, 1, 1); m_TextColor = ImVec4(.2f, .2f, .2f, 1.0f); m_HyperTextColor = ImVec4(0.03, 0.6, 0.18, 1); - - m_WindowRadius = 4.0f * wxGetApp().plater()->get_current_canvas3D()->get_scale(); } -void NotificationManager::PopNotification::on_change_color_mode(bool is_dark) +// We cannot call plater()->get_current_canvas3D() from constructor, so we do it here +void NotificationManager::PopNotification::ensure_ui_inited() { + if (!m_is_dark_inited) { + m_is_dark = wxGetApp().plater()->get_current_canvas3D()->get_dark_mode_status(); + m_is_dark_inited = true; + } + + if (!m_WindowRadius_inited) { + m_WindowRadius = 4.0f * wxGetApp().plater()->get_current_canvas3D()->get_scale(); + m_WindowRadius_inited = true; + } +} + + void NotificationManager::PopNotification::on_change_color_mode(bool is_dark) { + m_is_dark_inited = true; m_is_dark = is_dark; } void NotificationManager::PopNotification::use_bbl_theme() { + ensure_ui_inited(); + ImGuiStyle &OldStyle = ImGui::GetStyle(); m_DefaultTheme.mWindowPadding = OldStyle.WindowPadding; @@ -736,6 +748,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, void NotificationManager::PopNotification::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) { + ensure_ui_inited(); 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)); @@ -861,6 +874,7 @@ void NotificationManager::PopNotification::bbl_render_block_notif_left_sign(ImGu void NotificationManager::PopNotification::bbl_render_left_sign(ImGuiWrapper &imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { + ensure_ui_inited(); ImDrawList *draw_list = ImGui::GetWindowDrawList(); ImVec2 round_rect_pos = ImVec2(win_pos_x - win_size_x + ImGui::GetStyle().WindowBorderSize, win_pos_y + ImGui::GetStyle().WindowBorderSize); ImVec2 round_rect_size = ImVec2(m_WindowRadius * 2, win_size_y - 2 * ImGui::GetStyle().WindowBorderSize); @@ -892,6 +906,7 @@ void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) } void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) { + ensure_ui_inited(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 3bab8e7416..15eb0d8220 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -463,7 +463,10 @@ class NotificationManager // used this function instead of reading directly m_data.duration. Some notifications might need to return changing value. virtual int get_duration() { return m_data.duration; } + void ensure_ui_inited(); + bool m_is_dark = false; + bool m_is_dark_inited = false; const NotificationData m_data; // For reusing ImGUI windows. @@ -493,6 +496,7 @@ class NotificationManager ImVec4 m_CurrentColor; float m_WindowRadius; + bool m_WindowRadius_inited = false; void use_bbl_theme(); void restore_default_theme(); diff --git a/src/slic3r/GUI/OAuthDialog.cpp b/src/slic3r/GUI/OAuthDialog.cpp new file mode 100644 index 0000000000..473cc5513b --- /dev/null +++ b/src/slic3r/GUI/OAuthDialog.cpp @@ -0,0 +1,84 @@ +#include "OAuthDialog.hpp" + +#include "GUI_App.hpp" +#include "Jobs/BoostThreadWorker.hpp" +#include "Jobs/WindowWorker.hpp" +#include "wxExtensions.hpp" + +namespace Slic3r { +namespace GUI { + + +#define BORDER_W FromDIP(10) + +OAuthDialog::OAuthDialog(wxWindow* parent, OAuthParams params) + : DPIDialog(parent, wxID_ANY, _L("Login"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE) + , _params(params) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(*wxWHITE); + + m_worker = std::make_unique>(this, nullptr, "auth_worker"); + + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxCANCEL); + btnCancel = static_cast(this->FindWindowById(wxID_CANCEL, this)); + wxGetApp().UpdateDarkUI(btnCancel); + btnCancel->Bind(wxEVT_BUTTON, &OAuthDialog::on_cancel, this); + + const auto message_sizer = new wxBoxSizer(wxVERTICAL); + const auto message = new wxStaticText(this, wxID_ANY, _L("Authorizing..."), wxDefaultPosition, wxDefaultSize, 0); + message->SetForegroundColour(*wxBLACK); + message_sizer->Add(message, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, BORDER_W); + + const auto topSizer = new wxBoxSizer(wxVERTICAL); + topSizer->Add(message_sizer, 0, wxEXPAND | wxALL, BORDER_W); + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); + + Bind(wxEVT_CLOSE_WINDOW, &OAuthDialog::on_cancel, this); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); + this->CenterOnParent(); + wxGetApp().UpdateDlgDarkUI(this); +} + +void OAuthDialog::on_cancel(wxEvent& event) +{ + m_worker->cancel_all(); + m_worker->wait_for_idle(); + EndModal(wxID_NO); +} + +bool OAuthDialog::Show(bool show) +{ + if (show) { + // Prepare login job + _result = std::make_shared(); + auto job = std::make_unique(OAuthData{_params, _result}); + job->set_event_handle(this); + Bind(EVT_OAUTH_COMPLETE_MESSAGE, [this](wxCommandEvent& evt) { EndModal(wxID_NO); }); + + // Start auth job + replace_job(*m_worker, std::move(job)); + + // Open login URL in external browser + wxLaunchDefaultBrowser(_params.login_url); + } + + return DPIDialog::Show(show); +} + +void OAuthDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + + msw_buttons_rescale(this, em, {wxID_CANCEL}); + + const wxSize& size = wxSize(45 * em, 35 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + +}} diff --git a/src/slic3r/GUI/OAuthDialog.hpp b/src/slic3r/GUI/OAuthDialog.hpp new file mode 100644 index 0000000000..55164588fb --- /dev/null +++ b/src/slic3r/GUI/OAuthDialog.hpp @@ -0,0 +1,34 @@ +#ifndef __OAuthDialog_HPP__ +#define __OAuthDialog_HPP__ + +#include "GUI_Utils.hpp" +#include "Jobs/OAuthJob.hpp" +#include "Jobs/Worker.hpp" + +namespace Slic3r { +namespace GUI { + +class OAuthDialog : public DPIDialog +{ +private: + OAuthParams _params; + std::shared_ptr _result; + + wxButton* btnCancel{nullptr}; + std::unique_ptr m_worker; + + void on_cancel(wxEvent& event); + +protected: + bool Show(bool show) override; + void on_dpi_changed(const wxRect& suggested_rect) override; + +public: + OAuthDialog(wxWindow* parent, OAuthParams params); + + OAuthResult get_result() { return *_result; } +}; + +}} // namespace Slic3r::GUI + +#endif diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index eb7bd88066..c8ccb0186e 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -185,6 +185,13 @@ void OptionsGroup::show_field(const t_config_option_key& opt_key, bool show/* = } } +void OptionsGroup::enable_field(const t_config_option_key& opt_key, bool enable) +{ + if (Field* f = get_field(opt_key); f) { + f->toggle(enable); + } +} + void OptionsGroup::set_name(const wxString& new_name) { stb->SetLabel(new_name); diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index a758f1652a..ab17df2d00 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -171,6 +171,9 @@ class OptionsGroup { void show_field(const t_config_option_key& opt_key, bool show = true); void hide_field(const t_config_option_key& opt_key) { show_field(opt_key, false); } + void enable_field(const t_config_option_key& opt_key, bool enable = true); + void disable_field(const t_config_option_key& opt_key) { enable_field(opt_key, false); } + void set_name(const wxString& new_name); inline void enable() { for (auto& field : m_fields) field.second->enable(); } @@ -181,7 +184,7 @@ class OptionsGroup { void hide_labels() { label_width = 0; } - OptionsGroup(wxWindow *_parent, const wxString &title, const wxString &icon, bool is_tab_opt = false, + OptionsGroup(wxWindow *_parent, const wxString &title, const wxString &icon, bool is_tab_opt = false, column_t extra_clmn = nullptr); ~OptionsGroup() { clear(true); } @@ -240,10 +243,10 @@ class OptionsGroup { class ConfigOptionsGroup: public OptionsGroup { public: - ConfigOptionsGroup( wxWindow* parent, const wxString& title, const wxString& icon, DynamicPrintConfig* config = nullptr, + ConfigOptionsGroup( wxWindow* parent, const wxString& title, const wxString& icon, DynamicPrintConfig* config = nullptr, bool is_tab_opt = false, column_t extra_clmn = nullptr) : OptionsGroup(parent, title, icon, is_tab_opt, extra_clmn), m_config(config) {} - ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* config = nullptr, + ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* config = nullptr, bool is_tab_opt = false, column_t extra_clmn = nullptr) : ConfigOptionsGroup(parent, title, wxEmptyString, config, is_tab_opt, extra_clmn) {} ConfigOptionsGroup( wxWindow* parent, const wxString& title, ModelConfig* config, @@ -257,7 +260,7 @@ class ConfigOptionsGroup: public OptionsGroup { const t_opt_map& opt_map() const throw() { return m_opt_map; } void set_config_category_and_type(const wxString &category, int type) { m_config_category = category; m_config_type = type; } - void set_config(DynamicPrintConfig* config) { + void set_config(DynamicPrintConfig* config) { m_config = config; m_modelconfig = nullptr; } Option get_option(const std::string& opt_key, int opt_index = -1); Line create_single_option_line(const std::string& title, const std::string& path = std::string(), int idx = -1) /*const*/{ @@ -275,7 +278,7 @@ class ConfigOptionsGroup: public OptionsGroup { Option option = get_option(title, idx); append_single_option_line(option, path); } - + void on_change_OG(const t_config_option_key& opt_id, const boost::any& value) override; void back_to_initial_value(const std::string& opt_key) override; void back_to_sys_value(const std::string& opt_key) override; diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 1ab4672144..3040520c1c 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -32,6 +32,8 @@ #include "BitmapCache.hpp" #include "BonjourDialog.hpp" #include "MsgDialog.hpp" +#include "OAuthDialog.hpp" +#include "SimplyPrint.hpp" namespace Slic3r { namespace GUI { @@ -148,6 +150,8 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr this->update(); if (opt_key == "print_host") this->update_printhost_buttons(); + if (opt_key == "bbl_use_print_host_webui") + this->update_webui(); }; m_optgroup->append_single_option_line("host_type"); @@ -191,16 +195,56 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr // Show a wait cursor during the connection test, as it is blocking UI. wxBusyCursor wait; result = host->test(msg); + + if (!result && host->is_cloud()) { + if (const auto h = dynamic_cast(host.get()); h) { + OAuthDialog dlg(this, h->get_oauth_params()); + dlg.ShowModal(); + + const auto& r = dlg.get_result(); + result = r.success; + if (r.success) { + h->save_oauth_credential(r); + } else { + msg = r.error_message; + } + } + } } if (result) show_info(this, host->get_test_ok_msg(), _L("Success!")); else show_error(this, host->get_test_failed_msg(msg)); + + update(); }); return sizer; }; + auto print_host_logout = [&](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_logout_btn, "", _L("Log Out")); + + m_printhost_logout_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + if (!host) { + const wxString text = _L("Could not get a valid Printer Host reference"); + show_error(this, text); + return; + } + + wxString msg_text = _L("Are you sure to log out?"); + MessageDialog dialog(this, msg_text, "", wxICON_QUESTION | wxYES_NO); + + if (dialog.ShowModal() == wxID_YES) { + host->log_out(); + update(); + } + }); + + return sizer; + }; + auto print_host_printers = [this, create_sizer_with_btn](wxWindow* parent) { //add_scaled_button(parent, &m_printhost_port_browse_btn, "browse", _(L("Refresh Printers")), wxBU_LEFT | wxBU_EXACTFIT); auto sizer = create_sizer_with_btn(parent, &m_printhost_port_browse_btn, "monitor_signal_strong", _(L("Refresh Printers"))); @@ -217,12 +261,26 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr //do not support now host_line.append_widget(printhost_browse); host_line.append_widget(print_host_test); + host_line.append_widget(print_host_logout); m_optgroup->append_line(host_line); option = m_optgroup->get_option("print_host_webui"); option.opt.width = Field::def_width_wider(); m_optgroup->append_single_option_line(option); + { + // For bbl printers, we build a fake option to control whether the original device tab should be used + ConfigOptionDef def; + def.type = coBool; + def.width = Field::def_width(); + def.label = L("View print host webui in Device tab"); + def.tooltip = L("Replace the BambuLab's device tab with print host webui"); + def.set_default_value(new ConfigOptionBool(false)); + + auto option = Option(def, "bbl_use_print_host_webui"); + m_optgroup->append_single_option_line(option); + } + m_optgroup->append_single_option_line("printhost_authorization_type"); option = m_optgroup->get_option("printhost_apikey"); @@ -336,12 +394,38 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr update(); } +void PhysicalPrinterDialog::update_webui() +{ + const PrinterTechnology tech = Preset::printer_technology(*m_config); + if (tech == ptFFF) { + const auto opt = m_config->option>("host_type"); + if (opt->value == htSimplyPrint) { + bool bbl_use_print_host_webui = false; + if (Field* printhost_webui_field = m_optgroup->get_field("bbl_use_print_host_webui"); printhost_webui_field) { + if (CheckBox* temp = dynamic_cast(printhost_webui_field); temp) { + bbl_use_print_host_webui = boost::any_cast(temp->get_value()); + } + } + + const std::string v = bbl_use_print_host_webui ? "https://simplyprint.io/panel" : ""; + if (Field* printhost_webui_field = m_optgroup->get_field("print_host_webui"); printhost_webui_field) { + if (wxTextCtrl* temp = dynamic_cast(printhost_webui_field)->text_ctrl(); temp) { + temp->SetValue(v); + } + } + m_config->opt_string("print_host_webui") = v; + } + } +} + void PhysicalPrinterDialog::update_printhost_buttons() { std::unique_ptr host(PrintHost::get_print_host(m_config)); if (host) { m_printhost_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); - m_printhost_browse_btn->Enable(host->has_auto_discovery()); + m_printhost_browse_btn->Show(host->has_auto_discovery()); + m_printhost_logout_btn->Show(host->is_logged_in()); + m_printhost_test_btn->SetLabel(host->is_cloud() ? _L("Login/Test") : _L("Test")); } } @@ -434,6 +518,25 @@ 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"); + + m_optgroup->enable_field("print_host"); + m_optgroup->show_field("print_host_webui"); + m_optgroup->hide_field("bbl_use_print_host_webui"); + m_optgroup->enable_field("printhost_cafile"); + m_optgroup->enable_field("printhost_ssl_ignore_revoke"); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->Enable(); + + // hide pre-configured address, in case user switched to a different host type + if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { + if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp) { + const auto current_host = temp->GetValue(); + if (current_host == "https://simplyprint.io" || current_host == "https://simplyprint.io/panel") { + temp->SetValue(wxString()); + } + } + } + if (opt->value == htPrusaLink) { m_optgroup->show_field("printhost_authorization_type"); @@ -448,7 +551,45 @@ void PhysicalPrinterDialog::update(bool printer_change) m_optgroup->hide_field(opt_key); supports_multiple_printers = opt && opt->value == htRepetier; } - + + if (opt->value == htSimplyPrint) { + // Set the host url + if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { + printhost_field->disable(); + if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { + temp->SetValue("https://simplyprint.io/panel"); + } + m_config->opt_string("print_host") = "https://simplyprint.io/panel"; + } + + const auto current_webui = m_config->opt_string("print_host_webui"); + if (!current_webui.empty()) { + if (Field* printhost_webui_field = m_optgroup->get_field("print_host_webui"); printhost_webui_field) { + if (wxTextCtrl* temp = dynamic_cast(printhost_webui_field)->text_ctrl(); temp) { + temp->SetValue("https://simplyprint.io/panel"); + } + } + m_config->opt_string("print_host_webui") = "https://simplyprint.io/panel"; + } + + // For bbl printers, show option to control the device tab + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + m_optgroup->show_field("bbl_use_print_host_webui"); + const bool use_print_host_webui = !current_webui.empty(); + if (Field* printhost_webui_field = m_optgroup->get_field("bbl_use_print_host_webui"); printhost_webui_field) { + if (CheckBox* temp = dynamic_cast(printhost_webui_field); temp) { + temp->set_value(use_print_host_webui); + } + } + } + + m_optgroup->hide_field("print_host_webui"); + m_optgroup->hide_field("printhost_apikey"); + m_optgroup->disable_field("printhost_cafile"); + m_optgroup->disable_field("printhost_ssl_ignore_revoke"); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->Disable(); + } } else { m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); @@ -497,7 +638,7 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change) m_config->set_key_value("host_type", new ConfigOptionEnum(type)); }; if ((printer_change && all_presets_are_from_mk3_family) || all_presets_are_from_mk3_family) - set_to_choice_and_config(htPrusaLink); + 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 @@ -530,6 +671,7 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) m_printhost_browse_btn->msw_rescale(); m_printhost_test_btn->msw_rescale(); + m_printhost_logout_btn->msw_rescale(); if (m_printhost_cafile_browse_btn) m_printhost_cafile_browse_btn->msw_rescale(); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index 3e6706de67..9392253129 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -32,6 +32,7 @@ class PhysicalPrinterDialog : public DPIDialog ScalableButton* m_printhost_browse_btn {nullptr}; ScalableButton* m_printhost_test_btn {nullptr}; + ScalableButton* m_printhost_logout_btn {nullptr}; ScalableButton* m_printhost_cafile_browse_btn {nullptr}; ScalableButton* m_printhost_client_cert_browse_btn {nullptr}; ScalableButton* m_printhost_port_browse_btn {nullptr}; @@ -64,6 +65,7 @@ class PhysicalPrinterDialog : public DPIDialog void update_preset_input(); void update_printhost_buttons(); void update_printers(); + void update_webui(); protected: void on_dpi_changed(const wxRect& suggested_rect) override; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fdd3509f82..26897288c8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -86,8 +86,11 @@ #include "Jobs/FillBedJob.hpp" #include "Jobs/RotoptimizeJob.hpp" #include "Jobs/SLAImportJob.hpp" +#include "Jobs/SLAImportDialog.hpp" #include "Jobs/PrintJob.hpp" #include "Jobs/NotificationProgressIndicator.hpp" +#include "Jobs/PlaterWorker.hpp" +#include "Jobs/BoostThreadWorker.hpp" #include "BackgroundSlicingProcess.hpp" #include "SelectMachine.hpp" #include "SendMultiMachinePage.hpp" @@ -1162,11 +1165,13 @@ void Sidebar::update_all_preset_comboboxes() const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); bool is_bbl_preset = preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle); + const bool use_bbl_network = preset_bundle.use_bbl_network(); auto cur_preset_name = preset_bundle.printers.get_edited_preset().name; auto p_mainframe = wxGetApp().mainframe; + auto cfg = preset_bundle.printers.get_edited_preset().config; - p_mainframe->show_device(is_bbl_preset); - if (is_bbl_preset) { + p_mainframe->show_device(use_bbl_network); + if (preset_bundle.use_bbl_network()) { //only show connection button for not-BBL printer connection_btn->Hide(); //only show sync-ams button for BBL printer @@ -1314,7 +1319,7 @@ void Sidebar::update_presets(Preset::Type preset_type) Preset& printer_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - bool isBBL = printer_preset.is_bbl_vendor_preset(wxGetApp().preset_bundle); + bool isBBL = preset_bundle.use_bbl_network(); wxGetApp().mainframe->show_calibration_button(!isBBL); if (auto printer_structure_opt = printer_preset.config.option>("printer_structure")) { @@ -2218,70 +2223,15 @@ struct Plater::priv BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; - // Jobs defined inside the group class will be managed so that only one can - // run at a time. Also, the background process will be stopped if a job is - // started. It is up the the plater to ensure that the background slicing - // can't be restarted while a ui job is still running. - class Jobs: public ExclusiveJobGroup - { - priv *m; - size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id, m_orient_id; - std::shared_ptr m_pri; - //BBS - size_t m_print_id; - - void before_start() override { m->background_process.stop(); } - - public: - Jobs(priv *_m) : - m(_m), - m_pri{std::make_shared(m->notification_manager.get())} - { - m_arrange_id = add_job(std::make_unique(m_pri, m->q)); - m_orient_id = add_job(std::make_unique(m_pri, m->q)); - m_fill_bed_id = add_job(std::make_unique(m_pri, m->q)); - m_rotoptimize_id = add_job(std::make_unique(m_pri, m->q)); - m_sla_import_id = add_job(std::make_unique(m_pri, m->q)); - //BBS add print id - m_print_id = add_job(std::make_unique(m_pri, m->q)); - } - - void arrange() - { - m->take_snapshot("Arrange"); - start(m_arrange_id); - } - - void orient() - { - m->take_snapshot("Orient"); - start(m_orient_id); - } - - void fill_bed() - { - m->take_snapshot("Fill bed"); - start(m_fill_bed_id); - } - - void optimize_rotation() - { - m->take_snapshot("Optimize Rotation"); - start(m_rotoptimize_id); - } - - void import_sla_arch() - { - m->take_snapshot("Import SLA archive"); - start(m_sla_import_id); - } - - //BBS bbl printing job - void print() - { - start(m_print_id); - } - } m_ui_jobs; + // TODO: A mechanism would be useful for blocking the plater interactions: + // objects would be frozen for the user. In case of arrange, an animation + // could be shown, or with the optimize orientations, partial results + // could be displayed. + // + // UIThreadWorker can be used as a replacement for BoostThreadWorker if + // no additional worker threads are desired (useful for debugging or profiling) + PlaterWorker m_worker; + SLAImportDialog * m_sla_import_dlg; int m_job_prepare_state; @@ -2746,7 +2696,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , notification_manager(std::make_unique(q)) - , m_ui_jobs(this) + , m_worker{q, std::make_unique(notification_manager.get()), "ui_worker"} + , m_sla_import_dlg{new SLAImportDialog{q}} , m_job_prepare_state(Job::JobPrepareState::PREPARE_STATE_DEFAULT) , delayed_scene_refresh(false) , collapse_toolbar(GLToolbar::Normal, "Collapse") @@ -3162,7 +3113,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); //wxPostEvent(this->q, wxCommandEvent{EVT_RESTORE_PROJECT}); } - this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent& evt) { BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event."; wxArrayString input_files; @@ -3962,6 +3912,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ std::vector project_presets; bool is_xxx; Semver file_version; + //ObjImportColorFn obj_color_fun=nullptr; auto obj_color_fun = [this, &path](std::vector &input_colors, bool is_single_color, std::vector &filament_ids, unsigned char &first_extruder_id) { @@ -4597,7 +4548,6 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) } std::string out_dir = (boost::filesystem::path(output_file).parent_path()).string(); - wxFileDialog dlg(q, dlg_title, is_shapes_dir(out_dir) ? from_u8(wxGetApp().app_config->get_last_dir()) : from_path(output_file.parent_path()), from_path(output_file.filename()), wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxPD_APP_MODAL); @@ -4730,7 +4680,7 @@ void Plater::priv::remove(size_t obj_idx) if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); - m_ui_jobs.cancel_all(); + m_worker.cancel_all(); model.delete_object(obj_idx); //BBS: notify partplate the instance removed partplate_list.notify_instance_removed(obj_idx, -1); @@ -4761,7 +4711,7 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx, bool refresh_immedia if (!obj->name.empty()) snapshot_label += ": " + obj->name; Plater::TakeSnapshot snapshot(q, snapshot_label); - m_ui_jobs.cancel_all(); + m_worker.cancel_all(); if (obj->is_cut()) sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx); @@ -4791,7 +4741,7 @@ void Plater::priv::delete_all_objects_from_model() view3D->get_canvas3d()->reset_sequential_print_clearance(); - m_ui_jobs.cancel_all(); + m_worker.cancel_all(); // Stop and reset the Print content. background_process.reset(); @@ -4830,7 +4780,7 @@ void Plater::priv::reset(bool apply_presets_change) view3D->get_canvas3d()->reset_sequential_print_clearance(); - m_ui_jobs.cancel_all(); + m_worker.cancel_all(); //BBS: clear the partplate list's object before object cleared partplate_list.reinit(); @@ -5245,7 +5195,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. bool Plater::priv::restart_background_process(unsigned int state) { - if (m_ui_jobs.is_any_running()) { + if (!m_worker.is_idle()) { // Avoid a race condition BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: ui jobs running, return false")%__LINE__; return false; @@ -6472,7 +6422,7 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) std::string title_text = _u8L("Slicing"); evt.status.text = title_text + evt.status.text; if (evt.status.percent >= 0) { - if (m_ui_jobs.is_any_running()) { + if (!m_worker.is_idle()) { // Avoid a race condition return; } @@ -6998,12 +6948,18 @@ void Plater::priv::on_action_print_plate(SimpleEvent&) BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received print plate event\n" ; } - //BBS - if (!m_select_machine_dlg) m_select_machine_dlg = new SelectMachineDialog(q); - m_select_machine_dlg->set_print_type(PrintFromType::FROM_NORMAL); - m_select_machine_dlg->prepare(partplate_list.get_curr_plate_index()); - m_select_machine_dlg->ShowModal(); - record_start_print_preset("print_plate"); + PresetBundle& preset_bundle = *wxGetApp().preset_bundle; + if (preset_bundle.use_bbl_network()) { + // BBS + if (!m_select_machine_dlg) + m_select_machine_dlg = new SelectMachineDialog(q); + m_select_machine_dlg->set_print_type(PrintFromType::FROM_NORMAL); + m_select_machine_dlg->prepare(partplate_list.get_curr_plate_index()); + m_select_machine_dlg->ShowModal(); + record_start_print_preset("print_plate"); + } else { + q->send_gcode_legacy(PLATE_CURRENT_IDX, nullptr, true); + } } void Plater::priv::on_action_send_to_multi_machine(SimpleEvent&) @@ -7061,12 +7017,18 @@ void Plater::priv::on_action_print_all(SimpleEvent&) BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received print all event\n" ; } - //BBS - if (!m_select_machine_dlg) m_select_machine_dlg = new SelectMachineDialog(q); - m_select_machine_dlg->set_print_type(PrintFromType::FROM_NORMAL); - m_select_machine_dlg->prepare(PLATE_ALL_IDX); - m_select_machine_dlg->ShowModal(); - record_start_print_preset("print_all"); + PresetBundle& preset_bundle = *wxGetApp().preset_bundle; + if (preset_bundle.use_bbl_network()) { + // BBS + if (!m_select_machine_dlg) + m_select_machine_dlg = new SelectMachineDialog(q); + m_select_machine_dlg->set_print_type(PrintFromType::FROM_NORMAL); + m_select_machine_dlg->prepare(PLATE_ALL_IDX); + m_select_machine_dlg->ShowModal(); + record_start_print_preset("print_all"); + } else { + q->send_gcode_legacy(PLATE_ALL_IDX, nullptr, true); + } } void Plater::priv::on_action_export_gcode(SimpleEvent&) @@ -7564,7 +7526,11 @@ void Plater::priv::init_notification_manager() void Plater::orient() { - p->m_ui_jobs.orient(); + auto &w = get_ui_job_worker(); + if (w.is_idle()) { + p->take_snapshot(_u8L("Orient")); + replace_job(w, std::make_unique()); + } } //BBS: add job state related functions @@ -7986,7 +7952,7 @@ bool Plater::priv::can_simplify() const bool Plater::priv::can_increase_instances() const { - if (m_ui_jobs.is_any_running() + if (!m_worker.is_idle() || q->get_view3D_canvas3D()->get_gizmos_manager().is_in_editing_mode()) return false; @@ -7998,7 +7964,7 @@ bool Plater::priv::can_increase_instances() const bool Plater::priv::can_decrease_instances() const { - if (m_ui_jobs.is_any_running() + if (!m_worker.is_idle() || q->get_view3D_canvas3D()->get_gizmos_manager().is_in_editing_mode()) return false; @@ -8019,7 +7985,7 @@ bool Plater::priv::can_split_to_volumes() const bool Plater::priv::can_arrange() const { - return !model.objects.empty() && !m_ui_jobs.is_any_running(); + return !model.objects.empty() && m_worker.is_idle(); } bool Plater::priv::layers_height_allowed() const @@ -9752,8 +9718,11 @@ void Plater::calib_VFA(const Calib_Params ¶ms) void Plater::import_sl1_archive() { - if (!p->m_ui_jobs.is_any_running()) - p->m_ui_jobs.import_sla_arch(); + auto &w = get_ui_job_worker(); + if (w.is_idle() && p->m_sla_import_dlg->ShowModal() == wxID_OK) { + p->take_snapshot(_u8L("Import SLA archive")); + replace_job(w, std::make_unique(p->m_sla_import_dlg)); + } } void Plater::extract_config_from_project() @@ -10587,12 +10556,9 @@ void Plater::update(bool conside_update_flag, bool force_background_processing_u void Plater::object_list_changed() { p->object_list_changed(); } -void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); } +Worker &Plater::get_ui_job_worker() { return p->m_worker; } -bool Plater::is_any_job_running() const -{ - return p->m_ui_jobs.is_any_running(); -} +const Worker &Plater::get_ui_job_worker() const { return p->m_worker; } void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } @@ -10702,7 +10668,7 @@ void Plater::set_selected_visible(bool visible) return; Plater::TakeSnapshot snapshot(this, "Set Selected Objects Visible in AssembleView"); - p->m_ui_jobs.cancel_all(); + get_ui_job_worker().cancel_all(); p->get_current_canvas3D()->set_selected_visible(visible); } @@ -10720,7 +10686,7 @@ void Plater::remove_selected() return; Plater::TakeSnapshot snapshot(this, "Delete Selected Objects"); - p->m_ui_jobs.cancel_all(); + get_ui_job_worker().cancel_all(); //BBS delete current selected // p->view3D->delete_selected(); @@ -10840,8 +10806,11 @@ void Plater::set_number_of_copies(/*size_t num*/) void Plater::fill_bed_with_instances() { - if (!p->m_ui_jobs.is_any_running()) - p->m_ui_jobs.fill_bed(); + auto &w = get_ui_job_worker(); + if (w.is_idle()) { + p->take_snapshot(_u8L("Fill bed")); + replace_job(w, std::make_unique()); + } } bool Plater::is_selection_empty() const @@ -11794,7 +11763,7 @@ void Plater::reslice() return; // Stop arrange and (or) optimize rotation tasks. - this->stop_jobs(); + stop_queue(this->get_ui_job_worker()); // softfever: regenerate CalibPressureAdvancePattern custom G-code to apply changes if (model().calib_pa_pattern) { @@ -12036,7 +12005,7 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject & // and let the background processing start. this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART); } -void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn) +void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool use_3mf) { // if physical_printer is selected, send gcode for this printer // DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); @@ -12048,6 +12017,8 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn) if (upload_job.empty()) return; + upload_job.upload_data.use_3mf = use_3mf; + // Obtain default output path fs::path default_output_file; try { @@ -12066,6 +12037,9 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn) return; } default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); + if (use_3mf) { + default_output_file.replace_extension("3mf"); + } // Repetier specific: Query the server for the list of file groups. wxArrayString groups; @@ -12074,12 +12048,28 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn) upload_job.printhost->get_groups(groups); } - PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups); + auto config = get_app_config(); + PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, config->get("open_device_tab_post_upload") == "true"); if (dlg.ShowModal() == wxID_OK) { + config->set_bool("open_device_tab_post_upload", dlg.switch_to_device_tab()); + upload_job.switch_to_device_tab = dlg.switch_to_device_tab(); upload_job.upload_data.upload_path = dlg.filename(); upload_job.upload_data.post_action = dlg.post_action(); upload_job.upload_data.group = dlg.group(); + if (use_3mf) { + // Process gcode + const int result = send_gcode(plate_idx, nullptr); + + if (result < 0) { + wxString msg = _L("Abnormal print file data. Please slice again"); + show_error(this, msg, false); + return; + } + + upload_job.upload_data.source_path = p->m_print_job_data._3mf_path; + } + p->export_gcode(fs::path(), false, std::move(upload_job)); try { @@ -12664,8 +12654,10 @@ GLCanvas3D* Plater::get_current_canvas3D(bool exclude_preview) void Plater::arrange() { - if (!p->m_ui_jobs.is_any_running()) { - p->m_ui_jobs.arrange(); + auto &w = get_ui_job_worker(); + if (w.is_idle()) { + p->take_snapshot(_u8L("Arrange")); + replace_job(w, std::make_unique()); } } @@ -12821,7 +12813,14 @@ void Plater::center_selection() { p->center_selection(); } void Plater::mirror(Axis axis) { p->mirror(axis); } void Plater::split_object() { p->split_object(); } void Plater::split_volume() { p->split_volume(); } -void Plater::optimize_rotation() { if (!p->m_ui_jobs.is_any_running()) p->m_ui_jobs.optimize_rotation(); } +void Plater::optimize_rotation() +{ + auto &w = get_ui_job_worker(); + if (w.is_idle()) { + p->take_snapshot(_u8L("Optimize Rotation")); + replace_job(w, std::make_unique()); + } +} void Plater::update_menus() { p->menus.update(); } // BBS //void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 14ce5befdb..0d044b4a74 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -16,6 +16,7 @@ #include "libslic3r/BoundingBox.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "Jobs/Job.hpp" +#include "Jobs/Worker.hpp" #include "Search.hpp" #include "PartPlate.hpp" #include "GUI_App.hpp" @@ -294,8 +295,40 @@ class Plater: public wxPanel void update(bool conside_update_flag = false, bool force_background_processing_update = false); //BBS void object_list_changed(); - void stop_jobs(); - bool is_any_job_running() const; + + // Get the worker handling the UI jobs (arrange, fill bed, etc...) + // Here is an example of starting up an ad-hoc job: + // queue_job( + // get_ui_job_worker(), + // [](Job::Ctl &ctl) { + // // Executed in the worker thread + // + // CursorSetterRAII cursor_setter{ctl}; + // std::string msg = "Running"; + // + // ctl.update_status(0, msg); + // for (int i = 0; i < 100; i++) { + // usleep(100000); + // if (ctl.was_canceled()) break; + // ctl.update_status(i + 1, msg); + // } + // ctl.update_status(100, msg); + // }, + // [](bool, std::exception_ptr &e) { + // // Executed in UI thread after the work is done + // + // try { + // if (e) std::rethrow_exception(e); + // } catch (std::exception &e) { + // BOOST_LOG_TRIVIAL(error) << e.what(); + // } + // e = nullptr; + // }); + // This would result in quick run of the progress indicator notification + // from 0 to 100. Use replace_job() instead of queue_job() to cancel all + // pending jobs. + Worker& get_ui_job_worker(); + const Worker & get_ui_job_worker() const; void select_view(const std::string& direction); //BBS: add no_slice logic void select_view_3D(const std::string& name, bool no_slice = true); @@ -390,7 +423,7 @@ class Plater: public wxPanel /* -1: send current gcode if not specified * -2: send all gcode to target machine */ int send_gcode(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); - void send_gcode_legacy(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); + void send_gcode_legacy(int plate_idx = -1, Export3mfProgressFn proFn = nullptr, bool use_3mf = false); int export_config_3mf(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); //BBS jump to nonitor after print job finished void send_calibration_job_finished(wxCommandEvent &evt); @@ -618,7 +651,6 @@ class Plater: public wxPanel bool need_update() const; void set_need_update(bool need_update); - // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. class SuppressSnapshots { diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 82693e6bc4..57372efddf 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -36,11 +36,12 @@ namespace GUI { static const char *CONFIG_KEY_PATH = "printhost_path"; static const char *CONFIG_KEY_GROUP = "printhost_group"; -PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups) +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, bool switch_to_device_tab) : MsgDialog(static_cast(wxGetApp().mainframe), _L("Send to print"), _L("Upload to Printer Host with the following filename:"),0) , txt_filename(new wxTextCtrl(this, wxID_ANY)) , combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr) , post_upload_action(PrintHostPostUploadAction::None) + , m_switch_to_device_tab(switch_to_device_tab) { #ifdef __APPLE__ txt_filename->OSXDisableAllSmartSubstitutions(); @@ -76,7 +77,23 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo txt_filename->SetValue(recent_path); txt_filename->SetFocus(); - + + auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL); + auto checkbox = new ::CheckBox(this, wxID_APPLY); + checkbox->SetValue(m_switch_to_device_tab); + checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) { + m_switch_to_device_tab = e.IsChecked(); + e.Skip(); + }); + checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2)); + + auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Switch to Device tab after upload."), wxDefaultPosition, wxDefaultSize, 0); + checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2)); + checkbox_text->SetFont(::Label::Body_13); + checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D"))); + content_sizer->Add(checkbox_sizer); + content_sizer->AddSpacer(VERT_SPACING); + m_valid_suffix = recent_path.substr(recent_path.find_last_of('.')); // .gcode suffix control auto validate_path = [this](const wxString &path) -> bool { @@ -113,7 +130,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo if (validate_path(txt_filename->GetValue())) { post_upload_action = PrintHostPostUploadAction::StartSimulation; EndDialog(wxID_OK); - } + } }); } diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index ff3eb60125..0b6c2fcf8b 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -26,10 +26,11 @@ 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, bool switch_to_device_tab); boost::filesystem::path filename() const; PrintHostPostUploadAction post_action() const; std::string group() const; + bool switch_to_device_tab() const {return m_switch_to_device_tab;} virtual void EndModal(int ret) override; private: @@ -37,6 +38,7 @@ class PrintHostSendDialog : public GUI::MsgDialog wxComboBox *combo_groups; PrintHostPostUploadAction post_upload_action; wxString m_valid_suffix; + bool m_switch_to_device_tab; }; diff --git a/src/slic3r/GUI/ReleaseNote.cpp b/src/slic3r/GUI/ReleaseNote.cpp index af7ec9a47d..7bd7def6d1 100644 --- a/src/slic3r/GUI/ReleaseNote.cpp +++ b/src/slic3r/GUI/ReleaseNote.cpp @@ -12,6 +12,8 @@ #include "Widgets/RoundedRectangle.hpp" #include "Widgets/StaticBox.hpp" #include "Widgets/WebView.hpp" +#include "Jobs/WindowWorker.hpp" +#include "Jobs/BoostThreadWorker.hpp" #include #include @@ -1542,6 +1544,7 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) m_status_bar = std::make_shared(this); m_status_bar->get_panel()->Hide(); + m_worker = std::make_unique>(this, m_status_bar, "send_worker"); auto m_step_icon_panel1 = new wxWindow(this, wxID_ANY); auto m_step_icon_panel2 = new wxWindow(this, wxID_ANY); @@ -1658,12 +1661,7 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) void InputIpAddressDialog::on_cancel() { - if (m_send_job) { - if (m_send_job->is_running()) { - m_send_job->cancel(); - m_send_job->join(); - } - } + m_worker->cancel_all(); if (m_result == 0){ this->EndModal(wxID_YES); }else { @@ -1781,48 +1779,40 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt) m_button_ok->SetBackgroundColor(wxColour(0x90, 0x90, 0x90)); m_button_ok->SetBorderColor(wxColour(0x90, 0x90, 0x90)); - if (m_send_job) { - m_send_job->join(); - } + m_worker->wait_for_idle(); m_status_bar->reset(); m_status_bar->set_prog_block(); m_status_bar->set_cancel_callback_fina([this]() { BOOST_LOG_TRIVIAL(info) << "print_job: enter canceled"; - if (m_send_job) { - if (m_send_job->is_running()) { - BOOST_LOG_TRIVIAL(info) << "send_job: canceled"; - m_send_job->cancel(); - } - m_send_job->join(); - } + m_worker->cancel_all(); }); - m_send_job = std::make_shared(m_status_bar, wxGetApp().plater(), m_obj->dev_id); - m_send_job->m_dev_ip = ip.ToStdString(); - m_send_job->m_access_code = str_access_code.ToStdString(); + auto send_job = std::make_unique(m_obj->dev_id); + send_job->m_dev_ip = ip.ToStdString(); + send_job->m_access_code = str_access_code.ToStdString(); #if !BBL_RELEASE_TO_PUBLIC - m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; - m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; + send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; + send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; #else - m_send_job->m_local_use_ssl_for_mqtt = m_obj->local_use_ssl_for_mqtt; - m_send_job->m_local_use_ssl_for_ftp = m_obj->local_use_ssl_for_ftp; + send_job->m_local_use_ssl_for_mqtt = m_obj->local_use_ssl_for_mqtt; + send_job->m_local_use_ssl_for_ftp = m_obj->local_use_ssl_for_ftp; #endif - m_send_job->connection_type = m_obj->connection_type(); - m_send_job->cloud_print_only = true; - m_send_job->has_sdcard = m_obj->has_sdcard(); - m_send_job->set_check_mode(); - m_send_job->set_project_name("verify_job"); + send_job->connection_type = m_obj->connection_type(); + send_job->cloud_print_only = true; + send_job->has_sdcard = m_obj->has_sdcard(); + send_job->set_check_mode(); + send_job->set_project_name("verify_job"); - m_send_job->on_check_ip_address_fail([this](int result) { + send_job->on_check_ip_address_fail([this](int result) { this->check_ip_address_failed(result); }); - m_send_job->on_check_ip_address_success([this, ip, str_access_code]() { + send_job->on_check_ip_address_success([this, ip, str_access_code]() { wxString input_str = wxString::Format("%s|%s", ip, str_access_code); auto event = wxCommandEvent(EVT_ENTER_IP_ADDRESS); event.SetString(input_str); @@ -1834,7 +1824,7 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt) }); - m_send_job->start(); + replace_job(*m_worker, std::move(send_job)); } void InputIpAddressDialog::check_ip_address_failed(int result) diff --git a/src/slic3r/GUI/ReleaseNote.hpp b/src/slic3r/GUI/ReleaseNote.hpp index 469bc6460d..9c5a1b09df 100644 --- a/src/slic3r/GUI/ReleaseNote.hpp +++ b/src/slic3r/GUI/ReleaseNote.hpp @@ -35,6 +35,7 @@ #include "Widgets/CheckBox.hpp" #include "Widgets/ComboBox.hpp" #include "Widgets/ScrolledWindow.hpp" +#include "Jobs/Worker.hpp" #include #include @@ -298,7 +299,7 @@ class InputIpAddressDialog : public DPIDialog wxHyperlinkCtrl* m_trouble_shoot{ nullptr }; bool m_show_access_code{ false }; int m_result; - std::shared_ptr m_send_job{nullptr}; + std::unique_ptr m_worker; std::shared_ptr m_status_bar; void on_cancel(); diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index fa7ab16345..0fa6afae82 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -24,6 +24,8 @@ #include "Notebook.hpp" #include "BitmapCache.hpp" #include "BindDialog.hpp" +#include "Jobs/WindowWorker.hpp" +#include "Jobs/BoostThreadWorker.hpp" namespace Slic3r { namespace GUI { @@ -1262,6 +1264,8 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater) m_panel_sending = m_status_bar->get_panel(); m_simplebook->AddPage(m_panel_sending, wxEmptyString, false); + m_worker = std::make_unique>(this, m_status_bar, "send_worker"); + // finish mode m_panel_finish = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_panel_finish->SetBackgroundColour(wxColour(135, 206, 250)); @@ -1716,9 +1720,7 @@ void SelectMachineDialog::prepare_mode(bool refresh_button) show_print_failed_info(false); m_is_in_sending_mode = false; - if (m_print_job) { - m_print_job->join(); - } + m_worker->wait_for_idle(); if (wxIsBusy()) wxEndBusyCursor(); @@ -2266,12 +2268,8 @@ void SelectMachineDialog::on_cancel(wxCloseEvent &event) if (m_mapping_popup.IsShown()) m_mapping_popup.Dismiss(); - if (m_print_job) { - if (m_print_job->is_running()) { - m_print_job->cancel(); - m_print_job->join(); - } - } + m_worker->cancel_all(); + this->EndModal(wxID_CANCEL); } @@ -2747,13 +2745,7 @@ void SelectMachineDialog::on_send_print() m_status_bar->set_prog_block(); m_status_bar->set_cancel_callback_fina([this]() { BOOST_LOG_TRIVIAL(info) << "print_job: enter canceled"; - if (m_print_job) { - if (m_print_job->is_running()) { - BOOST_LOG_TRIVIAL(info) << "print_job: canceled"; - m_print_job->cancel(); - } - m_print_job->join(); - } + m_worker->cancel_all(); m_is_canceled = true; wxCommandEvent* event = new wxCommandEvent(EVT_PRINT_JOB_CANCEL); wxQueueEvent(this, event); @@ -2821,63 +2813,63 @@ void SelectMachineDialog::on_send_print() } } - m_print_job = std::make_shared(m_status_bar, m_plater, m_printer_last_select); - m_print_job->m_dev_ip = obj_->dev_ip; - m_print_job->m_ftp_folder = obj_->get_ftp_folder(); - m_print_job->m_access_code = obj_->get_access_code(); + auto print_job = std::make_unique(m_printer_last_select); + print_job->m_dev_ip = obj_->dev_ip; + print_job->m_ftp_folder = obj_->get_ftp_folder(); + print_job->m_access_code = obj_->get_access_code(); #if !BBL_RELEASE_TO_PUBLIC - m_print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; - m_print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; + print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; + print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; #else - m_print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; - m_print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; + print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; + print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; #endif - m_print_job->connection_type = obj_->connection_type(); - m_print_job->cloud_print_only = obj_->is_support_cloud_print_only; + print_job->connection_type = obj_->connection_type(); + print_job->cloud_print_only = obj_->is_support_cloud_print_only; if (m_print_type == PrintFromType::FROM_NORMAL) { BOOST_LOG_TRIVIAL(info) << "print_job: m_print_type = from_normal"; - m_print_job->m_print_type = "from_normal"; - m_print_job->set_project_name(m_current_project_name.utf8_string()); + print_job->m_print_type = "from_normal"; + print_job->set_project_name(m_current_project_name.utf8_string()); } else if(m_print_type == PrintFromType::FROM_SDCARD_VIEW){ BOOST_LOG_TRIVIAL(info) << "print_job: m_print_type = from_sdcard_view"; - m_print_job->m_print_type = "from_sdcard_view"; - //m_print_job->connection_type = "lan"; + print_job->m_print_type = "from_sdcard_view"; + //print_job->connection_type = "lan"; try { - m_print_job->m_print_from_sdc_plate_idx = m_required_data_plate_data_list[m_print_plate_idx]->plate_index + 1; - m_print_job->set_dst_name(m_required_data_file_path); + print_job->m_print_from_sdc_plate_idx = m_required_data_plate_data_list[m_print_plate_idx]->plate_index + 1; + print_job->set_dst_name(m_required_data_file_path); } catch (...) {} - BOOST_LOG_TRIVIAL(info) << "print_job: m_print_plate_idx =" << m_print_job->m_print_from_sdc_plate_idx; + BOOST_LOG_TRIVIAL(info) << "print_job: m_print_plate_idx =" << print_job->m_print_from_sdc_plate_idx; auto input_str_arr = wxGetApp().split_str(m_required_data_file_name, ".gcode.3mf"); if (input_str_arr.size() <= 1) { input_str_arr = wxGetApp().split_str(m_required_data_file_name, ".3mf"); if (input_str_arr.size() > 1) { - m_print_job->set_project_name(input_str_arr[0]); + print_job->set_project_name(input_str_arr[0]); } } else { - m_print_job->set_project_name(input_str_arr[0]); + print_job->set_project_name(input_str_arr[0]); } } if (obj_->is_support_ams_mapping()) { - m_print_job->task_ams_mapping = ams_mapping_array; - m_print_job->task_ams_mapping_info = ams_mapping_info; + print_job->task_ams_mapping = ams_mapping_array; + print_job->task_ams_mapping_info = ams_mapping_info; } else { - m_print_job->task_ams_mapping = ""; - m_print_job->task_ams_mapping_info = ""; + print_job->task_ams_mapping = ""; + print_job->task_ams_mapping_info = ""; } - m_print_job->has_sdcard = obj_->has_sdcard(); + print_job->has_sdcard = obj_->has_sdcard(); bool timelapse_option = select_timelapse->IsShown() ? m_checkbox_list["timelapse"]->GetValue() : true; - m_print_job->set_print_config( + print_job->set_print_config( MachineBedTypeString[0], m_checkbox_list["bed_leveling"]->GetValue(), m_checkbox_list["flow_cali"]->GetValue(), @@ -2886,17 +2878,17 @@ void SelectMachineDialog::on_send_print() true); if (obj_->has_ams()) { - m_print_job->task_use_ams = m_checkbox_list["use_ams"]->GetValue(); + print_job->task_use_ams = m_checkbox_list["use_ams"]->GetValue(); } else { - m_print_job->task_use_ams = false; + print_job->task_use_ams = false; } BOOST_LOG_TRIVIAL(info) << "print_job: timelapse_option = " << timelapse_option; - BOOST_LOG_TRIVIAL(info) << "print_job: use_ams = " << m_print_job->task_use_ams; + BOOST_LOG_TRIVIAL(info) << "print_job: use_ams = " << print_job->task_use_ams; - m_print_job->on_success([this]() { finish_mode(); }); + print_job->on_success([this]() { finish_mode(); }); - m_print_job->on_check_ip_address_fail([this]() { + print_job->on_check_ip_address_fail([this]() { wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS); wxQueueEvent(this, evt); wxGetApp().show_ip_address_enter_dialog(); @@ -2909,7 +2901,7 @@ void SelectMachineDialog::on_send_print() agent->track_update_property(dev_ota_str, obj_->get_ota_version()); } - m_print_job->start(); + replace_job(*m_worker, std::move(print_job)); BOOST_LOG_TRIVIAL(info) << "print_job: start print job"; } diff --git a/src/slic3r/GUI/SelectMachine.hpp b/src/slic3r/GUI/SelectMachine.hpp index 29eb042f79..160f9789cf 100644 --- a/src/slic3r/GUI/SelectMachine.hpp +++ b/src/slic3r/GUI/SelectMachine.hpp @@ -38,6 +38,7 @@ #include "Widgets/ComboBox.hpp" #include "Widgets/ScrolledWindow.hpp" #include "Widgets/PopupWindow.hpp" +#include "Jobs/Worker.hpp" #include #include @@ -429,7 +430,7 @@ class SelectMachineDialog : public DPIDialog wxStaticText* m_statictext_finish{nullptr}; TextInput* m_rename_input{nullptr}; wxTimer* m_refresh_timer{ nullptr }; - std::shared_ptr m_print_job; + std::unique_ptr m_worker; wxScrolledWindow* m_scrollable_view; wxScrolledWindow* m_sw_print_failed_info{nullptr}; wxHyperlinkCtrl* m_hyperlink{nullptr}; diff --git a/src/slic3r/GUI/SendToPrinter.cpp b/src/slic3r/GUI/SendToPrinter.cpp index 023ed1ec44..7706767ed5 100644 --- a/src/slic3r/GUI/SendToPrinter.cpp +++ b/src/slic3r/GUI/SendToPrinter.cpp @@ -12,6 +12,8 @@ #include "Widgets/RoundedRectangle.hpp" #include "Widgets/StaticBox.hpp" #include "ConnectPrinter.hpp" +#include "Jobs/WindowWorker.hpp" +#include "Jobs/BoostThreadWorker.hpp" #include #include @@ -301,6 +303,8 @@ SendToPrinterDialog::SendToPrinterDialog(Plater *plater) m_panel_sending = m_status_bar->get_panel(); m_simplebook->AddPage(m_panel_sending, wxEmptyString, false); + m_worker = std::make_unique>(this, m_status_bar, "send_worker"); + // finish mode m_panel_finish = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_panel_finish->SetBackgroundColour(wxColour(135, 206, 250)); @@ -580,9 +584,7 @@ void SendToPrinterDialog::prepare_mode() { m_is_in_sending_mode = false; m_comboBox_printer->Enable(); - if (m_send_job) { - m_send_job->join(); - } + m_worker->wait_for_idle(); if (wxIsBusy()) wxEndBusyCursor(); @@ -670,12 +672,7 @@ void SendToPrinterDialog::init_timer() void SendToPrinterDialog::on_cancel(wxCloseEvent &event) { - if (m_send_job) { - if (m_send_job->is_running()) { - m_send_job->cancel(); - m_send_job->join(); - } - } + m_worker->cancel_all(); this->EndModal(wxID_CANCEL); } @@ -712,13 +709,7 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event) m_status_bar->set_prog_block(); m_status_bar->set_cancel_callback_fina([this]() { BOOST_LOG_TRIVIAL(info) << "print_job: enter canceled"; - if (m_send_job) { - if (m_send_job->is_running()) { - BOOST_LOG_TRIVIAL(info) << "send_job: canceled"; - m_send_job->cancel(); - } - m_send_job->join(); - } + m_worker->cancel_all(); m_is_canceled = true; wxCommandEvent* event = new wxCommandEvent(EVT_PRINT_JOB_CANCEL); wxQueueEvent(this, event); @@ -776,40 +767,37 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event) - m_send_job = std::make_shared(m_status_bar, m_plater, m_printer_last_select); - m_send_job->m_dev_ip = obj_->dev_ip; - m_send_job->m_access_code = obj_->get_access_code(); + auto send_job = std::make_unique(m_printer_last_select); + send_job->m_dev_ip = obj_->dev_ip; + send_job->m_access_code = obj_->get_access_code(); #if !BBL_RELEASE_TO_PUBLIC - m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; - m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; + send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; + send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; #else - m_send_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; - m_send_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; + send_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; + send_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; #endif - m_send_job->connection_type = obj_->connection_type(); - m_send_job->cloud_print_only = true; - m_send_job->has_sdcard = obj_->has_sdcard(); - m_send_job->set_project_name(m_current_project_name.utf8_string()); + send_job->connection_type = obj_->connection_type(); + send_job->cloud_print_only = true; + send_job->has_sdcard = obj_->has_sdcard(); + send_job->set_project_name(m_current_project_name.utf8_string()); enable_prepare_mode = false; - m_send_job->on_check_ip_address_fail([this](int result) { + send_job->on_check_ip_address_fail([this](int result) { wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS); wxQueueEvent(this, evt); wxGetApp().show_ip_address_enter_dialog(); }); if (obj_->is_lan_mode_printer()) { - m_send_job->set_check_mode(); - m_send_job->check_and_continue(); - m_send_job->start(); - } - else { - m_send_job->start(); + send_job->set_check_mode(); + send_job->check_and_continue(); } + replace_job(*m_worker, std::move(send_job)); BOOST_LOG_TRIVIAL(info) << "send_job: send print job"; } diff --git a/src/slic3r/GUI/SendToPrinter.hpp b/src/slic3r/GUI/SendToPrinter.hpp index 16a545ed12..07d3e203c4 100644 --- a/src/slic3r/GUI/SendToPrinter.hpp +++ b/src/slic3r/GUI/SendToPrinter.hpp @@ -36,6 +36,7 @@ #include "Widgets/CheckBox.hpp" #include "Widgets/ComboBox.hpp" #include "Widgets/ScrolledWindow.hpp" +#include "Jobs/Worker.hpp" #include #include @@ -105,7 +106,7 @@ class SendToPrinterDialog : public DPIDialog wxStaticText* m_file_name; PrintDialogStatus m_print_status{ PrintStatusInit }; - std::shared_ptr m_send_job{nullptr}; + std::unique_ptr m_worker; std::vector m_bedtype_list; std::map m_checkbox_list; std::vector m_list; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4bf2376e55..25b43d887e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1565,7 +1565,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "filament_long_retractions_when_cut"){ unsigned char activate = boost::any_cast(value); if (activate == 1) { - MessageDialog dialog(wxGetApp().plater(), + MessageDialog dialog(wxGetApp().plater(), _L("Experimental feature: Retracting and cutting off the filament at a greater distance during filament changes to minimize flush." "Although it can notably reduce flush, it may also elevate the risk of nozzle clogs or other printing complications.Please use with the latest printer firmware."), "", wxICON_WARNING | wxOK); dialog.ShowModal(); @@ -2748,7 +2748,7 @@ void TabPrintPlate::notify_changed(ObjectBase* object) for (auto item : items) { if (objects_list->GetModel()->GetItemType(item) == itPlate) { ObjectDataViewModelNode* node = static_cast(item.GetID()); - if (node) + if (node) node->set_action_icon(!m_all_keys.empty()); } } @@ -2756,7 +2756,7 @@ void TabPrintPlate::notify_changed(ObjectBase* object) void TabPrintPlate::update_custom_dirty() { - for (auto k : m_null_keys) + for (auto k : m_null_keys) m_options_list[k] = 0; for (auto k : m_all_keys) { if (k == "first_layer_sequence_choice" || k == "other_layers_sequence_choice") { @@ -3496,6 +3496,7 @@ void TabPrinter::build_fff() optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); optgroup->append_single_option_line("printer_structure"); optgroup->append_single_option_line("gcode_flavor"); + optgroup->append_single_option_line("bbl_use_printhost"); option =optgroup->get_option("thumbnail_size"); option.opt.full_width=true; @@ -3573,7 +3574,7 @@ void TabPrinter::build_fff() option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); - + optgroup = page->new_optgroup(L("Time lapse G-code"), L"param_gcode", 0); optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { validate_custom_gcode_cb(this, optgroup, opt_key, value); @@ -4113,6 +4114,7 @@ void TabPrinter::toggle_options() toggle_option("support_chamber_temp_control",!is_BBL_printer); toggle_option("use_firmware_retraction", !is_BBL_printer); toggle_option("support_air_filtration",is_BBL_printer); + toggle_option("bbl_use_printhost", is_BBL_printer); auto flavor = m_config->option>("gcode_flavor")->value; bool is_marlin_flavor = flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware; // Disable silent mode for non-marlin firmwares. @@ -4600,13 +4602,13 @@ bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, try { //BBS delete preset Preset ¤t_preset = m_presets->get_selected_preset(); - + // Obtain compatible filament and process presets for printers if (m_preset_bundle && m_presets->get_preset_base(current_preset) == ¤t_preset && printer_tab && !current_preset.is_system) { delete_third_printer = true; for (const Preset &preset : m_preset_bundle->filaments.get_presets()) { if (preset.is_compatible && !preset.is_default) { - if (preset.inherits() != "") + if (preset.inherits() != "") filament_presets.push_front(preset); else filament_presets.push_back(preset); @@ -4731,7 +4733,7 @@ bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, }); } - + } if (technology_changed) @@ -5498,7 +5500,7 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) load_key_value("bed_custom_model", custom_model); update_changed_ui(); } - + } else { show_error(m_parent, _L("Invalid input.")); } @@ -5972,7 +5974,7 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("support_head_front_diameter"); // optgroup->append_single_option_line("support_head_penetration"); // optgroup->append_single_option_line("support_head_width"); -// + // // optgroup = page->new_optgroup(L("Support pillar")); // optgroup->append_single_option_line("support_pillar_diameter"); // optgroup->append_single_option_line("support_small_pillar_diameter_percent"); @@ -6022,7 +6024,7 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("pad_object_connector_stride"); // optgroup->append_single_option_line("pad_object_connector_width"); // optgroup->append_single_option_line("pad_object_connector_penetration"); -// + // // page = add_options_page(L("Hollowing"), "hollowing"); // optgroup = page->new_optgroup(L("Hollowing")); // optgroup->append_single_option_line("hollowing_enable"); diff --git a/src/slic3r/Utils/CalibUtils.cpp b/src/slic3r/Utils/CalibUtils.cpp index fc985368c2..0d0fa69cc0 100644 --- a/src/slic3r/Utils/CalibUtils.cpp +++ b/src/slic3r/Utils/CalibUtils.cpp @@ -3,6 +3,8 @@ #include "../GUI/GUI_App.hpp" #include "../GUI/DeviceManager.hpp" #include "../GUI/Jobs/ProgressIndicator.hpp" +#include "../GUI/Jobs/PlaterWorker.hpp" +#include "../GUI/Jobs/BoostThreadWorker.hpp" #include "../GUI/PartPlate.hpp" #include "libslic3r/Model.hpp" @@ -14,7 +16,7 @@ namespace GUI { const float MIN_PA_K_VALUE = 0.0; const float MAX_PA_K_VALUE = 1.0; -std::shared_ptr CalibUtils::print_job; +std::unique_ptr CalibUtils::m_print_worker; wxString wxstr_temp_dir = fs::path(fs::temp_directory_path() / "calib").wstring(); static const std::string temp_dir = wxstr_temp_dir.utf8_string(); static const std::string temp_gcode_path = temp_dir + "/temp.gcode"; @@ -1240,7 +1242,9 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess } } - print_job = std::make_shared(std::move(process_bar), wxGetApp().plater(), dev_id); + m_print_worker = std::make_unique>(wxGetApp().plater(), std::move(process_bar), "calib_worker"); + + auto print_job = std::make_unique(dev_id); print_job->m_dev_ip = obj_->dev_ip; print_job->m_ftp_folder = obj_->get_ftp_folder(); print_job->m_access_code = obj_->get_access_code(); @@ -1293,7 +1297,7 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess BOOST_LOG_TRIVIAL(info) << "send_cali_job - after send: " << j.dump(); } - print_job->start(); + replace_job(*m_print_worker, std::move(print_job)); } } diff --git a/src/slic3r/Utils/CalibUtils.hpp b/src/slic3r/Utils/CalibUtils.hpp index 980ba30c1a..96ee502759 100644 --- a/src/slic3r/Utils/CalibUtils.hpp +++ b/src/slic3r/Utils/CalibUtils.hpp @@ -2,6 +2,7 @@ #include "libslic3r/Calib.hpp" #include "../GUI/DeviceManager.hpp" #include "../GUI/Jobs/PrintJob.hpp" +#include "../GUI/Jobs/Worker.hpp" namespace Slic3r { @@ -29,7 +30,7 @@ class CalibUtils { public: CalibUtils(){}; - static std::shared_ptr print_job; + static std::unique_ptr m_print_worker; static CalibMode get_calib_mode_by_name(const std::string name, int &cali_stage); diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 0e1428be31..ccd542a88f 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -87,6 +87,17 @@ std::unique_ptr CurlGlobalInit::instance; std::map extra_headers; std::mutex g_mutex; +struct form_file +{ + fs::ifstream ifs; + boost::filesystem::ifstream::off_type init_offset; + size_t content_length; + + form_file(fs::path const& p, const boost::filesystem::ifstream::off_type offset, const size_t content_length) + : ifs(p, std::ios::in | std::ios::binary), init_offset(offset), content_length(content_length) + {} +}; + struct Http::priv { enum { @@ -104,7 +115,7 @@ struct Http::priv std::string buffer; // Used for storing file streams added as multipart form parts // Using a deque here because unlike vector it doesn't ivalidate pointers on insertion - std::deque form_files; + std::deque form_files; std::string postfields; std::string error_buffer; // Used for CURLOPT_ERRORBUFFER std::string headers; @@ -131,7 +142,7 @@ struct Http::priv void set_timeout_connect(long timeout); void set_timeout_max(long timeout); - void form_add_file(const char *name, const fs::path &path, const char* filename); + void form_add_file(const char *name, const fs::path &path, const char* filename, boost::filesystem::ifstream::off_type offset, size_t length); /* mime */ void mime_form_add_text(const char* name, const char* value); void mime_form_add_file(const char* name, const char* path); @@ -254,15 +265,27 @@ int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp) { - auto stream = reinterpret_cast(userp); + auto f = reinterpret_cast(userp); try { - stream->read(buffer, size * nitems); + size_t max_read_size = size * nitems; + if (f->content_length == 0) { + // Unlimited + f->ifs.read(buffer, max_read_size); + } else { + unsigned long long read_size = f->ifs.tellg() - f->init_offset; + if (read_size >= f->content_length) { + return 0; + } + + max_read_size = std::min(max_read_size, size_t(f->content_length - read_size)); + f->ifs.read(buffer, max_read_size); + } } catch (const std::exception &) { return CURL_READFUNC_ABORT; } - return stream->gcount(); + return f->ifs.gcount(); } size_t Http::priv::headers_cb(char *buffer, size_t size, size_t nitems, void *userp) @@ -286,7 +309,7 @@ void Http::priv::set_timeout_max(long timeout) ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); } -void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) +void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename, boost::filesystem::ifstream::off_type offset, size_t length) { // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows // and so we use CURLFORM_STREAM with boost ifstream to read the file. @@ -295,18 +318,21 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha filename = path.string().c_str(); } - form_files.emplace_back(path, std::ios::in | std::ios::binary); - auto &stream = form_files.back(); - stream.seekg(0, std::ios::end); - size_t size = stream.tellg(); - stream.seekg(0); + form_files.emplace_back(path, offset, length); + auto &f = form_files.back(); + size_t size = length; + if (length == 0) { + f.ifs.seekg(0, std::ios::end); + size = f.ifs.tellg() - offset; + } + f.ifs.seekg(offset); if (filename != nullptr) { ::curl_formadd(&form, &form_end, CURLFORM_COPYNAME, name, CURLFORM_FILENAME, filename, CURLFORM_CONTENTTYPE, "application/octet-stream", - CURLFORM_STREAM, static_cast(&stream), + CURLFORM_STREAM, static_cast(&f), CURLFORM_CONTENTSLENGTH, static_cast(size), CURLFORM_END ); @@ -576,9 +602,9 @@ Http& Http::form_add(const std::string &name, const std::string &contents) return *this; } -Http& Http::form_add_file(const std::string &name, const fs::path &path) +Http& Http::form_add_file(const std::string &name, const fs::path &path, boost::filesystem::ifstream::off_type offset, size_t length) { - if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); } + if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr, offset, length); } return *this; } @@ -596,15 +622,15 @@ Http& Http::mime_form_add_file(std::string &name, const char* path) } -Http& Http::form_add_file(const std::wstring& name, const fs::path& path) +Http& Http::form_add_file(const std::wstring& name, const fs::path& path, boost::filesystem::ifstream::off_type offset, size_t length) { - if (p) { p->form_add_file((char*)name.c_str(), path.c_str(), nullptr); } + if (p) { p->form_add_file((char*)name.c_str(), path.c_str(), nullptr, offset, length); } return *this; } -Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename) +Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename, boost::filesystem::ifstream::off_type offset, size_t length) { - if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); } + if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str(), offset, length); } return *this; } diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index c7f7ac2cd9..7e7e10cc9e 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -118,15 +118,15 @@ class Http : public std::enable_shared_from_this { // Add a HTTP multipart form field Http& form_add(const std::string &name, const std::string &contents); // Add a HTTP multipart form file data contents, `name` is the name of the part - Http& form_add_file(const std::string &name, const boost::filesystem::path &path); + Http& form_add_file(const std::string &name, const boost::filesystem::path &path, boost::filesystem::ifstream::off_type offset = 0, size_t length = 0); // Add a HTTP mime form field Http& mime_form_add_text(std::string& name, std::string& value); // Add a HTTP mime form file Http& mime_form_add_file(std::string& name, const char* path); // Same as above except also override the file's filename with a wstring type - Http& form_add_file(const std::wstring& name, const boost::filesystem::path& path); + Http& form_add_file(const std::wstring& name, const boost::filesystem::path& path, boost::filesystem::ifstream::off_type offset = 0, size_t length = 0); // Same as above except also override the file's filename with a custom one - Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename); + Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename, boost::filesystem::ifstream::off_type offset = 0, size_t length = 0); #ifdef WIN32 // Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present. diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 86f6101b6d..6b9b6af8fb 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -20,6 +20,8 @@ #include "Repetier.hpp" #include "MKS.hpp" #include "../GUI/PrintHostDialogs.hpp" +#include "../GUI/MainFrame.hpp" +#include "SimplyPrint.hpp" namespace fs = boost::filesystem; using boost::optional; @@ -53,6 +55,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) case htRepetier: return new Repetier(config); case htPrusaLink: return new PrusaLink(config); case htMKS: return new MKS(config); + case htSimplyPrint: return new SimplyPrint(config); default: return nullptr; } } else { @@ -263,6 +266,10 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job) if (success) { emit_progress(100); + if (the_job.switch_to_device_tab) { + const auto mainframe = GUI::wxGetApp().mainframe; + mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor); + } } } diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index dd22e60b7d..27064caa9b 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -28,11 +28,12 @@ ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction); struct PrintHostUpload { + bool use_3mf; boost::filesystem::path source_path; boost::filesystem::path upload_path; std::string group; - + PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None }; }; @@ -64,6 +65,12 @@ class PrintHost static PrintHost* get_print_host(DynamicPrintConfig *config); + //Support for cloud webui login + virtual bool is_cloud() const { return false; } + virtual bool is_logged_in() const { return false; } + virtual void log_out() const {} + virtual bool get_login_url(wxString& auth_url) const { return false; } + protected: virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const; }; @@ -73,6 +80,7 @@ struct PrintHostJob { PrintHostUpload upload_data; std::unique_ptr printhost; + bool switch_to_device_tab{false}; bool cancelled = false; PrintHostJob() {} @@ -80,6 +88,7 @@ struct PrintHostJob PrintHostJob(PrintHostJob &&other) : upload_data(std::move(other.upload_data)) , printhost(std::move(other.printhost)) + , switch_to_device_tab(other.switch_to_device_tab) , cancelled(other.cancelled) {} @@ -91,7 +100,8 @@ struct PrintHostJob PrintHostJob& operator=(PrintHostJob &&other) { upload_data = std::move(other.upload_data); - printhost = std::move(other.printhost); + printhost = std::move(other.printhost); + switch_to_device_tab = other.switch_to_device_tab; cancelled = other.cancelled; return *this; } diff --git a/src/slic3r/Utils/SimplyPrint.cpp b/src/slic3r/Utils/SimplyPrint.cpp new file mode 100644 index 0000000000..625efc1fa6 --- /dev/null +++ b/src/slic3r/Utils/SimplyPrint.cpp @@ -0,0 +1,509 @@ +#include "SimplyPrint.hpp" + +#include +#include +#include + +#include "libslic3r/Utils.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/MainFrame.hpp" + +namespace pt = boost::property_tree; +namespace jp = boost::property_tree::json_parser; + +namespace Slic3r { + +// to make testing easier +//#define SIMPLYPRINT_TEST + +#ifdef SIMPLYPRINT_TEST +#define URL_BASE_HOME "https://test.simplyprint.io" +#define URL_BASE_API "https://testapi.simplyprint.io" +#else +#define URL_BASE_HOME "https://simplyprint.io" +#define URL_BASE_API "https://api.simplyprint.io" +#endif + +static constexpr boost::asio::ip::port_type CALLBACK_PORT = 21328; +static const std::string CALLBACK_URL = "http://localhost:21328/callback"; +static const std::string RESPONSE_TYPE = "code"; +static const std::string CLIENT_ID = "simplyprintbambustudio"; +static const std::string CLIENT_SCOPES = "user.read files.temp_upload"; +static const std::string OAUTH_CREDENTIAL_PATH = "simplyprint_oauth.json"; +static const std::string TOKEN_URL = URL_BASE_API"/oauth2/Token"; +#ifdef SIMPLYPRINT_TEST +static constexpr uint64_t MAX_SINGLE_UPLOAD_FILE_SIZE = 100000ull; // Max file size that can be uploaded in a single http request +#else +static constexpr uint64_t MAX_SINGLE_UPLOAD_FILE_SIZE = 100000000ull; // Max file size that can be uploaded in a single http request +#endif +static const std::string CHUNCK_RECEIVE_URL = URL_BASE_API"/0/files/ChunkReceive"; + +static std::string generate_verification_code(int code_length = 32) +{ + std::stringstream ss; + for (auto i = 0; i < code_length; i++) { + ss << std::hex << std::setw(2) << std::setfill('0') << (rand() % 0x100); + } + + return ss.str(); +} + +static std::string sha256b64(const std::string& inputStr) +{ + unsigned char hash[SHA256_DIGEST_LENGTH]; + const unsigned char* data = (const unsigned char*) inputStr.c_str(); + SHA256(data, inputStr.size(), hash); + + std::string b64; + b64.resize(boost::beast::detail::base64::encoded_size(sizeof(hash))); + b64.resize(boost::beast::detail::base64::encode(&b64[0], hash, sizeof(hash))); + + // uses '-' instead of '+' and '_' instead of '/' for url-safe + std::replace(b64.begin(), b64.end(), '+', '-'); + std::replace(b64.begin(), b64.end(), '/', '_'); + + // Stripping "=" is for RFC 7636 compliance + b64.erase(std::remove(b64.begin(), b64.end(), '='), b64.end()); + + return b64; +} + +static std::string url_encode(const std::vector> query) +{ + std::vector q; + q.reserve(query.size()); + + std::transform(query.begin(), query.end(), std::back_inserter(q), [](const auto& kv) { + if (kv.second.empty()) { + return Http::url_encode(kv.first); + } + return Http::url_encode(kv.first) + "=" + Http::url_encode(kv.second); + }); + + return boost::algorithm::join(q, "&"); +} + +static void set_auth(Http& http, const std::string& access_token) { http.header("Authorization", "Bearer " + access_token); } + +static bool should_open_in_external_browser() +{ + const auto& app = wxGetApp(); + + if (app.preset_bundle->use_bbl_device_tab()) { + // When using bbl device tab, we always need to open external browser + return true; + } + + // Otherwise, if user choose to switch to device tab, then don't bother opening external browser + return app.app_config->get("open_device_tab_post_upload") != "true"; +} + +SimplyPrint::SimplyPrint(DynamicPrintConfig* config) +{ + cred_file = (boost::filesystem::path(data_dir()) / OAUTH_CREDENTIAL_PATH).make_preferred().string(); + load_oauth_credential(); +} + +GUI::OAuthParams SimplyPrint::get_oauth_params() const +{ + const auto verification_code = generate_verification_code(); + // SimplyPrint uses S256 for PKCE + const auto code_challenge = sha256b64(verification_code); + const auto state = generate_verification_code(); + + const std::vector> query_parameters{ + {"client_id", CLIENT_ID}, + {"redirect_uri", CALLBACK_URL}, + {"scope", CLIENT_SCOPES}, + {"response_type", RESPONSE_TYPE}, + {"state", state}, + {"code_challenge", code_challenge}, + {"code_challenge_method", "S256"}, + }; + const auto login_url = (boost::format(URL_BASE_HOME"/panel/oauth2/authorize?%s") % url_encode(query_parameters)).str(); + + return GUI::OAuthParams{ + login_url, + CLIENT_ID, + CALLBACK_PORT, + CALLBACK_URL, + CLIENT_SCOPES, + RESPONSE_TYPE, + URL_BASE_HOME"/login-success", + URL_BASE_HOME"/login-success", + TOKEN_URL, + verification_code, + state, + }; +} + +void SimplyPrint::load_oauth_credential() +{ + cred.clear(); + if (boost::filesystem::exists(cred_file)) { + pt::ptree j; + try { + jp::read_json(cred_file, j); + + cred["access_token"] = j.get("access_token"); + cred["refresh_token"] = j.get("refresh_token"); + } catch (jp::json_parser_error& err) { // handle errors during parsing json to ptree + BOOST_LOG_TRIVIAL(error) + << __FUNCTION__ << ": parse " << cred_file << " failed, reason = " << err.what(); + cred.clear(); + } catch (pt::ptree_error& err) { // handle errors during retrieving data from ptree + BOOST_LOG_TRIVIAL(error) + << __FUNCTION__ << ": failed to get expected json data, reason = " << err.what(); + cred.clear(); + } + } +} + +void SimplyPrint::save_oauth_credential(const GUI::OAuthResult& cred) const +{ + pt::ptree j; + j.put("access_token", cred.access_token); + j.put("refresh_token", cred.refresh_token); + + try { + jp::write_json(cred_file, j); + } catch (jp::json_parser_error& err) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ + << ": failed to write json to file. Path = " << cred_file + << " Reason = " << err.what(); + } +} + +wxString SimplyPrint::get_test_ok_msg() const { return _(L("Connected to SimplyPrint successfully!")); } + +wxString SimplyPrint::get_test_failed_msg(wxString& msg) const +{ + return GUI::format_wxstr("%s: %s", _L("Could not connect to SimplyPrint"), msg.Truncate(256)); +} + +void SimplyPrint::log_out() const +{ + boost::nowide::remove(cred_file.c_str()); +} + +bool SimplyPrint::do_api_call(std::function build_request, + std::function on_complete, + std::function on_error) const +{ + if (cred.find("access_token") == cred.end()) { + return false; + } + + bool res = true; + + const auto create_request = [this, &build_request, &res, &on_complete](const std::string& access_token, bool is_retry) { + auto http = build_request(is_retry); + set_auth(http, access_token); + http.header("User-Agent", "SimplyPrint BambuStudio Plugin") + .on_complete([&](std::string body, unsigned http_status) { + res = on_complete(body, http_status); + }); + + return http; + }; + + create_request(cred.at("access_token"), false) + .on_error([&res, &on_error, this, &create_request](std::string body, std::string error, unsigned http_status) { + if (http_status == 401) { + // Refresh token + BOOST_LOG_TRIVIAL(warning) << boost::format("SimplyPrint: Access token invalid: %1%, HTTP %2%, body: `%3%`") % error % + http_status % body; + BOOST_LOG_TRIVIAL(info) << "SimplyPrint: Attempt to refresh access token"; + + auto http = Http::post(TOKEN_URL); + http.timeout_connect(5) + .timeout_max(5) + .form_add("grant_type", "refresh_token") + .form_add("client_id", CLIENT_ID) + .form_add("refresh_token", cred.at("refresh_token")) + .on_complete([this, &res, &on_error, &create_request](std::string body, unsigned http_status) { + GUI::OAuthResult r; + GUI::OAuthJob::parse_token_response(body, false, r); + if (r.success) { + BOOST_LOG_TRIVIAL(info) << "SimplyPrint: Successfully refreshed access token"; + this->save_oauth_credential(r); + + // Run the api call again + create_request(r.access_token, true) + .on_error([&res, &on_error](std::string body, std::string error, unsigned http_status) { + res = on_error(body, error, http_status); + }) + .perform_sync(); + } else { + BOOST_LOG_TRIVIAL(error) + << boost::format("SimplyPrint: Failed to refresh access token: %1%, body: `%2%`") % r.error_message % body; + res = on_error(body, r.error_message, http_status); + } + }) + .on_error([&res, &on_error](std::string body, std::string error, unsigned http_status) { + BOOST_LOG_TRIVIAL(error) + << boost::format("SimplyPrint: Failed to refresh access token: %1%, HTTP %2%, body: `%3%`") % error % + http_status % body; + res = on_error(body, error, http_status); + }) + .perform_sync(); + } else { + res = on_error(body, error, http_status); + } + }) + .perform_sync(); + + return res; +} + + +bool SimplyPrint::test(wxString& curl_msg) const +{ + if (cred.find("access_token") == cred.end()) { + return false; + } + + return do_api_call( + [](bool is_retry) { + auto http = Http::get(URL_BASE_API"/oauth2/TokenInfo"); + http.header("Accept", "application/json"); + return http; + }, + [](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: Got token info: %1%") % body; + return true; + }, + [](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("SimplyPrint: Error getting token info: %1%, HTTP %2%, body: `%3%`") % error % + status % body; + return false; + }); +} + +bool SimplyPrint::do_temp_upload(const boost::filesystem::path& file_path, + const std::string& chunk_id, + const std::string& filename, + ProgressFn prorgess_fn, + ErrorFn error_fn) const +{ + if (file_path.empty() == chunk_id.empty()) { + BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid arguments: both file_path and chunk_id are set or not provided"; + error_fn(_L("Internel error")); + return false; + } + + return do_api_call( + [&file_path, &chunk_id, &prorgess_fn, &filename](bool is_retry) { + auto http = Http::post(URL_BASE_HOME"/api/files/TempUpload"); + if (!file_path.empty()) { + http.form_add_file("file", file_path, filename); + } else { + http.form_add("chunkId", chunk_id); + } + http.on_progress([&prorgess_fn](Http::Progress progress, bool& cancel) { prorgess_fn(std::move(progress), cancel); }); + + return http; + }, + [&error_fn, &filename, this](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: File uploaded: HTTP %1%: %2%") % status % body; + + // Get file UUID + pt::ptree j; + std::stringstream ss(body); + try { + jp::read_json(ss, j); + } catch (jp::json_parser_error& err) { + BOOST_LOG_TRIVIAL(error) + << "SimplyPrint: Invalid JSON data on token response: Body = " << body + << " Reason = " << err.what(); + error_fn(_L("Unknown error")); + return false; + } + + auto opt = j.get_optional("uuid"); + if (!opt) { + BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid or no JSON data on token response: " << body; + error_fn(_L("Unknown error")); + return false; + } + const std::string& uuid = opt.get(); + + // Launch external browser for file importing after uploading + const auto url = URL_BASE_HOME"/panel?" + url_encode({{"import", "tmp:" + uuid}, {"filename", filename}}); + + if (should_open_in_external_browser()) { + wxLaunchDefaultBrowser(url); + } else { + const auto mainframe = GUI::wxGetApp().mainframe; + mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor); + mainframe->load_printer_url(url); + } + + return true; + }, + [this, &error_fn](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("SimplyPrint: Error uploading file : %1%, HTTP %2%, body: `%3%`") % error % status % body; + error_fn(format_error(body, error, status)); + return false; + }); +} + +bool SimplyPrint::do_chunk_upload(const boost::filesystem::path& file_path, const std::string& filename, ProgressFn prorgess_fn, ErrorFn error_fn) const +{ + const auto file_size = boost::filesystem::file_size(file_path); +#ifdef SIMPLYPRINT_TEST + constexpr auto buffer_size = MAX_SINGLE_UPLOAD_FILE_SIZE; +#else + constexpr auto buffer_size = MAX_SINGLE_UPLOAD_FILE_SIZE - 1000000; +#endif + + const auto chunk_amount = (size_t)ceil((double) file_size / buffer_size); + + std::string chunk_id; + std::string delete_token; + + // Tell SimplyPrint that the upload has failed and the chunks should be deleted + // Note: any error happens here won't be notified to the user + const auto clean_up = [this, &chunk_id, &delete_token]() { + if (chunk_id.empty()) { + // The initial upload failed, do nothing + BOOST_LOG_TRIVIAL(warning) << "SimplyPrint: Initial chunk upload failed, skip delete"; + return; + } + + assert(!delete_token.empty()); + + BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: Deleting file chunk %s...") % chunk_id; + const std::vector> query_parameters{ + {"id", chunk_id}, + {"temp", "true"}, + {"delete", delete_token}, + }; + const auto url = (boost::format("%s?%s") % CHUNCK_RECEIVE_URL % url_encode(query_parameters)).str(); + do_api_call( + [&url](bool is_retry) { + auto http = Http::get(url); + return http; + }, + [&chunk_id](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: File chunk %1% deleted: HTTP %2%: %3%") % chunk_id % status % body; + return true; + }, + [&chunk_id](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(warning) << boost::format("SimplyPrint: Error deleting file chunk %1%: %2%, HTTP %3%, body: `%4%`") % + chunk_id % error % status % body; + return false; + }); + }; + + // Do chunk upload + for (size_t i = 0; i < chunk_amount; i++) { + std::string url; + { + std::vector> query_parameters{ + {"i", std::to_string(i)}, + {"temp", "true"}, + }; + if (i == 0) { + query_parameters.emplace_back("filename", filename); + query_parameters.emplace_back("chunks", std::to_string(chunk_amount)); + query_parameters.emplace_back("totalsize", std::to_string(file_size)); + } else { + query_parameters.emplace_back("id", chunk_id); + } + url = (boost::format("%s?%s") % CHUNCK_RECEIVE_URL % url_encode(query_parameters)).str(); + } + + // Calculate the offset and length of current chunk + const boost::filesystem::ifstream::off_type offset = i * buffer_size; + const size_t length = i == (chunk_amount - 1) ? file_size - offset : buffer_size; + + const bool succ = do_api_call( + [&url, &file_path, &filename, i, chunk_amount, file_size, offset, length, prorgess_fn](bool is_retry) { + BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: Start uploading file chunk [%1%/%2%]...") % (i + 1) % chunk_amount; + auto http = Http::post(url); + http.form_add_file("file", file_path, filename, offset, length); + + http.on_progress([&prorgess_fn, file_size, offset](Http::Progress progress, bool& cancel) { + progress.ultotal = file_size; + progress.ulnow += offset; + + prorgess_fn(std::move(progress), cancel); + }); + + return http; + }, + [&error_fn, i, chunk_amount, this, &chunk_id, &delete_token](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: File chunk [%1%/%2%] uploaded: HTTP %3%: %4%") % (i + 1) % chunk_amount % status % body; + if (i == 0) { + // First chunk, parse chunk id + pt::ptree j; + std::stringstream ss(body); + try { + jp::read_json(ss, j); + } catch (jp::json_parser_error& err) { + BOOST_LOG_TRIVIAL(error) + << "SimplyPrint: Invalid data on ChunkReceive: Body = " << body + << " Reason = " << err.what(); + error_fn(_L("Unknown error")); + return false; + } + + auto opt_id = j.get_optional("id"); + auto opt_del_token = j.get_optional("delete_token"); + if (!opt_id || !opt_del_token) { + BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid or no JSON data on ChunkReceive: Body = " << body; + error_fn(_L("Unknown error")); + return false; + } + + chunk_id = opt_id.get(); + delete_token = opt_del_token.get(); + } + return true; + }, + [this, &error_fn, i, chunk_amount](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("SimplyPrint: Error uploading file chunk [%1%/%2%]: %3%, HTTP %4%, body: `%5%`") % + (i + 1) % chunk_amount % error % status % body; + error_fn(format_error(body, error, status)); + return false; + }); + + if (!succ) { + clean_up(); + return false; + } + } + + assert(!chunk_id.empty()); + + // Finally, complete the upload using the chunk id + const bool succ = do_temp_upload({}, chunk_id, filename, prorgess_fn, error_fn); + if (!succ) { + clean_up(); + } + + return succ; +} + + +bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const +{ + if (cred.find("access_token") == cred.end()) { + error_fn(_L("SimplyPrint account not linked. Go to Connect options to set it up.")); + return false; + } + const auto filename = upload_data.upload_path.filename().string(); + + if (boost::filesystem::file_size(upload_data.source_path) > MAX_SINGLE_UPLOAD_FILE_SIZE) { + // If file is over 100 MB, do chunk upload + return do_chunk_upload(upload_data.source_path, filename, prorgess_fn, error_fn); + } else { + // Normal upload + return do_temp_upload(upload_data.source_path, {}, filename, prorgess_fn, error_fn); + } +} + +} // namespace Slic3r diff --git a/src/slic3r/Utils/SimplyPrint.hpp b/src/slic3r/Utils/SimplyPrint.hpp new file mode 100644 index 0000000000..269e8336ba --- /dev/null +++ b/src/slic3r/Utils/SimplyPrint.hpp @@ -0,0 +1,72 @@ +#ifndef slic3r_SimplyPrint_hpp_ +#define slic3r_SimplyPrint_hpp_ + +#include "PrintHost.hpp" +#include "slic3r/GUI/Jobs/OAuthJob.hpp" + +namespace Slic3r { + +class DynamicPrintConfig; +class Http; +class SimplyPrint : public PrintHost +{ + std::string cred_file; + std::map cred; + + void load_oauth_credential(); + + /** + * \brief Call the given SimplyPrint API, and if the token expired do a token refresh then retry + * \param build_request the http request builder + * \param on_complete + * \param on_error + * \return whether the API call succeeded + */ + bool do_api_call(std::function build_request, + std::function on_complete, + std::function on_error) const; + + /** + * \brief Upload a temp file and open SimplyPrint panel for file importing + * \param file_path for file smaller than 100MB, this is the file path, otherwise must left empty + * \param chunk_id for file greater than 100MB, this is the chunk id returned by the ChunkReceive API, otherwise must left empty + * \param filename the target file name + * \param prorgess_fn + * \param error_fn + * \return whether upload succeeded + */ + bool do_temp_upload(const boost::filesystem::path& file_path, + const std::string& chunk_id, + const std::string& filename, + ProgressFn prorgess_fn, + ErrorFn error_fn) const; + + bool do_chunk_upload(const boost::filesystem::path& file_path, + const std::string& filename, + ProgressFn prorgess_fn, + ErrorFn error_fn) const; + +public: + SimplyPrint(DynamicPrintConfig* config); + ~SimplyPrint() override = default; + + const char* get_name() const override { return "SimplyPrint"; } + bool can_test() const override { return true; } + bool has_auto_discovery() const override { return false; } + bool is_cloud() const override { return true; } + std::string get_host() const override { return "https://simplyprint.io"; } + + GUI::OAuthParams get_oauth_params() const; + void save_oauth_credential(const GUI::OAuthResult& cred) const; + + wxString get_test_ok_msg() const override; + wxString get_test_failed_msg(wxString& msg) const override; + bool test(wxString& curl_msg) const override; + PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::None; } + bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override; + bool is_logged_in() const override { return !cred.empty(); } + void log_out() const override; +}; +} // namespace Slic3r + +#endif \ No newline at end of file