diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index ee5feb66d8..c4f58e3271 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -351,6 +351,20 @@ bool PresetBundle::use_bbl_network() 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 f88c39c210..976e654074 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -92,7 +92,10 @@ class PresetBundle 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); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7af8f5c7b9..8669592f82 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -549,7 +549,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("Orca Slicer can upload G-code files to a printer host. This field should contain " diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 0e98e85ea6..ca8c7a1663 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -901,23 +901,27 @@ 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()); - // Orca: skip post-processing scripts for BBL printers as we have run them already in finalize_gcode() - // todo: do we need to copy the file? + 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()); + // Orca: skip post-processing scripts for BBL printers as we have run them already in finalize_gcode() + // todo: do we need to copy the file? - // 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). - if (!m_fff_print->is_BBL_printer()) { - 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; - } + // 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). + if (!m_fff_print->is_BBL_printer()) { + 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()); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 414a4b2d5e..0689c5a7d3 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1615,7 +1615,7 @@ wxBoxSizer* MainFrame::create_side_tools() SidePopup* p = new SidePopup(this); if (wxGetApp().preset_bundle - && !wxGetApp().preset_bundle->use_bbl_network()) { + && !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); @@ -1715,10 +1715,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); + } if (enable_multi_machine) { SideButton* print_multi_machine_btn = new SideButton(p, _L("Send to Multi-device"), ""); print_multi_machine_btn->SetCornerRadius(0); @@ -3636,14 +3658,14 @@ void MainFrame::load_printer_url(wxString url, wxString apikey) void MainFrame::load_printer_url() { PresetBundle &preset_bundle = *wxGetApp().preset_bundle; - if (preset_bundle.use_bbl_network()) + 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"); wxString apikey; - if (cfg.has("printhost_apikey") && (cfg.option>("host_type")->value == htPrusaLink || - cfg.option>("host_type")->value == htPrusaConnect)) + const auto host_type = cfg.option>("host_type")->value; + if (cfg.has("printhost_apikey") && (host_type == htPrusaLink || host_type == htPrusaConnect)) apikey = cfg.opt_string("printhost_apikey"); if (!url.empty()) { if (!url.Lower().starts_with("http")) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 621248cb11..1ec7b537e3 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -130,6 +130,8 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr this->update_printhost_buttons(); if (opt_key == "printhost_port") this->update_ports(); + if (opt_key == "bbl_use_print_host_webui") + this->update_webui(); }; m_optgroup->append_single_option_line("host_type"); @@ -253,6 +255,19 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr 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"); @@ -402,6 +417,30 @@ void PhysicalPrinterDialog::update_ports() { } } +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)); @@ -504,7 +543,8 @@ void PhysicalPrinterDialog::update(bool printer_change) m_optgroup->show_field("host_type"); m_optgroup->enable_field("print_host"); - m_optgroup->enable_field("print_host_webui"); + 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) @@ -516,21 +556,12 @@ void PhysicalPrinterDialog::update(bool printer_change) const auto current_host = temp->GetValue(); if (current_host == L"https://connect.prusa3d.com" || current_host == L"https://app.obico.io" || - current_host == "https://simplyprint.io") { + current_host == "https://simplyprint.io" || current_host == "https://simplyprint.io/panel") { temp->SetValue(wxString()); m_config->opt_string("print_host") = ""; } } } - 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) { - const auto current_host = temp->GetValue(); - if (current_host == "https://simplyprint.io/panel") { - temp->SetValue(wxString()); - m_config->opt_string("print_host_webui") = ""; - } - } - } if (opt->value == htPrusaLink) { // PrusaConnect does NOT allow http digest m_optgroup->show_field("printhost_authorization_type"); AuthorizationType auth_type = m_config->option>("printhost_authorization_type")->value; @@ -548,6 +579,7 @@ void PhysicalPrinterDialog::update(bool printer_change) if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { temp->SetValue(L"https://connect.prusa3d.com"); + m_config->opt_string("print_host") = "https://connect.prusa3d.com"; } } } else if (opt->value == htObico) { // automatically show default obico address @@ -562,17 +594,33 @@ void PhysicalPrinterDialog::update(bool printer_change) 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"); + temp->SetValue("https://simplyprint.io/panel"); } - m_config->opt_string("print_host") = "https://simplyprint.io"; + m_config->opt_string("print_host") = "https://simplyprint.io/panel"; } - if (Field* printhost_webui_field = m_optgroup->get_field("print_host_webui"); printhost_webui_field) { - printhost_webui_field->disable(); - if (wxTextCtrl* temp = dynamic_cast(printhost_webui_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { - temp->SetValue("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"); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index 855370568f..b6faff82f9 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -63,6 +63,7 @@ class PhysicalPrinterDialog : public DPIDialog void update_printhost_buttons(); void update_printers(); void update_ports(); + 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 f27cad243c..629ba996d1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1181,12 +1181,11 @@ void Sidebar::update_all_preset_comboboxes() const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); bool is_bbl_vendor = preset_bundle.is_bbl_vendor(); - const bool use_bbl_network = preset_bundle.use_bbl_network(); auto p_mainframe = wxGetApp().mainframe; auto cfg = preset_bundle.printers.get_edited_preset().config; - if (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 @@ -1204,9 +1203,10 @@ void Sidebar::update_all_preset_comboboxes() else { if (!url.Lower().starts_with("http")) url = wxString::Format("http://%s", url); - if (cfg.has("printhost_apikey")) + const auto host_type = cfg.option>("host_type")->value; + if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint)) apikey = cfg.opt_string("printhost_apikey"); - print_btn_type = MainFrame::PrintSelectType::eSendGcode; + print_btn_type = preset_bundle.is_bbl_vendor() ? MainFrame::PrintSelectType::ePrintPlate : MainFrame::PrintSelectType::eSendGcode; } p_mainframe->load_printer_url(url, apikey); @@ -1250,7 +1250,7 @@ void Sidebar::update_all_preset_comboboxes() p->combo_printer->update(); // Orca:: show device tab based on vendor type - p_mainframe->show_device(use_bbl_network); + p_mainframe->show_device(preset_bundle.use_bbl_device_tab()); p_mainframe->m_tabpanel->SetSelection(p_mainframe->m_tabpanel->GetSelection()); } @@ -7069,12 +7069,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&) @@ -7104,7 +7110,7 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e) sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview; update_sidebar(); int old_sel = e.GetOldSelection(); - if (wxGetApp().preset_bundle && wxGetApp().preset_bundle->use_bbl_network() && new_sel == MainFrame::tpMonitor) { + if (wxGetApp().preset_bundle && wxGetApp().preset_bundle->use_bbl_device_tab() && new_sel == MainFrame::tpMonitor) { if (!wxGetApp().getAgent()) { e.Veto(); BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins") % old_sel % new_sel; @@ -7159,12 +7165,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&) @@ -12332,7 +12344,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(); @@ -12344,6 +12356,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 { @@ -12362,6 +12376,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; @@ -12383,8 +12400,11 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn) } } - PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names); + auto config = get_app_config(); + PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names, config->get_bool("open_device_tab_post_upload")); 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(); @@ -12397,6 +12417,19 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn) return; } + 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)); } } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ac2591a156..66d63fd54a 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -431,7 +431,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); diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 338b60eae0..14a5866635 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -38,7 +38,7 @@ static const char *CONFIG_KEY_PATH = "printhost_path"; static const char *CONFIG_KEY_GROUP = "printhost_group"; static const char* CONFIG_KEY_STORAGE = "printhost_storage"; -PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage_paths, const wxArrayString& storage_names) +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage_paths, const wxArrayString& storage_names, bool switch_to_device_tab) : MsgDialog(static_cast(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), 0) // Set style = 0 to avoid default creation of the "OK" button. // All buttons will be added later in this constructor , txt_filename(new wxTextCtrl(this, wxID_ANY)) @@ -46,6 +46,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo , combo_storage(storage_names.GetCount() > 1 ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, storage_names, wxCB_READONLY) : nullptr) , post_upload_action(PrintHostPostUploadAction::None) , m_paths(storage_paths) + , m_switch_to_device_tab(switch_to_device_tab) { #ifdef __APPLE__ txt_filename->OSXDisableAllSmartSubstitutions(); @@ -97,6 +98,22 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo txt_filename->SetValue(recent_path); + 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); + if (size_t extension_start = recent_path.find_last_of('.'); extension_start != std::string::npos) m_valid_suffix = recent_path.substr(extension_start); // .gcode suffix control diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index b3f5504050..8befa21d98 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -26,11 +26,12 @@ namespace GUI { class PrintHostSendDialog : public GUI::MsgDialog { public: - PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage_paths, const wxArrayString& storage_names); + PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage_paths, const wxArrayString& storage_names, bool switch_to_device_tab); boost::filesystem::path filename() const; PrintHostPostUploadAction post_action() const; std::string group() const; std::string storage() const; + bool switch_to_device_tab() const {return m_switch_to_device_tab;} virtual void EndModal(int ret) override; private: @@ -41,6 +42,7 @@ class PrintHostSendDialog : public GUI::MsgDialog wxString m_valid_suffix; wxString m_preselected_storage; wxArrayString m_paths; + bool m_switch_to_device_tab; }; diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 88d9acf934..995bf19374 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -86,6 +86,13 @@ 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; +}; + struct Http::priv { enum { @@ -103,7 +110,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; @@ -130,7 +137,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 +261,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 +305,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 +314,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(form_file{{path, std::ios::in | std::ios::binary}, 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 ); @@ -587,9 +609,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; } @@ -607,15 +629,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 1218a562a6..51a49d44e0 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -121,15 +121,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 b6dff85052..f88dceb14a 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -21,6 +21,7 @@ #include "MKS.hpp" #include "ESP3D.hpp" #include "../GUI/PrintHostDialogs.hpp" +#include "../GUI/MainFrame.hpp" #include "Obico.hpp" #include "Flashforge.hpp" #include "SimplyPrint.hpp" @@ -314,6 +315,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 50699cb4d8..83bd730e42 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -29,6 +29,7 @@ ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction); struct PrintHostUpload { + bool use_3mf; boost::filesystem::path source_path; boost::filesystem::path upload_path; @@ -85,6 +86,7 @@ struct PrintHostJob { PrintHostUpload upload_data; std::unique_ptr printhost; + bool switch_to_device_tab{false}; bool cancelled = false; PrintHostJob() {} @@ -92,6 +94,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) {} @@ -103,7 +106,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 index 2fc72fb9ef..d3f9ddc083 100644 --- a/src/slic3r/Utils/SimplyPrint.cpp +++ b/src/slic3r/Utils/SimplyPrint.cpp @@ -9,17 +9,36 @@ #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 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 = "simplyprintorcaslicer"; 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 = "https://simplyprint.io/api/oauth2/Token"; +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) { @@ -57,6 +76,9 @@ static std::string url_encode(const std::vectoruse_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_bool("open_device_tab_post_upload"); +} + SimplyPrint::SimplyPrint(DynamicPrintConfig* config) { cred_file = (boost::filesystem::path(data_dir()) / OAUTH_CREDENTIAL_PATH).make_preferred().string(); @@ -87,7 +122,7 @@ GUI::OAuthParams SimplyPrint::get_oauth_params() const {"code_challenge", code_challenge}, {"code_challenge_method", "S256"}, }; - const auto login_url = (boost::format("https://simplyprint.io/panel/oauth2/authorize?%s") % url_encode(query_parameters)).str(); + const auto login_url = (boost::format(URL_BASE_HOME"/panel/oauth2/authorize?%s") % url_encode(query_parameters)).str(); return GUI::OAuthParams{ login_url, @@ -96,8 +131,8 @@ GUI::OAuthParams SimplyPrint::get_oauth_params() const CALLBACK_URL, CLIENT_SCOPES, RESPONSE_TYPE, - "https://simplyprint.io/login-success", - "https://simplyprint.io/login-success", + URL_BASE_HOME"/login-success", + URL_BASE_HOME"/login-success", TOKEN_URL, verification_code, state, @@ -226,7 +261,7 @@ bool SimplyPrint::test(wxString& curl_msg) const return do_api_call( [](bool is_retry) { - auto http = Http::get("https://api.simplyprint.io/oauth2/TokenInfo"); + auto http = Http::get(URL_BASE_API"/oauth2/TokenInfo"); http.header("Accept", "application/json"); return http; }, @@ -241,30 +276,31 @@ bool SimplyPrint::test(wxString& curl_msg) const }); } -bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const +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 (cred.find("access_token") == cred.end()) { - error_fn(_L("SimplyPrint account not linked. Go to Connect options to set it up.")); - return false; - } - - // If file is over 100 MB, fail - if (boost::filesystem::file_size(upload_data.source_path) > 104857600ull) { - error_fn(_L("File size exceeds the 100MB upload limit. Please upload your file through the panel.")); + 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; } - const auto filename = upload_data.upload_path.filename().string(); - return do_api_call( - [&upload_data, &prorgess_fn, &filename](bool is_retry) { - auto http = Http::post("https://simplyprint.io/api/files/TempUpload"); - http.form_add_file("file", upload_data.source_path.string(), filename) - .on_progress([&prorgess_fn](Http::Progress progress, bool& cancel) { prorgess_fn(std::move(progress), cancel); }); + [&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](std::string body, unsigned status) { + [&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 @@ -283,8 +319,15 @@ bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Er const std::string uuid = j["uuid"]; // Launch external browser for file importing after uploading - const auto url = "https://simplyprint.io/panel?" + url_encode({{"import", "tmp:" + uuid}, {"filename", filename}}); - wxLaunchDefaultBrowser(url); + 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; }, @@ -295,4 +338,155 @@ bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Er }); } +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 + const auto j = nlohmann::json::parse(body, nullptr, false, true); + if (j.is_discarded()) { + BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid or no JSON data on ChunkReceive: " << body; + error_fn(_L("Unknown error")); + return false; + } + + if (j.find("id") == j.end() || j.find("delete_token") == j.end()) { + BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid or no JSON data on ChunkReceive: " << body; + error_fn(_L("Unknown error")); + return false; + } + + const unsigned long id = j["id"]; + + chunk_id = std::to_string(id); + delete_token = j["delete_token"]; + } + 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, InfoFn info_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 index 2b56764d7c..3804324724 100644 --- a/src/slic3r/Utils/SimplyPrint.hpp +++ b/src/slic3r/Utils/SimplyPrint.hpp @@ -15,10 +15,37 @@ class SimplyPrint : public PrintHost 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;