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