From 615deefa8763751258717ed1a5e7d629db2245fc Mon Sep 17 00:00:00 2001 From: SoftFever Date: Thu, 16 Jul 2026 17:52:28 +0800 Subject: [PATCH] Check timelapse storage space before sending a print --- src/slic3r/GUI/DeviceCore/DevStorage.cpp | 27 +++ src/slic3r/GUI/DeviceCore/DevStorage.h | 7 + src/slic3r/GUI/DeviceManager.cpp | 40 +++- src/slic3r/GUI/DeviceManager.hpp | 11 ++ src/slic3r/GUI/PrePrintChecker.cpp | 53 +++++- src/slic3r/GUI/PrePrintChecker.hpp | 10 +- src/slic3r/GUI/SelectMachine.cpp | 229 ++++++++++++++++++++++- src/slic3r/GUI/SelectMachine.hpp | 12 ++ 8 files changed, 384 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/DeviceCore/DevStorage.cpp b/src/slic3r/GUI/DeviceCore/DevStorage.cpp index 14623b0958..de4d007e46 100644 --- a/src/slic3r/GUI/DeviceCore/DevStorage.cpp +++ b/src/slic3r/GUI/DeviceCore/DevStorage.cpp @@ -27,8 +27,35 @@ DevStorage::SdcardState Slic3r::DevStorage::set_sdcard_state(int state) } else { system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD; } + + // parse timelapse storage space from cam push data + try { + if (print_json.contains("device") && print_json["device"].is_object()) { + const auto& device_json = print_json["device"]; + if (device_json.contains("cam") && device_json["cam"].is_object()) { + const auto& cam_data = device_json["cam"]; + if (cam_data.contains("tl_internal_free_kb")) + system->tl_internal_free_kb = cam_data["tl_internal_free_kb"].get(); + if (cam_data.contains("tl_internal_total_kb")) + system->tl_internal_total_kb = cam_data["tl_internal_total_kb"].get(); + if (cam_data.contains("tl_external_free_kb")) + system->tl_external_free_kb = cam_data["tl_external_free_kb"].get(); + if (cam_data.contains("tl_external_total_kb")) + system->tl_external_total_kb = cam_data["tl_external_total_kb"].get(); + } + } + } catch (...) {} } } +bool DevStorage::is_timelapse_storage_low(const std::string& storage) const +{ + const int THRESHOLD_KB = 20480; // 20MB + if (storage == "internal") + return tl_internal_free_kb >= 0 && tl_internal_free_kb < THRESHOLD_KB; + else + return tl_external_free_kb >= 0 && tl_external_free_kb < THRESHOLD_KB; +} + } // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevStorage.h b/src/slic3r/GUI/DeviceCore/DevStorage.h index e64be278b7..d3c03e7fe6 100644 --- a/src/slic3r/GUI/DeviceCore/DevStorage.h +++ b/src/slic3r/GUI/DeviceCore/DevStorage.h @@ -28,9 +28,16 @@ public: static void ParseV1_0(const json &print_json, DevStorage *system); + bool is_timelapse_storage_low(const std::string& storage) const; + private: MachineObject *m_owner; SdcardState m_sdcard_state { NO_SDCARD }; + // timelapse storage space info (from device push cam data) + int tl_internal_free_kb { -1 }; + int tl_internal_total_kb { -1 }; + int tl_external_free_kb { -1 }; + int tl_external_total_kb { -1 }; }; } // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 7508750fe1..bbaf52774f 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -1208,7 +1208,7 @@ bool MachineObject::canEnableTimelapse(wxString &error_message) const return true; } - if (m_storage->get_sdcard_state() != DevStorage::SdcardState::HAS_SDCARD_NORMAL) { + if (m_storage->get_sdcard_state() != DevStorage::SdcardState::HAS_SDCARD_NORMAL && !m_has_timelapse_kit) { if (m_storage->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD) { error_message = _L("Timelapse is not supported while the storage does not exist."); } else if (m_storage->get_sdcard_state() == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL) { @@ -2222,6 +2222,32 @@ int MachineObject::command_ack_proceed(json& proceed) { return this->publish_json(j); } +bool MachineObject::is_timelapse_storage_low(const std::string& storage) const +{ + return m_storage && m_storage->is_timelapse_storage_low(storage); +} + +int MachineObject::command_ipcam_check_timelapse_storage(const std::string& storage, int total_layer) +{ + json j; + j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["camera"]["command"] = "ipcam_get_media_info"; + j["camera"]["sub_command"] = "is_timelapse_storage_enough"; + j["camera"]["storage"] = storage; + j["camera"]["total_layer"] = total_layer; + return this->publish_json(j); +} + +int MachineObject::command_ipcam_delete_oldest_timelapse(const std::string& storage, int total_layer) +{ + json j; + j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["camera"]["command"] = "ipcam_delete_oldest_timelapse"; + j["camera"]["storage"] = storage; + j["camera"]["total_layer"] = total_layer; + return this->publish_json(j); +} + int MachineObject::command_purification_disable() { json j; @@ -4409,6 +4435,17 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ this->camera_resolution = j["camera"]["resolution"].get(); BOOST_LOG_TRIVIAL(info) << "ack of resolution = " << camera_resolution; } + } else if (j["camera"]["command"].get() == "ipcam_get_media_info") { + if (j["camera"].contains("sub_command") && + j["camera"]["sub_command"].get() == "is_timelapse_storage_enough") { + timelapse_storage_check_result = j["camera"]["result"].get(); + timelapse_storage_is_enough = j["camera"].value("is_enough", true); + timelapse_storage_file_count = j["camera"].value("file_count", 0); + timelapse_storage_check_done = true; + BOOST_LOG_TRIVIAL(info) << "timelapse storage check: result=" << timelapse_storage_check_result + << " is_enough=" << timelapse_storage_is_enough + << " file_count=" << timelapse_storage_file_count; + } } } } @@ -5182,6 +5219,7 @@ void MachineObject::parse_new_info(json print) if (!aux.empty()) { m_storage->set_sdcard_state(get_flag_bits(aux, 12, 2)); + m_has_timelapse_kit = (get_flag_bits(aux, 26, 1) == 1); } /*stat*/ diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 98ab9947e3..b4d5e15571 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -558,6 +559,7 @@ public: bool file_model_download{false}; bool virtual_camera{false}; + bool m_has_timelapse_kit{false}; bool xcam_ai_monitoring{ false }; bool xcam_disable_ai_detection_display{false}; @@ -634,6 +636,12 @@ public: bool is_support_airprinting_detection{false}; bool is_support_idelheadingprotect_detection{false}; + // timelapse storage check result (temp state from MQTT response) + std::atomic timelapse_storage_check_done { false }; + int timelapse_storage_check_result { -1 }; + bool timelapse_storage_is_enough { true }; + int timelapse_storage_file_count { 0 }; + // fun2 bool is_support_print_with_emmc{false}; bool is_support_remote_dry = false; @@ -706,6 +714,7 @@ public: /* quick check*/ bool canEnableTimelapse(wxString& error_message) const; + bool is_timelapse_storage_low(const std::string& storage) const; /* command commands */ int command_get_version(bool with_retry = true); @@ -810,6 +819,8 @@ public: int command_ipcam_record(bool on_off); int command_ipcam_timelapse(bool on_off); int command_ipcam_resolution_set(std::string resolution); + int command_ipcam_check_timelapse_storage(const std::string& storage, int total_layer); + int command_ipcam_delete_oldest_timelapse(const std::string& storage, int total_layer); int command_xcam_control(std::string module_name, bool on_off, std::string lvl = ""); //refine printer diff --git a/src/slic3r/GUI/PrePrintChecker.cpp b/src/slic3r/GUI/PrePrintChecker.cpp index 866c9bd15d..8ea1800b1e 100644 --- a/src/slic3r/GUI/PrePrintChecker.cpp +++ b/src/slic3r/GUI/PrePrintChecker.cpp @@ -162,6 +162,49 @@ void PrePrintChecker::add(PrintDialogStatus state, wxString msg, wxString tip, c } } +// Orca: minimal callback-link variant of add(): stores an internal click action +// (link_callback) rendered as a trailing link instead of the wiki-url launcher. +void PrePrintChecker::add_with_link(PrintDialogStatus state, wxString msg, wxString link_label, std::function link_callback) +{ + prePrintInfo info; + + if (is_error(state)) { + info.level = prePrintInfoLevel::Error; + } else if (is_warning(state)) { + info.level = prePrintInfoLevel::Warning; + } else { + info.level = prePrintInfoLevel::Normal; + } + + if (is_error_printer(state)) { + info.type = prePrintInfoType::Printer; + } else if (is_error_filament(state)) { + info.type = prePrintInfoType::Filament; + } else if (is_warning_printer(state)) { + info.type = prePrintInfoType::Printer; + } else if (is_warning_filament(state)) { + info.type = prePrintInfoType::Filament; + } + + info.msg = msg; + info.link_label = link_label; + info.link_callback = link_callback; + + switch (info.type) { + case prePrintInfoType::Filament: + if (std::find(filamentList.begin(), filamentList.end(), info) == filamentList.end()) { + filamentList.push_back(info); + } + break; + case prePrintInfoType::Printer: + if (std::find(printerList.begin(), printerList.end(), info) == printerList.end()) { + printerList.push_back(info); + } + break; + default: break; + } +} + //void PrePrintMsgBoard::add(const wxString &msg, const wxString &tips, bool is_error) //{ @@ -230,7 +273,15 @@ bool PrinterMsgPanel::UpdateInfos(const std::vector& infos) label->SetForegroundColour(_GetLabelColour(info)); - if (info.wiki_url.empty()) + if (!info.link_label.empty()) + { + // Orca: internal callback link (e.g. "Clean up files") instead of a wiki url + label->SetLabel(info.msg + " " + info.link_label); + label->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) { SetCursor(wxCURSOR_HAND); }); + label->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) { SetCursor(wxCURSOR_ARROW); }); + label->Bind(wxEVT_LEFT_DOWN, [info](wxMouseEvent& event) { if (info.link_callback) info.link_callback(); }); + } + else if (info.wiki_url.empty()) { label->SetLabel(info.msg); } diff --git a/src/slic3r/GUI/PrePrintChecker.hpp b/src/slic3r/GUI/PrePrintChecker.hpp index 199f97a1a0..b8a69912ca 100644 --- a/src/slic3r/GUI/PrePrintChecker.hpp +++ b/src/slic3r/GUI/PrePrintChecker.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_GUI_PRE_PRINT_CHECK_hpp_ #define slic3r_GUI_PRE_PRINT_CHECK_hpp_ +#include #include #include "Widgets/Label.hpp" namespace Slic3r { namespace GUI { @@ -23,13 +24,17 @@ struct prePrintInfo wxString msg; wxString tips; wxString wiki_url; + wxString link_label; // optional: clickable text appended after msg + std::function link_callback; // optional: internal action for link_label click int index; public: bool operator==(const prePrintInfo& other) const { return level == other.level && type == other.type && msg == other.msg && tips == other.tips && - wiki_url == other.wiki_url && index == other.index; + wiki_url == other.wiki_url && link_label == other.link_label && + index == other.index; + // link_callback excluded: std::function is not comparable } }; @@ -94,6 +99,7 @@ enum PrintDialogStatus : unsigned int { PrintStatusPrinterWarningBegin, PrintStatusTimelapseNoSdcard, PrintStatusTimelapseWarning, + PrintStatusTimelapseStorageLow, PrintStatusMixAmsAndVtSlotWarning, PrintStatusToolHeadCoolingFanWarning, PrintStatusRackNozzleMappingWarning, @@ -143,6 +149,8 @@ public: void clear(); /*auto merge*/ void add(PrintDialogStatus state, wxString msg, wxString tip, const wxString& wiki_url); + // Orca: minimal callback-link render path instead of the full style-bitmask machinery. + void add_with_link(PrintDialogStatus state, wxString msg, wxString link_label, std::function link_callback); static ::std::string get_print_status_info(PrintDialogStatus status); wxString get_pre_state_msg(PrintDialogStatus status); diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index ee6558a822..b7fa8dc078 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -1883,7 +1883,7 @@ bool SelectMachineDialog::check_sdcard_for_timelpase(MachineObject* obj) // must set to a status if return true if (m_checkbox_list["timelapse"]->IsShown() && m_checkbox_list["timelapse"]->getValue() == "on") { - if (obj->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD) { + if (obj->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD && !obj->m_has_timelapse_kit && !obj->is_support_internal_timelapse) { show_status(PrintDialogStatus::PrintStatusTimelapseNoSdcard); return true; } @@ -2083,6 +2083,9 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vectoron_send_print(); + // if machine supports internal timelapse and timelapse is on, check storage first + if (obj_->is_support_internal_timelapse && + m_checkbox_list["timelapse"]->IsShown() && + m_checkbox_list["timelapse"]->getValue() == "on" && + !m_timelapse_storage.empty()) + { + start_timelapse_storage_check(obj_); + } else { + this->on_send_print(); + } } } @@ -2881,6 +2893,9 @@ void SelectMachineDialog::show_timelapse_folder_popup() m_timelapse_storage = val; update_timelapse_folder_btn_icon(); if (m_timelapse_storage_popup) m_timelapse_storage_popup->Dismiss(); + DeviceManager* dev = wxGetApp().getDeviceManager(); + MachineObject* obj = dev ? dev->get_selected_machine() : nullptr; + if (obj) check_timelapse_storage_warning(obj); }; radio->Bind(wxEVT_LEFT_DOWN, on_select); text->Bind(wxEVT_LEFT_DOWN, on_select); @@ -2919,6 +2934,215 @@ void SelectMachineDialog::show_timelapse_folder_popup() m_timelapse_storage_popup->Popup(); } +void SelectMachineDialog::check_timelapse_storage_warning(MachineObject* obj) +{ + if (!obj || !obj->is_support_internal_timelapse) return; + if (!m_checkbox_list["timelapse"]->IsShown()) return; + if (m_checkbox_list["timelapse"]->getValue() != "on") return; + if (m_timelapse_storage.empty()) return; + + if (obj->is_timelapse_storage_low(m_timelapse_storage)) { + wxString storage_name = (m_timelapse_storage == "internal") ? _L("Internal") : _L("External"); + wxString msg = wxString::Format( + _L("%s space less than 20MB. Timelapse may not save properly. You can turn it off or"), + storage_name); + // show_status sets button state; then directly add with link callback + show_status(PrintDialogStatus::PrintStatusTimelapseStorageLow); + m_pre_print_checker.add_with_link( + PrintDialogStatus::PrintStatusTimelapseStorageLow, + msg, + _L("Clean up files"), + [this]() { navigate_to_timelapse_page(); }); + } +} + +void SelectMachineDialog::start_timelapse_storage_check(MachineObject* obj) +{ + if (!obj) { on_send_print(); return; } + + // get total layer count from the sliced result + // Orca: PrintStatistics::Mode has no per-layer time vector (unlike the reference), so + // derive the timelapse layer count from the sliced print objects instead. + m_timelapse_total_layer = 0; + if (m_print_type == PrintFromType::FROM_NORMAL) { + PartPlate* plate = m_plater->get_partplate_list().get_curr_plate(); + if (plate && plate->fff_print()) { + for (const PrintObject* po : plate->fff_print()->objects()) + m_timelapse_total_layer = std::max(m_timelapse_total_layer, (int)po->layer_count()); + } + } + + obj->timelapse_storage_check_done = false; + obj->timelapse_storage_check_result = -1; + obj->command_ipcam_check_timelapse_storage(m_timelapse_storage, m_timelapse_total_layer); + + // start polling timer + m_timelapse_check_elapsed_ms = 0; + if (!m_timelapse_check_timer) { + m_timelapse_check_timer = new wxTimer(this); + Bind(wxEVT_TIMER, &SelectMachineDialog::on_timelapse_storage_check_timer, this, m_timelapse_check_timer->GetId()); + } + m_timelapse_check_timer->Start(m_timelapse_check_interval_ms); +} + +void SelectMachineDialog::on_timelapse_storage_check_timer(wxTimerEvent& /*event*/) +{ + m_timelapse_check_elapsed_ms += m_timelapse_check_interval_ms; + + DeviceManager* dev = wxGetApp().getDeviceManager(); + MachineObject* obj = dev ? dev->get_selected_machine() : nullptr; + + bool timed_out = m_timelapse_check_elapsed_ms >= m_timelapse_check_timeout_ms; + bool done = obj && obj->timelapse_storage_check_done.load(); + + if (done || timed_out) { + m_timelapse_check_timer->Stop(); + if (timed_out && !done) { + BOOST_LOG_TRIVIAL(warning) << "timelapse storage check timed out, proceeding with print"; + on_send_print(); + return; + } + on_timelapse_storage_check_result(); + } +} + +void SelectMachineDialog::on_timelapse_storage_check_result() +{ + DeviceManager* dev = wxGetApp().getDeviceManager(); + MachineObject* obj = dev ? dev->get_selected_machine() : nullptr; + if (!obj) { on_send_print(); return; } + + // query failed -> ignore, proceed with print + if (obj->timelapse_storage_check_result != 0) { + BOOST_LOG_TRIVIAL(info) << "timelapse storage check failed (result=" << obj->timelapse_storage_check_result << "), proceeding"; + on_send_print(); + return; + } + + // space is enough -> proceed + if (obj->timelapse_storage_is_enough) { + on_send_print(); + return; + } + + // space not enough -> show dialog + show_timelapse_storage_dialog(obj); +} + +void SelectMachineDialog::show_timelapse_storage_dialog(MachineObject* obj) +{ + bool is_internal = (m_timelapse_storage == "internal"); + bool has_video_files = obj->timelapse_storage_file_count >= 0; + // internal: 2 buttons (Confirm & Print, Cancel Timelapse) + // external + has files: 3 buttons (Confirm & Print, Cancel Timelapse, Clean Up) + // external + no files: 1 button (Cancel Timelapse only) + bool show_confirm_btn = is_internal || has_video_files; + bool show_cleanup_btn = !is_internal && has_video_files; + + wxString body_text; + if (is_internal) + body_text = _L("Low internal storage. This timelapse will overwrite the oldest video files."); + else if (has_video_files) + body_text = _L("Low external storage. This timelapse will overwrite the oldest video files."); + else + body_text = _L("Insufficient external storage for time-lapse photography. Connect to computer to delete files, or use a larger memory card."); + + wxDialog dlg(this, wxID_ANY, _L("Storage Space Not Enough"), + wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE); + dlg.SetBackgroundColour(*wxWHITE); + + auto* main_sizer = new wxBoxSizer(wxVERTICAL); + + // warning icon + text row + auto* msg_sizer = new wxBoxSizer(wxHORIZONTAL); + auto* warn_bmp = new wxStaticBitmap(&dlg, wxID_ANY, + create_scaled_bitmap("obj_warning", &dlg, 16), wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16))); + auto* msg_label = new Label(&dlg, body_text); + msg_label->SetFont(Label::Body_14); + msg_label->SetForegroundColour(wxColour(0x33, 0x33, 0x33)); + msg_label->Wrap(FromDIP(340)); + msg_sizer->Add(warn_bmp, 0, wxALIGN_TOP | wxRIGHT, FromDIP(6)); + msg_sizer->Add(msg_label, 1, wxEXPAND); + main_sizer->Add(msg_sizer, 0, wxALL | wxEXPAND, FromDIP(20)); + + auto* btn_sizer = new wxBoxSizer(wxVERTICAL); + + const int ID_CLEANUP = wxID_HIGHEST + 1; // distinct from wxID_CANCEL (X button) + + // use int id to distinguish choices: wxID_OK=confirm, wxID_NO=cancel_tl, ID_CLEANUP=cleanup + if (show_confirm_btn) { + auto* btn_confirm = new Button(&dlg, _L("Confirm & Print")); + // Orca: use the accent green rather than the reference's hard-coded confirm colour. + StateColor confirm_bg(std::pair(wxColour(0, 150, 136), StateColor::Normal)); + btn_confirm->SetBackgroundColor(confirm_bg); + btn_confirm->SetTextColor(StateColor(std::pair(*wxWHITE, StateColor::Normal))); + btn_confirm->Bind(wxEVT_BUTTON, [&dlg](wxCommandEvent&) { dlg.EndModal(wxID_OK); }); + btn_sizer->Add(btn_confirm, 0, wxEXPAND | wxBOTTOM, FromDIP(8)); + } + + auto* btn_cancel_tl = new Button(&dlg, _L("Cancel Timelapse & Print")); + if (!show_confirm_btn) { + // Orca: use the accent green rather than the reference's hard-coded confirm colour. + StateColor cancel_bg(std::pair(wxColour(0, 150, 136), StateColor::Normal)); + btn_cancel_tl->SetBackgroundColor(cancel_bg); + btn_cancel_tl->SetTextColor(StateColor(std::pair(*wxWHITE, StateColor::Normal))); + } + btn_cancel_tl->Bind(wxEVT_BUTTON, [&dlg](wxCommandEvent&) { dlg.EndModal(wxID_NO); }); + btn_sizer->Add(btn_cancel_tl, 0, wxEXPAND | (show_cleanup_btn ? wxBOTTOM : 0), FromDIP(8)); + + if (show_cleanup_btn) { + auto* btn_cleanup = new Button(&dlg, _L("Clean Up")); + btn_cleanup->Bind(wxEVT_BUTTON, [&dlg, ID_CLEANUP](wxCommandEvent&) { dlg.EndModal(ID_CLEANUP); }); + btn_sizer->Add(btn_cleanup, 0, wxEXPAND); + } + + main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, FromDIP(20)); + dlg.SetSizer(main_sizer); + dlg.Fit(); + dlg.CenterOnParent(); + + // ShowModal returns after the dialog closes; handle the action outside the modal stack. + // wxID_CANCEL is returned when the user clicks X (close button) -> do nothing in that case. + int result = dlg.ShowModal(); + + if (result == wxID_OK) { + // Confirm & Print + obj->command_ipcam_delete_oldest_timelapse(m_timelapse_storage, m_timelapse_total_layer); + on_send_print(); + } else if (result == wxID_NO) { + // Cancel Timelapse & Print + m_checkbox_list["timelapse"]->setValue("off"); + on_send_print(); + } else if (result == ID_CLEANUP) { + // Clean Up: close SelectMachineDialog and navigate + navigate_to_timelapse_page(); + } + // wxID_CANCEL (X button): do nothing, user dismissed the dialog +} + +void SelectMachineDialog::navigate_to_timelapse_page() +{ + // EndModal closes the dialog; schedule navigation after the dialog is fully destroyed + wxGetApp().CallAfter([]() { + auto* main_frame = wxGetApp().mainframe; + if (!main_frame) return; + + // use existing jump_to_monitor to switch to Monitor tab + main_frame->jump_to_monitor(); + + // then switch to Storage (Media) tab inside Monitor + auto* monitor = dynamic_cast(main_frame->m_monitor); + if (monitor) { + auto* tabpanel = monitor->get_tabpanel(); + if (tabpanel) { + tabpanel->SetSelection(MonitorPanel::PT_MEDIA); + } + } + }); + + this->EndModal(wxID_CANCEL); +} + void SelectMachineDialog::on_send_print() { BOOST_LOG_TRIVIAL(info) << "print_job: on_ok to send"; @@ -4441,6 +4665,7 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_) } } } + check_timelapse_storage_warning(obj_); // Orca: show warning if external filament does not match for (auto& m : m_ams_mapping_result) { diff --git a/src/slic3r/GUI/SelectMachine.hpp b/src/slic3r/GUI/SelectMachine.hpp index 82889b7035..82266dbf1b 100644 --- a/src/slic3r/GUI/SelectMachine.hpp +++ b/src/slic3r/GUI/SelectMachine.hpp @@ -340,6 +340,12 @@ private: std::string m_timelapse_storage; // "internal" or "external"; empty = unsupported ScalableButton* m_timelapse_folder_btn { nullptr }; PopupWindow* m_timelapse_storage_popup { nullptr }; + // timelapse storage-low send-time gate (async ipcam MQTT round-trip) + wxTimer* m_timelapse_check_timer { nullptr }; + int m_timelapse_check_timeout_ms { 5000 }; + int m_timelapse_check_elapsed_ms { 0 }; + int m_timelapse_check_interval_ms { 100 }; + int m_timelapse_total_layer { 0 }; std::vector ops_auto; std::vector ops_no_auto; @@ -581,6 +587,12 @@ private: /* timelapse storage-location selector */ void update_timelapse_folder_btn_icon(); void show_timelapse_folder_popup(); + void check_timelapse_storage_warning(MachineObject* obj); + void start_timelapse_storage_check(MachineObject* obj); + void on_timelapse_storage_check_timer(wxTimerEvent& event); + void on_timelapse_storage_check_result(); + void show_timelapse_storage_dialog(MachineObject* obj); + void navigate_to_timelapse_page(); // save and restore from config void load_option_vals(MachineObject* obj);