mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-11 02:57:39 +00:00
Check timelapse storage space before sending a print
This commit is contained in:
@@ -27,8 +27,35 @@ DevStorage::SdcardState Slic3r::DevStorage::set_sdcard_state(int state)
|
|||||||
} else {
|
} else {
|
||||||
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
|
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<int>();
|
||||||
|
if (cam_data.contains("tl_internal_total_kb"))
|
||||||
|
system->tl_internal_total_kb = cam_data["tl_internal_total_kb"].get<int>();
|
||||||
|
if (cam_data.contains("tl_external_free_kb"))
|
||||||
|
system->tl_external_free_kb = cam_data["tl_external_free_kb"].get<int>();
|
||||||
|
if (cam_data.contains("tl_external_total_kb"))
|
||||||
|
system->tl_external_total_kb = cam_data["tl_external_total_kb"].get<int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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
|
} // namespace Slic3r
|
||||||
@@ -28,9 +28,16 @@ public:
|
|||||||
|
|
||||||
static void ParseV1_0(const json &print_json, DevStorage *system);
|
static void ParseV1_0(const json &print_json, DevStorage *system);
|
||||||
|
|
||||||
|
bool is_timelapse_storage_low(const std::string& storage) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MachineObject *m_owner;
|
MachineObject *m_owner;
|
||||||
SdcardState m_sdcard_state { NO_SDCARD };
|
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
|
} // namespace Slic3r
|
||||||
@@ -1208,7 +1208,7 @@ bool MachineObject::canEnableTimelapse(wxString &error_message) const
|
|||||||
return true;
|
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) {
|
if (m_storage->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD) {
|
||||||
error_message = _L("Timelapse is not supported while the storage does not exist.");
|
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) {
|
} 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);
|
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()
|
int MachineObject::command_purification_disable()
|
||||||
{
|
{
|
||||||
json j;
|
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<std::string>();
|
this->camera_resolution = j["camera"]["resolution"].get<std::string>();
|
||||||
BOOST_LOG_TRIVIAL(info) << "ack of resolution = " << camera_resolution;
|
BOOST_LOG_TRIVIAL(info) << "ack of resolution = " << camera_resolution;
|
||||||
}
|
}
|
||||||
|
} else if (j["camera"]["command"].get<std::string>() == "ipcam_get_media_info") {
|
||||||
|
if (j["camera"].contains("sub_command") &&
|
||||||
|
j["camera"]["sub_command"].get<std::string>() == "is_timelapse_storage_enough") {
|
||||||
|
timelapse_storage_check_result = j["camera"]["result"].get<int>();
|
||||||
|
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()) {
|
if (!aux.empty()) {
|
||||||
m_storage->set_sdcard_state(get_flag_bits(aux, 12, 2));
|
m_storage->set_sdcard_state(get_flag_bits(aux, 12, 2));
|
||||||
|
m_has_timelapse_kit = (get_flag_bits(aux, 26, 1) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*stat*/
|
/*stat*/
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -558,6 +559,7 @@ public:
|
|||||||
|
|
||||||
bool file_model_download{false};
|
bool file_model_download{false};
|
||||||
bool virtual_camera{false};
|
bool virtual_camera{false};
|
||||||
|
bool m_has_timelapse_kit{false};
|
||||||
|
|
||||||
bool xcam_ai_monitoring{ false };
|
bool xcam_ai_monitoring{ false };
|
||||||
bool xcam_disable_ai_detection_display{false};
|
bool xcam_disable_ai_detection_display{false};
|
||||||
@@ -634,6 +636,12 @@ public:
|
|||||||
bool is_support_airprinting_detection{false};
|
bool is_support_airprinting_detection{false};
|
||||||
bool is_support_idelheadingprotect_detection{false};
|
bool is_support_idelheadingprotect_detection{false};
|
||||||
|
|
||||||
|
// timelapse storage check result (temp state from MQTT response)
|
||||||
|
std::atomic<bool> timelapse_storage_check_done { false };
|
||||||
|
int timelapse_storage_check_result { -1 };
|
||||||
|
bool timelapse_storage_is_enough { true };
|
||||||
|
int timelapse_storage_file_count { 0 };
|
||||||
|
|
||||||
// fun2
|
// fun2
|
||||||
bool is_support_print_with_emmc{false};
|
bool is_support_print_with_emmc{false};
|
||||||
bool is_support_remote_dry = false;
|
bool is_support_remote_dry = false;
|
||||||
@@ -706,6 +714,7 @@ public:
|
|||||||
|
|
||||||
/* quick check*/
|
/* quick check*/
|
||||||
bool canEnableTimelapse(wxString& error_message) const;
|
bool canEnableTimelapse(wxString& error_message) const;
|
||||||
|
bool is_timelapse_storage_low(const std::string& storage) const;
|
||||||
|
|
||||||
/* command commands */
|
/* command commands */
|
||||||
int command_get_version(bool with_retry = true);
|
int command_get_version(bool with_retry = true);
|
||||||
@@ -810,6 +819,8 @@ public:
|
|||||||
int command_ipcam_record(bool on_off);
|
int command_ipcam_record(bool on_off);
|
||||||
int command_ipcam_timelapse(bool on_off);
|
int command_ipcam_timelapse(bool on_off);
|
||||||
int command_ipcam_resolution_set(std::string resolution);
|
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 = "");
|
int command_xcam_control(std::string module_name, bool on_off, std::string lvl = "");
|
||||||
|
|
||||||
//refine printer
|
//refine printer
|
||||||
|
|||||||
@@ -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<void()> 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)
|
//void PrePrintMsgBoard::add(const wxString &msg, const wxString &tips, bool is_error)
|
||||||
//{
|
//{
|
||||||
@@ -230,7 +273,15 @@ bool PrinterMsgPanel::UpdateInfos(const std::vector<prePrintInfo>& infos)
|
|||||||
label->SetForegroundColour(_GetLabelColour(info));
|
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);
|
label->SetLabel(info.msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef slic3r_GUI_PRE_PRINT_CHECK_hpp_
|
#ifndef slic3r_GUI_PRE_PRINT_CHECK_hpp_
|
||||||
#define slic3r_GUI_PRE_PRINT_CHECK_hpp_
|
#define slic3r_GUI_PRE_PRINT_CHECK_hpp_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <wx/wx.h>
|
#include <wx/wx.h>
|
||||||
#include "Widgets/Label.hpp"
|
#include "Widgets/Label.hpp"
|
||||||
namespace Slic3r { namespace GUI {
|
namespace Slic3r { namespace GUI {
|
||||||
@@ -23,13 +24,17 @@ struct prePrintInfo
|
|||||||
wxString msg;
|
wxString msg;
|
||||||
wxString tips;
|
wxString tips;
|
||||||
wxString wiki_url;
|
wxString wiki_url;
|
||||||
|
wxString link_label; // optional: clickable text appended after msg
|
||||||
|
std::function<void()> link_callback; // optional: internal action for link_label click
|
||||||
int index;
|
int index;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool operator==(const prePrintInfo& other) const {
|
bool operator==(const prePrintInfo& other) const {
|
||||||
return level == other.level && type == other.type &&
|
return level == other.level && type == other.type &&
|
||||||
msg == other.msg && tips == other.tips &&
|
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,
|
PrintStatusPrinterWarningBegin,
|
||||||
PrintStatusTimelapseNoSdcard,
|
PrintStatusTimelapseNoSdcard,
|
||||||
PrintStatusTimelapseWarning,
|
PrintStatusTimelapseWarning,
|
||||||
|
PrintStatusTimelapseStorageLow,
|
||||||
PrintStatusMixAmsAndVtSlotWarning,
|
PrintStatusMixAmsAndVtSlotWarning,
|
||||||
PrintStatusToolHeadCoolingFanWarning,
|
PrintStatusToolHeadCoolingFanWarning,
|
||||||
PrintStatusRackNozzleMappingWarning,
|
PrintStatusRackNozzleMappingWarning,
|
||||||
@@ -143,6 +149,8 @@ public:
|
|||||||
void clear();
|
void clear();
|
||||||
/*auto merge*/
|
/*auto merge*/
|
||||||
void add(PrintDialogStatus state, wxString msg, wxString tip, const wxString& wiki_url);
|
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<void()> link_callback);
|
||||||
static ::std::string get_print_status_info(PrintDialogStatus status);
|
static ::std::string get_print_status_info(PrintDialogStatus status);
|
||||||
|
|
||||||
wxString get_pre_state_msg(PrintDialogStatus status);
|
wxString get_pre_state_msg(PrintDialogStatus status);
|
||||||
|
|||||||
@@ -1883,7 +1883,7 @@ bool SelectMachineDialog::check_sdcard_for_timelpase(MachineObject* obj)
|
|||||||
// must set to a status if return true
|
// must set to a status if return true
|
||||||
if (m_checkbox_list["timelapse"]->IsShown() && m_checkbox_list["timelapse"]->getValue() == "on")
|
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);
|
show_status(PrintDialogStatus::PrintStatusTimelapseNoSdcard);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2083,6 +2083,9 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
|
|||||||
msg = msg_text;
|
msg = msg_text;
|
||||||
Enable_Refresh_Button(true);
|
Enable_Refresh_Button(true);
|
||||||
Enable_Send_Button(true);
|
Enable_Send_Button(true);
|
||||||
|
} else if (status == PrintDialogStatus::PrintStatusTimelapseStorageLow) {
|
||||||
|
Enable_Refresh_Button(true);
|
||||||
|
Enable_Send_Button(true);
|
||||||
} else if (status == PrintStatusToolHeadCoolingFanWarning) {
|
} else if (status == PrintStatusToolHeadCoolingFanWarning) {
|
||||||
Enable_Refresh_Button(true);
|
Enable_Refresh_Button(true);
|
||||||
Enable_Send_Button(true);
|
Enable_Send_Button(true);
|
||||||
@@ -2574,7 +2577,16 @@ void SelectMachineDialog::on_ok_btn(wxCommandEvent &event)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this->on_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;
|
m_timelapse_storage = val;
|
||||||
update_timelapse_folder_btn_icon();
|
update_timelapse_folder_btn_icon();
|
||||||
if (m_timelapse_storage_popup) m_timelapse_storage_popup->Dismiss();
|
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);
|
radio->Bind(wxEVT_LEFT_DOWN, on_select);
|
||||||
text->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();
|
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, int>(wxColour(0, 150, 136), StateColor::Normal));
|
||||||
|
btn_confirm->SetBackgroundColor(confirm_bg);
|
||||||
|
btn_confirm->SetTextColor(StateColor(std::pair<wxColour, int>(*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, int>(wxColour(0, 150, 136), StateColor::Normal));
|
||||||
|
btn_cancel_tl->SetBackgroundColor(cancel_bg);
|
||||||
|
btn_cancel_tl->SetTextColor(StateColor(std::pair<wxColour, int>(*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<MonitorPanel*>(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()
|
void SelectMachineDialog::on_send_print()
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(info) << "print_job: on_ok to send";
|
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
|
// Orca: show warning if external filament does not match
|
||||||
for (auto& m : m_ams_mapping_result) {
|
for (auto& m : m_ams_mapping_result) {
|
||||||
|
|||||||
@@ -340,6 +340,12 @@ private:
|
|||||||
std::string m_timelapse_storage; // "internal" or "external"; empty = unsupported
|
std::string m_timelapse_storage; // "internal" or "external"; empty = unsupported
|
||||||
ScalableButton* m_timelapse_folder_btn { nullptr };
|
ScalableButton* m_timelapse_folder_btn { nullptr };
|
||||||
PopupWindow* m_timelapse_storage_popup { 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<POItem> ops_auto;
|
std::vector<POItem> ops_auto;
|
||||||
std::vector<POItem> ops_no_auto;
|
std::vector<POItem> ops_no_auto;
|
||||||
@@ -581,6 +587,12 @@ private:
|
|||||||
/* timelapse storage-location selector */
|
/* timelapse storage-location selector */
|
||||||
void update_timelapse_folder_btn_icon();
|
void update_timelapse_folder_btn_icon();
|
||||||
void show_timelapse_folder_popup();
|
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
|
// save and restore from config
|
||||||
void load_option_vals(MachineObject* obj);
|
void load_option_vals(MachineObject* obj);
|
||||||
|
|||||||
Reference in New Issue
Block a user