mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-18 08:22:06 +00:00
Show the print-failure snapshot in the device error dialog
When a print fails, DeviceErrorDialog now fetches the printer's captured camera frame of the failure and shows it in place of the generic HMS illustration, falling back to the local image and a drawn placeholder on older plugins or errors. - Agent: add get_hms_snapshot through NetworkAgent / IPrinterAgent (BBL calls the bound plugin symbol; other agents no-op, so old plugins degrade gracefully). - DeviceManager: parse and clear m_print_error_img_id from the print-error message. - Dialog: tiered cloud/local/placeholder image reusing the single image widget, with a liveness-guarded async callback decoded on the UI thread.
This commit is contained in:
@@ -5,6 +5,10 @@
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "ReleaseNote.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/dcmemory.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI
|
||||
@@ -88,10 +92,22 @@ DeviceErrorDialog::DeviceErrorDialog(MachineObject* obj, wxWindow* parent, wxWin
|
||||
if (m_obj) { m_obj->command_clean_print_error_uiop(m_obj->print_error); }
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
m_request_timer = new wxTimer(this);
|
||||
Bind(wxEVT_TIMER, &DeviceErrorDialog::on_request_timeout, this, m_request_timer->GetId());
|
||||
}
|
||||
|
||||
DeviceErrorDialog::~DeviceErrorDialog()
|
||||
{
|
||||
// Orca: invalidate any in-flight cloud snapshot callback (the request has no cancel handle)
|
||||
*m_alive = false;
|
||||
|
||||
if (m_request_timer) {
|
||||
m_request_timer->Stop();
|
||||
delete m_request_timer;
|
||||
m_request_timer = nullptr;
|
||||
}
|
||||
|
||||
if (web_request.IsOk() && web_request.GetState() == wxWebRequest::State_Active)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "web_request: cancelled";
|
||||
@@ -103,6 +119,11 @@ DeviceErrorDialog::~DeviceErrorDialog()
|
||||
void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: monitor_panel web request state = " << evt.GetState();
|
||||
|
||||
// A local/HTTP illustration resolved (or the cloud path fell back to it): stop the watchdog.
|
||||
m_request_cancelled.store(true);
|
||||
clear_request_timer();
|
||||
|
||||
switch (evt.GetState())
|
||||
{
|
||||
case wxWebRequest::State_Completed:
|
||||
@@ -120,7 +141,9 @@ void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
|
||||
case wxWebRequest::State_Cancelled:
|
||||
case wxWebRequest::State_Unauthorized:
|
||||
{
|
||||
m_error_picture->SetBitmap(wxBitmap());
|
||||
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
|
||||
Layout();
|
||||
Fit();
|
||||
break;
|
||||
}
|
||||
case wxWebRequest::State_Active:
|
||||
@@ -129,6 +152,117 @@ void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceErrorDialog::on_request_timeout(wxTimerEvent& event)
|
||||
{
|
||||
if (m_request_cancelled.load()) { return; }
|
||||
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void DeviceErrorDialog::clear_request_timer()
|
||||
{
|
||||
if (m_request_timer && m_request_timer->IsRunning()) {
|
||||
m_request_timer->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
wxBitmap DeviceErrorDialog::make_placeholder_image(const wxString& text)
|
||||
{
|
||||
const int w = FromDIP(320);
|
||||
const int h = FromDIP(180);
|
||||
|
||||
wxBitmap bmp(wxSize(w, h));
|
||||
wxMemoryDC dc(bmp);
|
||||
dc.SetBackground(wxBrush(wxColour(238, 238, 238)));
|
||||
dc.Clear();
|
||||
|
||||
ScalableBitmap icon = ScalableBitmap(this, "dev_hms_diag_loading", 80);
|
||||
wxBitmap icon_bmp = icon.bmp();
|
||||
if (icon_bmp.IsOk()) {
|
||||
int ix = (w - icon_bmp.GetWidth()) / 2;
|
||||
int iy = (h - icon_bmp.GetHeight()) / 3;
|
||||
dc.DrawBitmap(icon_bmp, ix, iy, true);
|
||||
}
|
||||
|
||||
dc.SetTextForeground(wxColour(158, 158, 158));
|
||||
dc.SetFont(Label::Body_14);
|
||||
wxSize txtSize = dc.GetTextExtent(text);
|
||||
int tx = (w - txtSize.GetWidth()) / 2;
|
||||
int ty = h - txtSize.GetHeight() - FromDIP(45);
|
||||
dc.DrawText(text, tx, ty);
|
||||
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
bool DeviceErrorDialog::get_fail_snapshot_from_cloud()
|
||||
{
|
||||
if (!m_obj || m_obj->m_print_error_img_id.empty()) { return false; }
|
||||
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (!agent) { return false; }
|
||||
|
||||
// Orca: the cloud request has no cancel handle, so guard the callback against dialog
|
||||
// destruction with a liveness token (m_alive, cleared in the dtor) and marshal onto the UI
|
||||
// thread via the always-valid app handler instead of this->CallAfter. The raw bytes are
|
||||
// decoded on the UI thread so no wxImage (non-atomic refcount) is shared across threads.
|
||||
// Assumes a single in-flight request per dialog, which all current callers satisfy.
|
||||
auto alive = m_alive;
|
||||
int ret = agent->get_hms_snapshot(m_obj->get_dev_id(), m_obj->m_print_error_img_id,
|
||||
[this, alive](std::string body, int status) {
|
||||
if (!alive->load()) { return; }
|
||||
wxGetApp().CallAfter([this, alive, body = std::move(body), status]() {
|
||||
if (!alive->load()) { return; }
|
||||
m_request_cancelled.store(true);
|
||||
clear_request_timer();
|
||||
|
||||
wxImage image;
|
||||
bool ok = false;
|
||||
if (status == 200) {
|
||||
wxMemoryInputStream stream(body.data(), body.size());
|
||||
ok = image.LoadFile(stream, wxBITMAP_TYPE_ANY);
|
||||
if (!ok) { BOOST_LOG_TRIVIAL(error) << "get_fail_snapshot_from_cloud: failed to resolve stream"; }
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "get_fail_snapshot_from_cloud: status = " << status;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
wxImage resize_img = image.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
|
||||
m_error_picture->SetBitmap(wxBitmap(resize_img));
|
||||
} else if (!get_fail_snapshot_from_local(m_local_img_url)) {
|
||||
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
|
||||
}
|
||||
Layout();
|
||||
Fit();
|
||||
});
|
||||
});
|
||||
|
||||
return ret == 0; // dispatched; the frame arrives later via the callback
|
||||
}
|
||||
|
||||
bool DeviceErrorDialog::get_fail_snapshot_from_local(const wxString& image_url)
|
||||
{
|
||||
if (image_url.empty()) { return false; }
|
||||
|
||||
const wxImage& img = wxGetApp().get_hms_query()->query_image_from_local(image_url);
|
||||
if (!img.IsOk() && image_url.Contains("http"))
|
||||
{
|
||||
web_request = wxWebSession::GetDefault().CreateRequest(this, image_url);
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: create new webrequest, state = " << web_request.GetState();
|
||||
if (web_request.GetState() == wxWebRequest::State_Idle) web_request.Start();
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: start new webrequest, state = " << web_request.GetState();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_request_cancelled.store(true);
|
||||
clear_request_timer();
|
||||
const wxImage& resize_img = img.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
|
||||
m_error_picture->SetBitmap(wxBitmap(resize_img));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceErrorDialog::init_button(ActionButton style, wxString buton_text)
|
||||
{
|
||||
if (btn_bg_white.count() == 0)
|
||||
@@ -305,22 +439,22 @@ void DeviceErrorDialog::update_contents(const wxString& title, const wxString& t
|
||||
}
|
||||
|
||||
/* image */
|
||||
if (!image_url.empty())
|
||||
// Orca: tiered image slot — prefer the printer's captured camera frame of the failure
|
||||
// (cloud, keyed by m_print_error_img_id), else the local/HTTP HMS illustration, else hide.
|
||||
// Reuses m_error_picture + on_webrequest_state (single widget, no second request). The
|
||||
// loading placeholder and 10s watchdog are armed only for the async cloud fetch; the local
|
||||
// path keeps its existing behavior.
|
||||
m_local_img_url = image_url;
|
||||
m_request_cancelled.store(false);
|
||||
clear_request_timer();
|
||||
if (get_fail_snapshot_from_cloud())
|
||||
{
|
||||
m_error_picture->SetBitmap(make_placeholder_image(_L("Loading ...")));
|
||||
m_request_timer->StartOnce(10000);
|
||||
m_error_picture->Show();
|
||||
}
|
||||
else if (get_fail_snapshot_from_local(image_url))
|
||||
{
|
||||
const wxImage& img = wxGetApp().get_hms_query()->query_image_from_local(image_url);
|
||||
if (!img.IsOk() && image_url.Contains("http"))
|
||||
{
|
||||
web_request = wxWebSession::GetDefault().CreateRequest(this, image_url);
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: create new webrequest, state = " << web_request.GetState();
|
||||
if (web_request.GetState() == wxWebRequest::State_Idle) web_request.Start();
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: start new webrequest, state = " << web_request.GetState();
|
||||
}
|
||||
else
|
||||
{
|
||||
const wxImage& resize_img = img.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
|
||||
m_error_picture->SetBitmap(wxBitmap(resize_img));
|
||||
}
|
||||
|
||||
m_error_picture->Show();
|
||||
}
|
||||
else
|
||||
@@ -349,7 +483,9 @@ void DeviceErrorDialog::update_contents(const wxString& title, const wxString& t
|
||||
auto text_size = m_error_msg_label->GetBestSize();
|
||||
if (text_size.y < FromDIP(360))
|
||||
{
|
||||
if (!image_url.empty())
|
||||
// Orca: also reserve the image area when only a cloud snapshot (m_print_error_img_id)
|
||||
// is available, so the scroll area sizes correctly even with an empty local image_url.
|
||||
if (!image_url.empty() || (m_obj && !m_obj->m_print_error_img_id.empty()))
|
||||
{
|
||||
m_scroll_area->SetMinSize(wxSize(FromDIP(320), text_size.y + FromDIP(220)));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/webrequest.h>
|
||||
|
||||
@@ -84,6 +86,11 @@ protected:
|
||||
|
||||
void on_button_click(ActionButton btn_id);
|
||||
void on_webrequest_state(wxWebRequestEvent& evt);
|
||||
void on_request_timeout(wxTimerEvent& event);
|
||||
void clear_request_timer();
|
||||
wxBitmap make_placeholder_image(const wxString& text);
|
||||
bool get_fail_snapshot_from_cloud();
|
||||
bool get_fail_snapshot_from_local(const wxString& image_url);
|
||||
void on_dpi_changed(const wxRect& suggested_rect);
|
||||
|
||||
private:
|
||||
@@ -93,6 +100,11 @@ private:
|
||||
std::unordered_set<Button*> m_used_button;
|
||||
|
||||
wxWebRequest web_request;
|
||||
wxTimer* m_request_timer{ nullptr };
|
||||
std::atomic<bool> m_request_cancelled{ false };
|
||||
wxString m_local_img_url;
|
||||
// Orca: liveness token for the async cloud snapshot callback (the request has no cancel handle)
|
||||
std::shared_ptr<std::atomic_bool> m_alive{ std::make_shared<std::atomic_bool>(true) };
|
||||
wxStaticBitmap* m_error_picture;
|
||||
Label* m_error_msg_label{ nullptr };
|
||||
Label* m_error_code_label{ nullptr };
|
||||
|
||||
@@ -553,6 +553,7 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
|
||||
mc_print_sub_stage = 0;
|
||||
mc_left_time = 0;
|
||||
hw_switch_state = 0;
|
||||
m_print_error_img_id = "";
|
||||
|
||||
has_ipcam = true; // default true
|
||||
|
||||
@@ -3142,6 +3143,17 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
if (jj["print_error"].is_number())
|
||||
print_error = jj["print_error"].get<int>();
|
||||
}
|
||||
// Orca: keep the failure-snapshot id only while an error is active, so a stale
|
||||
// id can't leak into a later unrelated error dialog once the failure clears.
|
||||
if (print_error <= 0) {
|
||||
m_print_error_img_id.clear();
|
||||
}
|
||||
else if (jj.contains("err2") && jj["err2"].is_object()) {
|
||||
json err2 = jj["err2"];
|
||||
if (err2.contains("img_id") && err2["img_id"].is_string()) {
|
||||
m_print_error_img_id = err2["img_id"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
DevStorage::ParseV1_0(jj, m_storage);
|
||||
|
||||
|
||||
@@ -417,6 +417,7 @@ public:
|
||||
bool is_system_printing();
|
||||
|
||||
int print_error;
|
||||
std::string m_print_error_img_id;
|
||||
static std::string get_error_code_str(int error_code);
|
||||
std::string get_print_error_str() const { return MachineObject::get_error_code_str(this->print_error); }
|
||||
|
||||
|
||||
@@ -171,6 +171,19 @@ int BBLPrinterAgent::request_bind_ticket(std::string* ticket)
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_hms_snapshot();
|
||||
// dev_id/file_name are passed as lvalues to bind the plugin's std::string& params.
|
||||
// A null func (older plugin without this symbol) falls through to -1 so callers degrade gracefully.
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id, file_name, callback);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_server_callback(OnServerErrFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
|
||||
@@ -45,6 +45,7 @@ public:
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
int request_bind_ticket(std::string* ticket) override;
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
|
||||
int set_server_callback(OnServerErrFn fn) override;
|
||||
|
||||
// Machine Selection
|
||||
|
||||
@@ -139,6 +139,12 @@ public:
|
||||
*/
|
||||
virtual int request_bind_ticket(std::string* ticket) = 0;
|
||||
|
||||
/**
|
||||
* Fetch the cloud snapshot image captured at a print failure.
|
||||
* Returns 0 if the request was dispatched; the image body arrives via callback(body, http_status).
|
||||
*/
|
||||
virtual int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) = 0;
|
||||
|
||||
/**
|
||||
* Register callback for fatal HTTP errors.
|
||||
*/
|
||||
|
||||
@@ -254,6 +254,15 @@ int MoonrakerPrinterAgent::request_bind_ticket(std::string* ticket)
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
|
||||
{
|
||||
// No BBL cloud snapshot source; report failure so the caller falls back.
|
||||
(void) dev_id;
|
||||
(void) file_name;
|
||||
(void) callback;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::set_server_callback(OnServerErrFn fn)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(state_mutex);
|
||||
|
||||
@@ -45,6 +45,7 @@ public:
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
int request_bind_ticket(std::string* ticket) override;
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
|
||||
int set_server_callback(OnServerErrFn fn) override;
|
||||
|
||||
// Machine Selection
|
||||
|
||||
@@ -937,4 +937,11 @@ int NetworkAgent::request_bind_ticket(std::string* ticket)
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->get_hms_snapshot(dev_id, file_name, callback);
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -166,6 +166,7 @@ public:
|
||||
FilamentSyncMode get_filament_sync_mode() const;
|
||||
bool fetch_filament_info(std::string dev_id);
|
||||
int request_bind_ticket(std::string* ticket);
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback);
|
||||
|
||||
private:
|
||||
struct PrinterCallbacks {
|
||||
|
||||
@@ -95,6 +95,15 @@ int OrcaPrinterAgent::request_bind_ticket(std::string* ticket)
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
|
||||
{
|
||||
// No BBL cloud snapshot source; report failure so the caller falls back.
|
||||
(void) dev_id;
|
||||
(void) file_name;
|
||||
(void) callback;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_server_callback(OnServerErrFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
|
||||
@@ -45,6 +45,7 @@ public:
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
int request_bind_ticket(std::string* ticket) override;
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
|
||||
int set_server_callback(OnServerErrFn fn) override;
|
||||
|
||||
// Machine Selection
|
||||
|
||||
Reference in New Issue
Block a user