From 0a8fce091349f763eef071413549e575830978ae Mon Sep 17 00:00:00 2001 From: Lam Wei Lun Date: Mon, 5 Oct 2026 18:01:13 +0800 Subject: [PATCH] Add support for printing gcode files in the Storage tab --- src/slic3r/GUI/ImageGrid.cpp | 6 +-- src/slic3r/GUI/OrcaFilesPanel.cpp | 48 ++++++++++++++----- src/slic3r/GUI/Printer/IFileGridModel.h | 4 +- .../GUI/Printer/RemoteFileGridModel.cpp | 28 +++++++---- src/slic3r/GUI/Printer/RemoteFileGridModel.h | 1 - src/slic3r/Utils/OrcaPrinterAgent.cpp | 5 +- 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/ImageGrid.cpp b/src/slic3r/GUI/ImageGrid.cpp index 569c0a681e..5ef8524ef1 100644 --- a/src/slic3r/GUI/ImageGrid.cpp +++ b/src/slic3r/GUI/ImageGrid.cpp @@ -275,7 +275,7 @@ void Slic3r::GUI::ImageGrid::UpdateFocusRange() bool Slic3r::GUI::ImageGrid::hasSecondAction(const FileGridCard &card) const { return m_show_download || card.downloading || - (m_model->GetFileType() == FileGridType::Model && m_model->supports_print_action()); + (m_model->GetFileType() == FileGridType::Model && card.printable); } std::pair Slic3r::GUI::ImageGrid::HitTest(wxPoint const &pt) @@ -308,7 +308,7 @@ std::pair Slic3r::GUI::ImageGrid::HitTest(wxPoint const &pt) const FileGridCard &card = m_model->GetFile(index); int btn = card.downloading && card.download_progress >= 0 ? 3 : 2; if (m_model->GetFileType() == FileGridType::Model) { - if (m_show_download && m_model->supports_print_action()) + if (m_show_download && card.printable) btn = 3; hover_rect.y -= m_content_rect.GetHeight() * 64 / 264; } @@ -686,7 +686,7 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind thirdAction = wxString::Format(L"%d%%...", progress); } } - if (m_model->GetFileType() == FileGridType::Model && m_model->supports_print_action()) { + if (m_model->GetFileType() == FileGridType::Model && card.printable) { if (secondAction != _L("Play")) thirdAction = secondAction; secondAction = _L_CONTEXT("Print", "Verb"); diff --git a/src/slic3r/GUI/OrcaFilesPanel.cpp b/src/slic3r/GUI/OrcaFilesPanel.cpp index 906c076250..6a5cda0016 100644 --- a/src/slic3r/GUI/OrcaFilesPanel.cpp +++ b/src/slic3r/GUI/OrcaFilesPanel.cpp @@ -10,6 +10,8 @@ #include "Widgets/Label.hpp" #include "Widgets/StaticBox.hpp" #include "Widgets/StateColor.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" +#include "slic3r/Utils/bambu_networking.hpp" #include #include @@ -102,8 +104,7 @@ OrcaFilesPanel::OrcaFilesPanel(wxWindow* parent) }); m_image_grid->Bind(EVT_ITEM_ACTION, [this](wxCommandEvent& event) { - if (event.GetInt() != 0) // only Delete is wired for this grid - return; + const int action = event.GetInt(); if (!m_model || m_device_id.empty()) return; @@ -116,18 +117,39 @@ OrcaFilesPanel::OrcaFilesPanel(wxWindow* parent) const std::string path = card.id; const std::string name = card.name; - MessageDialog dlg(this, - wxString::Format(_L("Do you want to delete the file '%s' from printer?"), from_u8(name)), - _L("Delete file"), wxYES_NO | wxICON_WARNING); - if (dlg.ShowModal() != wxID_YES) - return; - - m_model->DeleteFile(path, [this](bool ok) { - if (ok) + if (action == 0) { + MessageDialog dlg(this, + wxString::Format(_L("Do you want to delete the file '%s' from printer?"), from_u8(name)), + _L("Delete file"), wxYES_NO | wxICON_WARNING); + if (dlg.ShowModal() != wxID_YES) return; - MessageDialog(this, _L("Failed to delete the file from printer."), _L("Delete file"), - wxOK | wxICON_ERROR).ShowModal(); - }); + + m_model->DeleteFile(path, [this](bool ok) { + if (ok) + return; + MessageDialog(this, _L("Failed to delete the file from printer."), _L("Delete file"), + wxOK | wxICON_ERROR).ShowModal(); + }); + } else if (action == 1) { + // Print is .gcode-only for now; .gcode.3mf support to be added later. + MessageDialog dlg(this, + wxString::Format(_L("Do you want to print the file '%s' from printer?"), from_u8(name)), + _L("Print file"), wxYES_NO | wxICON_WARNING); + if (dlg.ShowModal() != wxID_YES) + return; + + NetworkAgent* agent = wxGetApp().getAgent(); + if (!agent) + return; + + PrintParams params; + params.dev_id = m_device_id; + params.dst_file = path; + params.connection_type = "lan"; + if (agent->start_sdcard_print(params, {}, {}) != BAMBU_NETWORK_SUCCESS) + MessageDialog(this, _L("Failed to start the print job."), _L("Print file"), + wxOK | wxICON_ERROR).ShowModal(); + } }); } diff --git a/src/slic3r/GUI/Printer/IFileGridModel.h b/src/slic3r/GUI/Printer/IFileGridModel.h index 9abc4e3cc1..6f9ea775b7 100644 --- a/src/slic3r/GUI/Printer/IFileGridModel.h +++ b/src/slic3r/GUI/Printer/IFileGridModel.h @@ -27,6 +27,7 @@ struct FileGridCard { bool downloading = false; int download_progress = 0; // -1 waiting, <0 failed, 0..100 progress bool selected = false; + bool printable = true; wxBitmap thumbnail; }; @@ -48,9 +49,6 @@ public: // When true, the thumbnail is fitted inside the tile instead of stretched to fill it. virtual bool preserve_thumbnail_aspect() const { return false; } - // When false, a Model tile omits the Print action. Default keeps Bambu behavior. - virtual bool supports_print_action() const { return true; } - virtual size_t GetIndexAtTime(time_t time) const = 0; virtual size_t EnterSubGroup(size_t index) = 0; diff --git a/src/slic3r/GUI/Printer/RemoteFileGridModel.cpp b/src/slic3r/GUI/Printer/RemoteFileGridModel.cpp index 26c71a200a..851c288aea 100644 --- a/src/slic3r/GUI/Printer/RemoteFileGridModel.cpp +++ b/src/slic3r/GUI/Printer/RemoteFileGridModel.cpp @@ -34,18 +34,27 @@ std::string durationString(long duration) return boost::regex_replace(time.str(), rx, ""); } +// Case-insensitive suffix test shared by the file filters below. +bool ends_with_ci(const std::string& name, const std::string& suffix) +{ + return name.size() >= suffix.size() && + std::equal(suffix.rbegin(), suffix.rend(), name.rbegin(), + [](char a, char b) { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); + }); +} + // .gcode / .3mf only, case-insensitive; a .gcode.3mf name matches via the .3mf suffix. bool is_displayable_file(const std::string& name) { - auto ends_with_ci = [&name](const std::string& suffix) { - return name.size() >= suffix.size() && - std::equal(suffix.rbegin(), suffix.rend(), name.rbegin(), - [](char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - }); - }; - return ends_with_ci(".gcode") || ends_with_ci(".3mf"); + return ends_with_ci(name, ".gcode") || ends_with_ci(name, ".3mf"); +} + +// Only .gcode is printable today; .gcode.3mf support to be added later. +bool is_printable_gcode(const std::string& name) +{ + return ends_with_ci(name, ".gcode"); } } @@ -127,6 +136,7 @@ void RemoteFileGridModel::onFilesLoaded(std::uint64_t request, const std::string card.id = file.path; card.name = file.name; card.time = static_cast(file.modified); + card.printable = is_printable_gcode(file.name); m_cards.emplace_back(std::move(card)); } buildGroups(); diff --git a/src/slic3r/GUI/Printer/RemoteFileGridModel.h b/src/slic3r/GUI/Printer/RemoteFileGridModel.h index abd84701de..82cccb7412 100644 --- a/src/slic3r/GUI/Printer/RemoteFileGridModel.h +++ b/src/slic3r/GUI/Printer/RemoteFileGridModel.h @@ -49,7 +49,6 @@ public: int GetLastError() const override; void DownloadCheckFiles(const std::string& path) override; bool preserve_thumbnail_aspect() const override { return true; } - bool supports_print_action() const override { return false; } // Delete `path` from the printer; `done` reports success on the UI thread. void DeleteFile(const std::string& path, std::function done); diff --git a/src/slic3r/Utils/OrcaPrinterAgent.cpp b/src/slic3r/Utils/OrcaPrinterAgent.cpp index 72094f4a78..0ba810f998 100644 --- a/src/slic3r/Utils/OrcaPrinterAgent.cpp +++ b/src/slic3r/Utils/OrcaPrinterAgent.cpp @@ -2239,7 +2239,10 @@ int OrcaPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn up // dst_file, when set, names a file already on the printer (print-from-SD flow); // otherwise start what start_send_gcode_to_sdcard just uploaded to `gcodes`. - const std::string target = params.dst_file.empty() ? remote_gcode_name(params) : fs::path(params.dst_file).filename().string(); + // Keep the gcodes-relative path with subfolders preserved, minus a leading '/'. + std::string target = params.dst_file.empty() ? remote_gcode_name(params) : params.dst_file; + if (!target.empty() && target.front() == '/') + target.erase(target.begin()); // Per-print mapping. A mapped print is refused when the connector did not // advertise filament_mapping: the GUI send gates make this visible first, and