Compare commits

...

47 Commits

Author SHA1 Message Date
SoftFever
01f2af94a1 fix build errors 2026-07-18 03:15:11 +08:00
SoftFever
8d9b08fed9 Merge branch 'main' into feature/bambu_printer_housekeeping 2026-07-18 02:43:38 +08:00
SoftFever
ddfa961111 Merge branch 'main' into feature/bambu_printer_housekeeping
# Conflicts:
#	tests/slic3rutils/CMakeLists.txt
2026-07-18 02:08:13 +08:00
SoftFever
0b4cb89f7c Remove porting jargon and provenance notes from comments 2026-07-18 01:46:23 +08:00
SoftFever
4683c17f6f Fix device popup, mapping, and status-parse defects
- Allow mapping to EMPTY trays only from the multi-machine send page and keep
  both panels pickable in the LEFT_AND_RIGHT view
- Guard the error-dialog cloud snapshot against stale callbacks and fall back
  to the local illustration on timeout
- Parse the ipcam storage-check ack and axis/chamber pushes defensively
- Strip fan-control telemetry, initialize the upgrade error code, restore the
  .json filter in the model-id scan
- Fix best-position popup tray lookup, gradient placement, and colour-list
  ownership
- Cover switch binding sets and invalid-track transients in DevMapping tests
2026-07-18 01:46:10 +08:00
SoftFever
12720ccbd8 Gate network plugin loading to ABI-compatible series
Trim the version whitelist to the latest series plus the pinned legacy build,
and reject out-of-series configured versions at startup, compatibility check,
and load failure - falling back to the latest installed build or the clean
re-download flow so the config never keeps pointing at an unsupported build.
2026-07-18 01:45:54 +08:00
SoftFever
6767ab5ae0 Make the Latest and installed version labels dynamic 2026-07-17 16:25:10 +08:00
SoftFever
9d9bb9cdbd Install the OTA plugin update immediately and hot-reload it 2026-07-17 16:25:10 +08:00
SoftFever
79593e07c6 List same-series OTA plugin builds in the version selector 2026-07-17 14:23:40 +08:00
SoftFever
5808ae5ab0 Fix the network plugin OTA update flow 2026-07-17 14:23:39 +08:00
SoftFever
1b722de6e7 Fix latent popup defects and mark the unwired device modules 2026-07-17 13:20:41 +08:00
SoftFever
38ab986c5f Drop the transitional device-layer shims 2026-07-17 12:46:45 +08:00
SoftFever
37e27a24c4 Fix the pre-send advisories and port the remaining send-flow checks 2026-07-17 12:10:45 +08:00
SoftFever
7f8baf46d3 Port the pre-send checks and wire the AMS best-position popup 2026-07-17 11:10:21 +08:00
SoftFever
d097fbec9a Resync the device send flow 2026-07-17 10:20:24 +08:00
SoftFever
2ee0bbde54 Port the filament-change stop button, hub version row and HMS fallback 2026-07-17 09:41:01 +08:00
SoftFever
80d3c3908d Resync the device status pages 2026-07-17 08:30:22 +08:00
SoftFever
5e95aca42d Restore the popup accent colors and improved strings 2026-07-17 07:25:02 +08:00
SoftFever
40a875800d Resync the AMS and device dialogs 2026-07-17 06:48:15 +08:00
SoftFever
50abe02f18 Make AMS trays read-only in laser mode and add the gradient fill helper 2026-07-17 05:06:09 +08:00
SoftFever
7265a2fbfe Resync the AMS control and item widgets 2026-07-17 04:30:45 +08:00
SoftFever
db51d8ab9b Mark the accent-color divergence in the best-position popup 2026-07-17 03:50:29 +08:00
SoftFever
d4e95e3269 Resync the device tab widgets and add the AMS best-position popup 2026-07-17 03:37:18 +08:00
SoftFever
0feca8d442 Port accessory firmware versions, cold-pull state and filament checks 2026-07-17 02:49:45 +08:00
SoftFever
8d211ce878 Move axis, calib, chamber, status and upgrade handling into DeviceCore 2026-07-17 02:04:25 +08:00
SoftFever
8a6609f282 Restore the extruder name fallback and fix the loading-busy check 2026-07-17 01:16:47 +08:00
SoftFever
4bca7b3008 Resync the DeviceCore state models 2026-07-17 00:49:04 +08:00
SoftFever
550ab5d438 Guard the don't-remind command parse and init the checker index 2026-07-16 20:09:50 +08:00
SoftFever
56ac4e1281 Mark deliberate porting divergences 2026-07-16 20:07:55 +08:00
SoftFever
5fd1aa6e8b Add firmware print-option toggles to the print options dialog 2026-07-16 19:28:50 +08:00
SoftFever
eeec8ab06b Parse the new detection bits on the live push path 2026-07-16 18:51:25 +08:00
SoftFever
28b7fd90d0 Restructure DevPrintOptions around a detection-option map 2026-07-16 18:40:53 +08:00
SoftFever
4a1c53e76e Stop the pending storage check when jumping to the timelapse page 2026-07-16 18:18:51 +08:00
SoftFever
3ec074edb2 Stop the timelapse storage check when the send dialog closes 2026-07-16 18:11:01 +08:00
SoftFever
615deefa87 Check timelapse storage space before sending a print 2026-07-16 17:52:28 +08:00
SoftFever
ffda4282eb Add timelapse storage location selection 2026-07-16 16:59:16 +08:00
SoftFever
5cf7e27c23 Show combined nozzle mapping when a filament switch is installed 2026-07-16 16:35:11 +08:00
SoftFever
8bb093b50b Map switch-bound AMS trays to both extruders 2026-07-16 15:54:56 +08:00
SoftFever
47f7e09e84 Block TPU on the left extruder without firmware support 2026-07-16 15:21:35 +08:00
SoftFever
e8f8a4bb7d Add purification and don't-remind actions to the device error dialog 2026-07-16 15:15:40 +08:00
SoftFever
c7581e590c Warn when a filament will switch extruders mid-print 2026-07-16 14:58:58 +08:00
SoftFever
5f3f892a6b Fix inverted MQTT jog on i3-arch printers 2026-07-16 14:54:16 +08:00
SoftFever
0f58f23853 Destroy the network-plugin agent before unloading its DLL
A network-plugin hot reload unloaded the DLL without destroying the agent
handle it created, so the plugin's m_agent was left dangling into freed
memory. On reload create_agent() short-circuited on has_agent() and kept the
stale handle, and the next call into the new DLL (install_device_cert from the
device-refresh timer) dereferenced it and crashed with an access violation.
unload() now destroys the agent first, so a reload always gets a fresh handle.
2026-07-16 10:53:28 +08:00
SoftFever
7cc572be47 Add PA-profile sharing toggle to the device send dialog 2026-07-16 02:47:47 +08:00
SoftFever
d591d50ca0 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.
2026-07-16 01:44:43 +08:00
SoftFever
aa44d57eac Bind the 02.08.01.52 plugin's new ABI symbols
Add the 11 functions the newer plugin exports (HMS snapshot, GoLive camera
URL, consent report, cloud filament-spool CRUD, AMS-filament sync, login-state
and studio-info hooks) plus their four parameter structs. Everything resolves
to null on older plugins and no caller invokes them yet, so this is inert ABI
surface for later features.
2026-07-16 00:48:56 +08:00
SoftFever
bf4fb64c8a Make 02.08.01.52 the default network plugin and match its ABI
The newer plugin adds four PrintParams fields (task_timelapse_use_internal,
extruder_cali_manual_mode, svc_context, slicer_uid) and an extra dev_model
argument to bind. Both cross the by-value C ABI boundary, so match the struct
layout and thread dev_model through the bind chain, else start_print and bind
corrupt the stack on the newer plugin. Keep 02.03.00.62 selectable as a fallback.
2026-07-16 00:17:01 +08:00
136 changed files with 8767 additions and 2143 deletions

View File

@@ -0,0 +1,18 @@
<svg width="117" height="88" viewBox="0 0 117 88" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_dev_hms_diag_loading)">
<ellipse cx="58.5" cy="48.2667" rx="32.175" ry="32.2667" fill="#F2F2F2" stroke="#DBDBDB"/>
<rect x="35.175" y="24.8662" width="46.65" height="46.8" rx="5" fill="#F7F7F7" stroke="#EBEBEB" stroke-width="6"/>
<path d="M43.875 57.0665L52.4779 46.7998L62.1562 57.0665H43.875Z" fill="#DBDBDB"/>
<path d="M51.1875 57.0671L62.8875 42.4004L76.05 57.0671H51.1875Z" fill="#EBEBEB"/>
<ellipse cx="49.3595" cy="39.0998" rx="3.29063" ry="3.3" fill="#DBDBDB"/>
<rect x="32.175" y="21.8662" width="52.65" height="52.8" rx="8" stroke="#DBDBDB"/>
<ellipse cx="17.55" cy="68.7995" rx="3.65625" ry="3.66667" fill="#EBEBEB"/>
<ellipse cx="24.1313" cy="28.4666" rx="2.19375" ry="2.2" fill="#EBEBEB"/>
<ellipse cx="100.912" cy="35.0662" rx="2.19375" ry="2.2" fill="#EBEBEB"/>
</g>
<defs>
<clipPath id="clip0_dev_hms_diag_loading">
<rect width="117" height="88" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.88802 4.48958C8.16145 4.76302 8.53124 4.91667 8.91927 4.91667H13C13.2292 4.91667 13.4167 5.10417 13.4167 5.33333V6.16667H2.58333V3.66667C2.58333 3.4375 2.77083 3.25 2.99999 3.25H6.47656C6.58593 3.25 6.6927 3.29427 6.77083 3.3724L7.88802 4.48958ZM2.58333 7.41667H13.4167V12C13.4167 12.2292 13.2292 12.4167 13 12.4167H2.99999C2.77083 12.4167 2.58333 12.2292 2.58333 12V7.41667ZM8.77343 3.60677L7.65364 2.48698C7.34114 2.17448 6.91666 2 6.47395 2H2.99999C2.08072 2 1.33333 2.7474 1.33333 3.66667V12C1.33333 12.9193 2.08072 13.6667 2.99999 13.6667H13C13.9193 13.6667 14.6667 12.9193 14.6667 12V5.33333C14.6667 4.41406 13.9193 3.66667 13 3.66667H8.91927C8.86458 3.66667 8.80989 3.64583 8.77083 3.60677H8.77343Z" fill="#009688"/>
</svg>

After

Width:  |  Height:  |  Size: 839 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.88802 4.48958C8.16145 4.76302 8.53124 4.91667 8.91927 4.91667H13C13.2292 4.91667 13.4167 5.10417 13.4167 5.33333V6.16667H2.58333V3.66667C2.58333 3.4375 2.77083 3.25 2.99999 3.25H6.47656C6.58593 3.25 6.6927 3.29427 6.77083 3.3724L7.88802 4.48958ZM2.58333 7.41667H13.4167V12C13.4167 12.2292 13.2292 12.4167 13 12.4167H2.99999C2.77083 12.4167 2.58333 12.2292 2.58333 12V7.41667ZM8.77343 3.60677L7.65364 2.48698C7.34114 2.17448 6.91666 2 6.47395 2H2.99999C2.08072 2 1.33333 2.7474 1.33333 3.66667V12C1.33333 12.9193 2.08072 13.6667 2.99999 13.6667H13C13.9193 13.6667 14.6667 12.9193 14.6667 12V5.33333C14.6667 4.41406 13.9193 3.66667 13 3.66667H8.91927C8.86458 3.66667 8.80989 3.64583 8.77083 3.60677H8.77343Z" fill="#6B6B6B"/>
</svg>

After

Width:  |  Height:  |  Size: 839 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.88802 4.48958C8.16145 4.76302 8.53124 4.91667 8.91927 4.91667H13C13.2292 4.91667 13.4167 5.10417 13.4167 5.33333V6.16667H2.58333V3.66667C2.58333 3.4375 2.77083 3.25 2.99999 3.25H6.47656C6.58593 3.25 6.6927 3.29427 6.77083 3.3724L7.88802 4.48958ZM2.58333 7.41667H13.4167V12C13.4167 12.2292 13.2292 12.4167 13 12.4167H2.99999C2.77083 12.4167 2.58333 12.2292 2.58333 12V7.41667ZM8.77343 3.60677L7.65364 2.48698C7.34114 2.17448 6.91666 2 6.47395 2H2.99999C2.08072 2 1.33333 2.7474 1.33333 3.66667V12C1.33333 12.9193 2.08072 13.6667 2.99999 13.6667H13C13.9193 13.6667 14.6667 12.9193 14.6667 12V5.33333C14.6667 4.41406 13.9193 3.66667 13 3.66667H8.91927C8.86458 3.66667 8.80989 3.64583 8.77083 3.60677H8.77343Z" fill="#262E30"/>
</svg>

After

Width:  |  Height:  |  Size: 839 B

View File

@@ -75,7 +75,8 @@
"support_extrusion_cali": false,
"support_user_preset": false,
"support_refresh_nozzle": true,
"support_ams_ext_mix_print": true
"support_ams_ext_mix_print": true,
"support_print_check_firmware_for_tpu_left": true
},
"model_id": "O1D",
"support_wrapping_detection": true,

View File

@@ -75,7 +75,8 @@
"support_extrusion_cali": false,
"support_user_preset": false,
"support_refresh_nozzle": true,
"support_ams_ext_mix_print": true
"support_ams_ext_mix_print": true,
"support_print_check_firmware_for_tpu_left": true
},
"model_id": "O1E",
"auto_pa_cali_thumbnail_image": "fd_calibration_auto_multi_extruders",

View File

@@ -23,6 +23,7 @@ set(SLIC3R_GUI_SOURCES
GUI/AboutDialog.cpp
GUI/AboutDialog.hpp
GUI/AmsMappingPopup.cpp
GUI/AmsMappingPopupUpdate.cpp
GUI/AmsMappingPopup.hpp
GUI/AMSMaterialsSetting.cpp
GUI/AMSMaterialsSetting.hpp

View File

@@ -42,7 +42,7 @@ static std::string get_humidity_level_img_path(int humidity_percent)
}
if (wxGetApp().dark_mode()) {
return "hum_level" + std::to_string(hum_level) + "_no_num_dark";
return "hum_level" + std::to_string(hum_level) + "_no_num_dark"; // Orca: use the dark-mode humidity glyph in dark mode
} else {
return "hum_level" + std::to_string(hum_level) + "_no_num_light";
}
@@ -86,7 +86,7 @@ FilamentItemPanel::FilamentItemPanel(wxWindow* parent, const wxString& text, con
: wxPanel(parent, id)
, m_icon_name(icon_name)
{
SetBackgroundColour(wxColour("#F0F0F1")); // Light gray background
SetBackgroundColour(wxColour("#F0F0F1")); // Orca: light-gray panel scheme (#F0F0F1 replaces REF #F7F7F7)
SetMinSize(wxSize(FromDIP(64), FromDIP(106))); // Width: 64, Height: 106
SetSize(wxSize(FromDIP(64), FromDIP(106))); // Fixed size
@@ -977,7 +977,7 @@ void AMSDryCtrWin::create()
// set title icon
SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
this->SetDoubleBuffered(true);
std::string icon_path = (boost::format("%1%/images/OrcaSlicerTitle.ico") % resources_dir()).str();
std::string icon_path = (boost::format("%1%/images/OrcaSlicerTitle.ico") % resources_dir()).str(); // Orca: app title icon
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetSize(wxSize(FromDIP(700), FromDIP(500)));

View File

@@ -6,7 +6,7 @@
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/I18N.hpp" // Orca: explicit _L() catalog include
#include <chrono>
#include <optional>

View File

@@ -296,11 +296,8 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
m_ratio_text->SetForegroundColour(wxColour(50, 58, 61));
m_ratio_text->SetFont(Label::Head_14);
std::string language = wxGetApp().app_config->get("language");
wxString region = "en";
if (language.find("zh") == 0)
region = "zh";
wxString link_url = wxString::Format("https://wiki.bambulab.com/%s/software/bambu-studio/calibration_pa", region);
// Orca: link to the Orca Slicer pressure-advance wiki (region-agnostic).
wxString link_url = "https://www.orcaslicer.com/wiki/pressure_advance_calib";
m_wiki_ctrl = new HyperLink(parent, _L("Wiki Guide"), link_url);
cali_title_sizer->Add(m_ratio_text, 0, wxALIGN_CENTER_VERTICAL);
cali_title_sizer->Add(m_wiki_ctrl, 0, wxALIGN_CENTER_VERTICAL);
@@ -449,9 +446,19 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
m_tip_readonly->Wrap(FromDIP(380));
m_tip_readonly->Show(is_printing);
}
if (m_view_only) { // Orca: view-only (2D laser/cut) — lock every edit control and hide apply/reset
m_comboBox_filament->Enable(false);
m_comboBox_cali_result->Enable(false);
m_input_k_val->Enable(false);
m_input_n_val->Enable(false);
m_button_confirm->Hide();
m_button_reset->Hide();
}
}
void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
if (m_view_only) return; // Orca: view-only never commits
MessageDialog msg_dlg(nullptr, _L("Are you sure you want to clear the filament information?"), wxEmptyString, wxICON_WARNING | wxOK | wxCANCEL);
auto result = msg_dlg.ShowModal();
if (result != wxID_OK)
@@ -619,6 +626,8 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
if (!obj)
return;
if (m_view_only) return; // Orca: view-only never commits
//get filament id
ams_filament_id = "";
ams_setting_id = "";
@@ -830,6 +839,7 @@ void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event)
void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
{
if (m_view_only) return; // Orca: view-only disables color editing
if(!m_is_third)
return;

View File

@@ -124,6 +124,9 @@ public:
std::string ams_setting_id;
bool m_is_third;
// Orca: view-only mode (laser/cut). When set, the dialog is inspectable but every
// editing control is disabled and no command is sent.
bool m_view_only = false;
wxString m_brand_filament;
wxString m_brand_sn;
wxString m_brand_tmp;
@@ -183,6 +186,7 @@ protected:
int m_pa_cali_select_id = 0;
// Orca: native wxComboBox on macOS (the custom ComboBox mis-renders the filament dropdown there)
#ifdef __APPLE__
wxComboBox *m_comboBox_filament;
#else

View File

@@ -272,7 +272,7 @@ void AMSSetting::create()
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10));
m_sizer_main->Add(m_static_ams_settings, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(24));
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10));
m_sizer_main->Add(m_panel_body, 1, wxBottom | wxLEFT | wxRIGHT | wxEXPAND, FromDIP(24));
m_sizer_main->Add(m_panel_body, 1, wxBOTTOM | wxLEFT | wxRIGHT | wxEXPAND, FromDIP(24)); // Orca: wxBOTTOM (REF uses the wxEdge enum by mistake)
this->SetSizer(m_sizer_main);
this->Layout();
@@ -644,7 +644,7 @@ void AMSSettingTypePanel::Update(const MachineObject* obj)
}
if (ptr->IsSwitching()) {
int display_percent = obj->get_upgrade_percent();
int display_percent = obj->get_upgrade_percent(); // Orca: read upgrade progress via the kept MachineObject accessor
if (display_percent == 100 || display_percent == 0) {
display_percent = 1;// special case, sometimes it's switching but percent is 0 or 100
}
@@ -735,6 +735,7 @@ void AMSSettingTypePanel::OnAmsTypeChanged(wxCommandEvent& event)
event.Skip();
}
// Orca: AMSSettingArrangeAMSOrder impl kept compiled-out (feature intentionally not shipped).
#if 0 /*used option*/
AMSSettingArrangeAMSOrder::AMSSettingArrangeAMSOrder(wxWindow* parent)
: wxPanel(parent)

View File

@@ -129,6 +129,7 @@ private:
AnimaIcon* m_switching_icon;
};
// Orca: the firmware AMS-reorder ("Arrange AMS Order") panel is intentionally not shipped.
#if 0
class AMSSettingArrangeAMSOrder : public wxPanel
{

File diff suppressed because it is too large Load Diff

View File

@@ -34,23 +34,28 @@
#include "Widgets/CheckBox.hpp"
#include "Widgets/ComboBox.hpp"
#include "Widgets/ScrolledWindow.hpp"
#include "Widgets/SwitchButton.hpp"
#include "Widgets/SwitchButton.hpp" // Orca: SwitchBoard (m_nozzle_btn_panel); not transitively available here
#include "Widgets/PopupWindow.hpp"
#include <wx/simplebook.h>
#include <wx/hashmap.h>
#include "slic3r/GUI/DeviceCore/DevUtil.h"
#include <optional>
#define MAPPING_ITEM_INVALID_REMAIN -1
namespace Slic3r {
// Previous definitions
namespace Slic3r
{
class DevNozzleRack;
namespace GUI { class wgtDeviceNozzleRackSelect; }
}
namespace GUI
{
class wgtDeviceNozzleRackSelect;
class wgtMsgPanel;
};
};
namespace Slic3r { namespace GUI {
namespace Slic3r {
namespace GUI {
#define AMS_TOTAL_COUNT 4
@@ -64,7 +69,8 @@ enum TrayType {
enum ShowType {
LEFT, // only show left ams and left ext
RIGHT, //only show right ams and right ext
LEFT_AND_RIGHT //show left and right ams at the same time
LEFT_AND_RIGHT, //show left and right ams at the same time
LEFT_AND_RIGHT_DYNAMIC //show all left and right at one panel when use_dynamic_switch
};
struct TrayData
@@ -85,26 +91,59 @@ struct TrayData
class MaterialItem: public wxPanel
{
protected:
int m_text_pos_x = 0;
int m_text_pos_y = -1;
bool m_dropdown_allow_painted = true;
wxString m_mapping_text;
public:
MaterialItem(wxWindow *parent, wxColour mcolour, wxString mname);
// Orca: filament_id defaults to empty so the existing 3-arg callers keep compiling;
// m_filament_id is otherwise unused here.
MaterialItem(wxWindow *parent, wxColour mcolour, wxString mname, std::string filament_id = std::string());
~MaterialItem();
wxPanel* m_main_panel;
void allow_paint_dropdown(bool flag);
void set_ams_text(const wxString& txt);
void set_ams_info(wxColour col, wxString txt,
int ctype = 0, std::vector<wxColour> cols = std::vector<wxColour>(),
bool record_back_info = false);
void set_material_cols(int ctype, const std::vector<wxColour>& cols);
void reset_ams_info();
virtual void reset_valid_info();
void set_nozzle_info(const wxString& mapped_nozzle_str);
void disable();
void enable();
void on_normal();
void on_selected();
void on_warning();
bool is_selected() const { return m_selected;}
bool is_warning() const { return m_warning;}
void msw_rescale();
protected:
void messure_size();
public:
std::string m_filament_id;
wxColour m_material_coloul;
int m_material_ctype = 0;
std::vector<wxColour> m_material_cols;
wxString m_material_name;
wxString m_mapped_nozzle_str;
//info
wxColour m_ams_coloul;
wxString m_ams_name;
// Physical nozzle(s) the print-dispatch mapping assigned to this filament (e.g. "R1", "L R").
// Empty on printers without a nozzle rack or filament switcher; when set, the card grows a row.
wxString m_mapped_nozzle_str;
int m_ams_ctype = 0;
std::vector<wxColour> m_ams_cols = std::vector<wxColour>();
//reset
wxColour m_back_ams_coloul;
wxString m_back_ams_name;
@@ -124,32 +163,17 @@ public:
bool m_match {true};
bool m_enable {true};
void msw_rescale();
void allow_paint_dropdown(bool flag);
void set_ams_info(wxColour col, wxString txt, int ctype=0, std::vector<wxColour> cols= std::vector<wxColour>(),bool record_back_info = false);
void reset_ams_info();
// Set the mapped-nozzle label ("R1", "L R", ...); grows/shrinks the card as the row appears/clears.
void set_nozzle_info(const wxString& mapped_nozzle_str);
// Size the card: base swatch height, plus one row when a nozzle label is present.
void messure_size();
void disable();
void enable();
void on_normal();
void on_selected();
void on_warning();
void paintEvent(wxPaintEvent &evt);
virtual void render(wxDC &dc);
void match(bool mat);
virtual void doRender(wxDC &dc);
virtual void reset_valid_info();
};
class MaterialSyncItem : public MaterialItem
{
public:
MaterialSyncItem(wxWindow *parent, wxColour mcolour, wxString mname);
// Orca: filament_id defaults to empty for existing 3-arg callers (see MaterialItem).
MaterialSyncItem(wxWindow *parent, wxColour mcolour, wxString mname, std::string filament_id = std::string());
~MaterialSyncItem();
int get_real_offset();
void render(wxDC &dc) override;
@@ -181,7 +205,7 @@ public:
public:
void update_data(TrayData data);
void send_event(int fliament_id);
void set_data(const wxString& tag_name, wxColour colour, wxString name, bool remain_detect, TrayData data, bool unmatch = false);
void set_data(const wxString& tag_name, wxColour colour, wxString name, bool remain_detect, TrayData data, bool unmatch = false, std::optional<wxString> tooltip_opt = std::nullopt);
void set_checked(bool checked);
void set_tray_index(wxString t_index) { m_tray_index = t_index; };
@@ -222,13 +246,38 @@ protected:
void doRender(wxDC& dc);
};
class DevIconLabel : public wxPanel
{
public:
DevIconLabel(wxWindow* parent, const wxString& icon, const wxString& label);
public:
void SetAllBackgroundColor(const wxColour& color);
Label* GetLabelItem() const { return m_label; }
void SetLabel(const wxString& label);
void SetIcon(const wxString& icon);
void Rescale();
private:
void CreateGui();
private:
Label* m_label{ nullptr };
wxString m_icon_str;
wxStaticBitmap* m_icon{ nullptr };
};
class AmsMapingPopup : public PopupWindow
{
bool m_use_in_sync_dialog = false;
bool m_ams_remain_detect_flag = false;
bool m_ext_mapping_filatype_check = true;
wxStaticText* m_title_text{ nullptr };
wgtDeviceNozzleRackSelect *m_rack_nozzle_select{nullptr};
DevIconLabel* m_flush_warning_panel;
public:
AmsMapingPopup(wxWindow *parent,bool use_in_sync_dialog = false);
~AmsMapingPopup() {};
@@ -244,11 +293,11 @@ public:
bool m_has_unmatch_filament {false};
int m_current_filament_id;
ShowType m_show_type{ShowType::RIGHT};
// Orca: set from update() — true when a Filament Track Switch is installed, which makes the
// external-spool slots un-pickable (false on any printer without a switch, so behavior is unchanged).
bool m_fila_switch_installed{false};
std::string m_tag_material;
wxScrolledWindow *m_scrolled_window{nullptr};
wxBoxSizer *m_sizer_main{nullptr};
wxBoxSizer *m_sizer_main_h{nullptr};
wxBoxSizer *m_sizer_ams_v{nullptr};
wxBoxSizer *m_sizer_ams{nullptr};
wxBoxSizer *m_sizer_ams_left{nullptr};
wxBoxSizer *m_sizer_ams_right{nullptr};
@@ -262,26 +311,27 @@ public:
MappingItem* m_right_extra_slot{nullptr};
wxPanel * m_left_marea_panel{nullptr};
wxPanel * m_right_marea_panel{nullptr};
wxPanel* m_right_marea_panel{ nullptr }; // used as right if both left and right sides shown. used as single panel if only one side shown.
wxPanel * m_left_first_text_panel{nullptr};
wxPanel * m_right_first_text_panel{nullptr};
wgtMsgPanel* m_ams_tips_msg_panel{nullptr};
wxPanel * m_split_line_panel{nullptr};
wxBoxSizer * m_left_split_ams_sizer{nullptr};
wxBoxSizer * m_right_split_ams_sizer{nullptr};
wxBoxSizer * m_right_split_ext_sizer{ nullptr };
Label * m_left_tips{nullptr};
Label * m_right_tips{nullptr};
ScalableButton* m_reset_btn{nullptr};
wxString m_single_tip_text;
wxString m_left_tip_text;
wxString m_right_tip_text;
wxString m_ams_tips_panel_text;
wxBoxSizer* m_sizer_split_ams_left;
wxBoxSizer* m_sizer_split_ams_right;
bool m_mapping_from_multi_machines {false};
// Rack nozzle manual-pick (rack printers only; hidden by default via Show(false)).
wgtDeviceNozzleRackSelect* m_rack_nozzle_select{nullptr};
Label* m_flush_warning_panel{nullptr}; // Orca: uses a plain Label for the flush warning
std::weak_ptr<DevNozzleRack> m_rack;
bool get_use_in_sync_dialog() { return m_use_in_sync_dialog; }
void set_sizer_title(wxBoxSizer *sizer, wxString text);
wxBoxSizer* create_split_sizer(wxWindow* parent, wxString text);
void set_send_win(wxWindow* win) {send_win = win;};
@@ -289,8 +339,6 @@ public:
void set_tag_texture(std::string texture);
void update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch = false, std::optional<PrintFromType> print_type = std::nullopt);
void update_rack_select(MachineObject* obj, bool use_dynamic_switch, std::optional<PrintFromType> print_type);
void update_flush_waste(MachineObject* obj);
void update_title(MachineObject* obj);
void update_items_check_state(const std::vector<FilamentInfo>& ams_mapping_result);
void update_ams_data_multi_machines();
void add_ams_mapping(std::vector<TrayData> tray_data, bool remain_detect_flag, wxWindow *container, wxBoxSizer *sizer);
@@ -304,15 +352,24 @@ public:
void paintEvent(wxPaintEvent &evt);
void set_parent_item(MaterialItem* item) {m_parent_item = item;};
void set_show_type(ShowType type) { m_show_type = type; };
// Orca: kept as a no-op for SelectMachine — its only caller passes false, which
// equals the update path's default (all slots shown).
void set_only_show_ext_spool(bool /*flag*/) {}
// Orca: dropped by the reference but still consumed by SelectMachine.
std::vector<TrayData> parse_ams_mapping(const std::map<std::string, DevAms*, NumericStrCompare>& amsList);
#ifdef __APPLE__
void on_mouse_move(wxMouseEvent &evt);
wxPopupWindow * m_tip_popup{nullptr};
Label* m_tip_label{nullptr};
#endif
using ResetCallback = std::function<void(const std::string&)>;
void reset_ams_info();
void set_reset_callback(ResetCallback callback);
void show_reset_button();
void set_material_index_str(std::string str) { m_material_index = str; }
const std::string &get_material_index_str() { return m_material_index; }
void set_only_show_ext_spool(bool flag);
public:
void msw_rescale();
@@ -320,11 +377,20 @@ public:
void EnableExtMappingFilaTypeCheck(bool to_check = true) { m_ext_mapping_filatype_check = to_check;} ;
private:
// update
void update_title(MachineObject* obj);
void update_ams_tips(MachineObject* obj);
void update_mapping_items(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch);
// events
void OnNozzleMappingSelected(wxCommandEvent& evt);
void update_flush_waste(MachineObject* obj);
private:
std::weak_ptr<DevNozzleRack> m_rack;
ResetCallback m_reset_callback{nullptr};
std::string m_material_index;
bool m_only_show_ext_spool{false};
};
class AmsMapingTipPopup : public PopupWindow

View File

@@ -0,0 +1,708 @@
/*****************************************************************//**
* \file AmsMappingPopupUpdate.cpp
* \brief do update for AmsMappingPopup
* \note moved from AmsMappingPopup.cpp to here to reduce the code size of AmsMappingPopup.cpp
*
* \author xin.zhang
* \date February 2026
*********************************************************************/
#include "AmsMappingPopup.hpp"
#include "I18N.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "libslic3r/Utils.hpp"
#include "slic3r/Utils/WxFontUtils.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Preview.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "Widgets/ProgressDialog.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/StaticBox.hpp"
#include <wx/progdlg.h>
#include <wx/clipbrd.h>
#include <wx/dcgraph.h>
#include <wx/graphics.h>
#include <miniz.h>
#include <algorithm>
#include <optional>
#include "Plater.hpp"
#include "BitmapCache.hpp"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevMappingNozzle.h"
#include "DeviceCore/DevNozzleRack.h" // Orca: full type for GetNozzleSystem()->GetNozzleRack()->IsSupported()
#include "DeviceTab/wgtDeviceNozzleSelect.h"
#include "DeviceTab/wgtMsgPanel.h"
namespace Slic3r::GUI {
static void _add_containers(const AmsMapingPopup* win,
std::list<MappingContainer*>& one_slot_containers,
const std::vector<MappingContainer*>& four_slots_containers,
wxBoxSizer* target_sizer)
{
for (auto container : four_slots_containers) { target_sizer->Add(container, 0, wxTOP, win->FromDIP(5)); }
while (!one_slot_containers.empty()) {
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
for (int i = 0; i < 3; i++) {
if (one_slot_containers.empty()) { break; }
sizer->Add(one_slot_containers.front(), 0, wxLEFT, (i == 0) ? 0 : win->FromDIP(5));
one_slot_containers.pop_front();
}
target_sizer->Add(sizer, 0, wxTOP, win->FromDIP(5));
}
}
void AmsMapingPopup::update(MachineObject* obj,
const std::vector<FilamentInfo>& ams_mapping_result,
bool use_dynamic_switch,
std::optional<PrintFromType> print_type)
{
if (!obj) { return; } // Orca: guard before use (REF logs before its null check)
BOOST_LOG_TRIVIAL(info) << "ams_mapping total count " << obj->GetFilaSystem()->GetAmsCount();
for (auto& ams_container : m_amsmapping_container_list) {
ams_container->Destroy();
}
m_amsmapping_container_list.clear();
m_amsmapping_container_sizer_list.clear();
m_mapping_item_list.clear();
/*title*/
update_title(obj);
/*ams mapping*/
update_ams_tips(obj);
update_mapping_items(obj, ams_mapping_result, use_dynamic_switch);
/*rack*/
update_rack_select(obj, use_dynamic_switch, print_type);
update_flush_waste(obj);
if (wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase_dark") {
m_reset_btn->SetName("erase_dark");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase_dark", 14).bmp());
} else if (!wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase") {
m_reset_btn->SetName("erase");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase", 14).bmp());
}
size_t extruder_num = obj->GetExtderSystem()->GetTotalExtderCount();
if (extruder_num == 1) {
m_left_marea_panel->Hide();
m_left_extra_slot->Hide();
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
m_right_extra_slot->Show();
m_right_extra_slot->Enable(true);
m_right_split_ext_sizer->Show(true);
set_sizer_title(m_right_split_ams_sizer, _L("AMS"));
} else if (extruder_num > 1) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_left_marea_panel->Show(false);
m_left_extra_slot->Show(false);
m_right_extra_slot->Show(false);
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
m_right_split_ext_sizer->Show(false);
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
} else {
m_left_marea_panel->Show();
m_left_extra_slot->Show();
m_split_line_panel->Show();
m_right_marea_panel->Show();
m_right_extra_slot->Show();
m_right_split_ext_sizer->Show(true);
m_left_tips->SetLabel(m_left_tip_text);
m_right_tips->SetLabel(m_right_tip_text);
set_sizer_title(m_left_split_ams_sizer, _L("Left AMS"));
set_sizer_title(m_right_split_ams_sizer, _L("Right AMS"));
if (m_show_type == ShowType::LEFT) {
m_left_marea_panel->Enable(true);
m_left_extra_slot->Enable(true);
m_right_marea_panel->Enable(false);
m_right_extra_slot->Enable(false);
if (m_use_in_sync_dialog) {
m_left_tips->SetLabel(m_single_tip_text);
m_right_tips->SetLabel("");
}
} else if (m_show_type == ShowType::RIGHT) {
m_left_marea_panel->Enable(false);
m_left_extra_slot->Enable(false);
m_right_marea_panel->Enable(true);
m_right_extra_slot->Enable(true);
if (m_use_in_sync_dialog) {
// m_right_tips->SetLabel(m_single_tip_text);
m_left_tips->SetLabel("");
}
} else if (m_show_type == ShowType::LEFT_AND_RIGHT) {
m_left_marea_panel->Enable(true);
m_left_extra_slot->Enable(true);
m_right_marea_panel->Enable(true);
m_right_extra_slot->Enable(true);
if (m_use_in_sync_dialog) {
m_left_tips->SetLabel(m_single_tip_text);
m_right_tips->SetLabel("");
}
} else if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_left_marea_panel->Show(false);
m_left_extra_slot->Show(false);
m_right_extra_slot->Show(false);
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
m_right_split_ext_sizer->Show(false);
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
}
}
}
Layout();
Fit();
Refresh();
}
// Orca: the kept fila model has no tray-level ams_type; the owning AMS type is passed in
// (DevAms::GetAmsType() for AMS trays, DevAmsType::EXT_SPOOL for the virtual/ext slots).
static std::optional<TrayData> sGetTrayData(DevAmsTray* tray,
const std::string& ams_id_str,
DevAmsType ams_type)
{
int ams_id = 0;
int tray_id = 0;
try {
ams_id = std::stoi(ams_id_str);
tray_id = atoi(tray->id.c_str());
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":[error] invalid ams_id or tray_id: " << e.what();
return std::nullopt;
}
TrayData td;
td.ams_id = ams_id;
if (ams_type != DevAmsType::EXT_SPOOL) {
td.slot_id = tray_id;
} else {
td.slot_id = 0;
}
if (ams_type == DevAmsType::AMS ||
ams_type == DevAmsType::AMS_LITE ||
ams_type == DevAmsType::N3F) {
td.id = ams_id * 4 + tray_id;
} else if (ams_type == DevAmsType::N3S) {
td.id = ams_id + tray_id;
} else if(ams_type == DevAmsType::EXT_SPOOL){
td.id = tray_id;
}
if (ams_type != EXT_SPOOL && !tray->is_exists) {
td.type = EMPTY;
} else {
if (!tray->is_tray_info_ready()) {
td.type = THIRD;
} else {
td.type = NORMAL;
td.remain = tray->remain;
td.colour = DevAmsTray::decode_color(tray->color);
td.name = tray->get_display_filament_type();
td.filament_type = tray->get_filament_type();
td.ctype = tray->ctype;
for (const auto& col : tray->cols) {
td.material_cols.push_back(DevAmsTray::decode_color(col));
}
}
}
return td;
}
void AmsMapingPopup::update_mapping_items(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch)
{
std::list<MappingContainer*> left_one_slot_containers;
std::list<MappingContainer*> right_one_slot_containers;
std::vector<MappingContainer*> left_four_slots_containers;
std::vector<MappingContainer*> right_four_slot_containers;
const auto& ams_list = obj->GetFilaSystem()->GetAmsList();
for (auto ams_iter = ams_list.begin(); ams_iter != ams_list.end(); ams_iter++) {
const auto& extruder_set = ams_iter->second->GetBindedExtruderSet();
if (extruder_set.size() <= 0) {
continue;
}
wxPanel* target_panel = nullptr;
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
target_panel = m_right_marea_panel;
} else {
int extruder_id = *extruder_set.begin();
if (extruder_id == MAIN_EXTRUDER_ID) {
target_panel = m_right_marea_panel;
} else if (extruder_id == DEPUTY_EXTRUDER_ID) {
target_panel = m_left_marea_panel;
}
}
if (!target_panel) {
continue;
}
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
auto ams_mapping_item_container = new MappingContainer(target_panel, ams_iter->second->GetDisplayName(), ams_iter->second->GetSlotCount());
ams_mapping_item_container->SetName(target_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
BOOST_LOG_TRIVIAL(trace) << "ams_mapping ams id " << ams_iter->first.c_str();
DevAms* ams_group = ams_iter->second;
auto ams_type = ams_group->GetAmsType();
std::vector<TrayData> tray_datas;
std::map<std::string, DevAmsTray*>::const_iterator tray_iter;
for (tray_iter = ams_group->GetTrays().cbegin(); tray_iter != ams_group->GetTrays().cend(); tray_iter++) {
const auto& td_opt = sGetTrayData(tray_iter->second, ams_group->GetAmsId(), ams_type);
if (td_opt.has_value()) {
tray_datas.push_back(td_opt.value());
}
}
ams_mapping_item_container->Show();
add_ams_mapping(tray_datas, obj->GetFilaSystem()->IsDetectRemainEnabled(), ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
if (target_panel == m_right_marea_panel) {
if (ams_mapping_item_container->get_slots_num() == 1) {
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
right_four_slot_containers.push_back(ams_mapping_item_container);
}
} else if (target_panel == m_left_marea_panel) {
if (ams_mapping_item_container->get_slots_num() == 1) {
left_one_slot_containers.push_back(ams_mapping_item_container);
} else {
left_four_slots_containers.push_back(ams_mapping_item_container);
}
}
}
for (int i = obj->vt_slot.size() - 1; i >= 0; i--) {
DevAmsTray* tray_data = &obj->vt_slot[i];
const auto& td_opt = sGetTrayData(tray_data, tray_data->id, DevAmsType::EXT_SPOOL);
if (!td_opt.has_value()) {
continue;
}
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
const auto& shown_name = td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID ? "Ext-R" : "Ext-L";
auto ams_mapping_item_container = new MappingContainer(m_right_marea_panel, shown_name, 1);
ams_mapping_item_container->SetName(m_right_marea_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
std::vector<TrayData> tray_datas;
tray_datas.push_back(td_opt.value());
add_ams_mapping(tray_datas, false, ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
if (td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID) {
m_right_extra_slot->send_win = send_win;
add_ext_ams_mapping(td_opt.value(), m_right_extra_slot);
} else if (td_opt->ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
m_left_extra_slot->send_win = send_win;
add_ext_ams_mapping(td_opt.value(), m_left_extra_slot);
}
}
}
_add_containers(this, left_one_slot_containers, left_four_slots_containers, m_sizer_ams_basket_left);
_add_containers(this, right_one_slot_containers, right_four_slot_containers, m_sizer_ams_basket_right);
if (m_show_type != ShowType::LEFT_AND_RIGHT_DYNAMIC){
m_left_split_ams_sizer->Show(!left_one_slot_containers.empty() || !left_four_slots_containers.empty());
m_right_split_ams_sizer->Show(!right_one_slot_containers.empty() || !right_four_slot_containers.empty());
} else {
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
}
update_items_check_state(ams_mapping_result);
}
void AmsMapingPopup::update_ams_data_multi_machines()
{
m_mapping_from_multi_machines = true;
std::vector<TrayData> tray_datas;
for (int i = 0; i < 4; ++i) {
TrayData td;
td.id = i;
td.type = EMPTY;
td.colour = wxColour(166, 169, 170);
td.name = "";
td.filament_type = "";
td.ctype = 0;
tray_datas.push_back(td);
}
for (auto& ams_container : m_amsmapping_container_list) {
ams_container->Destroy();
}
m_amsmapping_container_list.clear();
m_amsmapping_container_sizer_list.clear();
m_mapping_item_list.clear();
if (wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase_dark") {
m_reset_btn->SetName("erase_dark");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase_dark", 14).bmp());
} else if (!wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase") {
m_reset_btn->SetName("erase");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase", 14).bmp());
}
size_t nozzle_nums = 1;
m_show_type = ShowType::RIGHT;
m_left_marea_panel->Hide();
m_left_extra_slot->Hide();
// m_left_marea_panel->Show();
m_right_marea_panel->Show();
set_sizer_title(m_right_split_ams_sizer, _L("AMS"));
// m_right_tips->SetLabel(m_single_tip_text);
m_right_extra_slot->Hide();
m_left_extra_slot->Hide();
/*ams*/
bool has_left_ams = false, has_right_ams = false;
std::list<MappingContainer*> left_one_slot_containers;
std::list<MappingContainer*> right_one_slot_containers;
std::vector<MappingContainer*> left_four_slots_containers;
std::vector<MappingContainer*> right_four_slot_containers;
for (int i = 0; i < 1; i++) {
int ams_indx = 0;
int ams_type = 1;
int nozzle_id = 0;
if (ams_type >= 1 || ams_type <= 3) { // 1:ams 2:ams-lite 3:n3f
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
auto ams_mapping_item_container = new MappingContainer(nozzle_id == 0 ? m_right_marea_panel : m_left_marea_panel, "AMS-1", 4);
ams_mapping_item_container->SetName(nozzle_id == 0 ? m_right_marea_panel->GetName() : m_left_marea_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
m_has_unmatch_filament = false;
ams_mapping_item_container->Show();
add_ams_mapping(tray_datas, false, ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
if (nozzle_id == 0) {
has_right_ams = true;
if (ams_mapping_item_container->get_slots_num() == 1) {
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
right_four_slot_containers.push_back(ams_mapping_item_container);
}
} else if (nozzle_id == 1) {
has_left_ams = true;
if (ams_mapping_item_container->get_slots_num() == 1) {
left_one_slot_containers.push_back(ams_mapping_item_container);
} else {
left_four_slots_containers.push_back(ams_mapping_item_container);
}
}
} else if (ams_type == 4) { // 4:n3s
}
}
_add_containers(this, left_one_slot_containers, left_four_slots_containers, m_sizer_ams_basket_left);
_add_containers(this, right_one_slot_containers, right_four_slot_containers, m_sizer_ams_basket_right);
m_left_split_ams_sizer->Show(has_left_ams);
m_right_split_ams_sizer->Show(has_right_ams);
//update_items_check_state(ams_mapping_result);
Layout();
Fit();
}
void AmsMapingPopup::update_title(MachineObject* obj)
{
const auto& full_config = wxGetApp().preset_bundle->full_config();
size_t nozzle_nums = full_config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values.size();
if (nozzle_nums > 1) {
m_split_line_panel->Show();
if (m_show_type == ShowType::LEFT) {
wxString nozzle_name = obj ? _L(DevPrinterConfigUtil::get_toolhead_display_name(
obj->printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase)) : _L("left nozzle");
m_title_text->SetLabelText(wxString::Format(_L("Please select the filament installed on the %s."), nozzle_name));
return;
} else if (m_show_type == ShowType::RIGHT) {
wxString nozzle_name = obj ? _L(DevPrinterConfigUtil::get_toolhead_display_name(
obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase)) : _L("right nozzle");
m_title_text->SetLabelText(wxString::Format(_L("Please select the filament installed on the %s."), nozzle_name));
return;
} else if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_title_text->SetLabelText(_L("Please select the filament."));
return;
}
} else if (nozzle_nums == 1) {
m_split_line_panel->Hide();
}
m_title_text->SetLabelText(_L("Nozzle"));
}
void AmsMapingPopup::update_ams_tips(MachineObject* obj)
{
const auto& full_config = wxGetApp().preset_bundle->full_config();
size_t nozzle_nums = full_config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values.size();
if (m_ams_tips_msg_panel) {
m_ams_tips_msg_panel->Clear();
if (nozzle_nums == 2 && m_show_type != ShowType::LEFT_AND_RIGHT) {
m_ams_tips_msg_panel->AddMessage(_L("To learn about the filaments matching rules."),
"#FF6F00", ""); // Orca: dropped vendor help link
}
if (obj && obj->GetFilaSwitch()->IsInstalled()) {
const auto& msg = _L("External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
m_ams_tips_msg_panel->AddMessage(msg, "#FF6F00", "");
}
m_ams_tips_msg_panel->Layout();
m_ams_tips_msg_panel->Fit();
m_ams_tips_msg_panel->Show(m_ams_tips_msg_panel->GetMessageCount() > 0);
}
}
void AmsMapingPopup::update_rack_select(MachineObject* obj, bool use_dynamic_switch, std::optional<PrintFromType> print_from_type)
{
// Orca: MachineObject has no GetNozzleRack() convenience; route through the nozzle system instead.
m_rack = obj ? obj->GetNozzleSystem()->GetNozzleRack() : nullptr;
bool show_rack_select_area = false;
if (!m_mapping_from_multi_machines && !m_use_in_sync_dialog &&
obj && obj->GetNozzleSystem()->GetNozzleRack()->IsSupported() &&
print_from_type.has_value() && print_from_type.value() == PrintFromType::FROM_NORMAL) {
const auto& nozzle_pos_vec = obj->get_nozzle_mapping_result()->GetMappedNozzlePosVecByFilaId(m_current_filament_id);
m_rack_nozzle_select->UpdatSelectedNozzles(obj->GetNozzleSystem()->GetNozzleRack(), nozzle_pos_vec, use_dynamic_switch, print_from_type);
show_rack_select_area = true;
}
if (show_rack_select_area != m_rack_nozzle_select->IsShown()) {
m_right_tip_text = show_rack_select_area ? _L("Select Filament && Hotends") : _L("Select Filament");
m_right_tips->SetLabel(m_right_tip_text);
m_rack_nozzle_select->Show(show_rack_select_area);
Layout();
Fit();
}
}
void AmsMapingPopup::update_items_check_state(const std::vector<FilamentInfo>& ams_mapping_result)
{
/*update check states*/
if (m_parent_item) {
auto update_item_check_state = [&ams_mapping_result, this](MappingItem* item) {
if (item) {
for (const auto& mapping_res : ams_mapping_result) {
if (mapping_res.id == this->m_current_filament_id) {
if (mapping_res.ams_id == std::to_string(item->m_ams_id) &&
mapping_res.slot_id == std::to_string(item->m_slot_id)) {
item->set_checked(true);
} else {
item->set_checked(false);
}
return;
}
}
item->set_checked(false);
}
};
update_item_check_state(m_left_extra_slot);
update_item_check_state(m_right_extra_slot);
for (auto mapping_item : m_mapping_item_list) {
update_item_check_state(mapping_item);
}
}
}
void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data,
bool remain_detect_flag,
wxWindow* container,
wxBoxSizer* sizer)
{
sizer->Add(0, 0, 0, wxLEFT, FromDIP(6));
for (auto i = 0; i < tray_data.size(); i++) {
// set button
MappingItem* m_mapping_item = new MappingItem(container);
m_mapping_item->send_win = send_win;
m_mapping_item->m_ams_id = tray_data[i].ams_id;
m_mapping_item->m_slot_id = tray_data[i].slot_id;
// Orca: transition_tridid here has no total-extruder-count overload; label the virtual (ext)
// slots Ext-R / Ext-L inline in the two-nozzle left+right views (matches that overload's result).
if ((m_show_type == ShowType::LEFT_AND_RIGHT || m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC)
&& (tray_data[i].id == VIRTUAL_TRAY_MAIN_ID || tray_data[i].id == VIRTUAL_TRAY_DEPUTY_ID)) {
m_mapping_item->set_tray_index(tray_data[i].id == VIRTUAL_TRAY_MAIN_ID ? wxString("Ext-R") : wxString("Ext-L"));
} else {
m_mapping_item->set_tray_index(wxGetApp().transition_tridid(tray_data[i].id));
}
m_mapping_item->SetSize(wxSize(FromDIP(48), FromDIP(60)));
m_mapping_item->SetMinSize(wxSize(FromDIP(48), FromDIP(60)));
m_mapping_item->SetMaxSize(wxSize(FromDIP(48), FromDIP(60)));
// traversal if can pick the item or not
bool can_pick_the_item = true;
std::optional<wxString> item_tooltip_msg;
// check filament area
if (m_use_in_sync_dialog) {
if (can_pick_the_item) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id);
if (!can_pick_the_item) {
item_tooltip_msg = _L(
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
}
}
}
}
else {
if (can_pick_the_item) {
// LEFT_AND_RIGHT — the fallback when a filament has no entry in the
// nozzle map — keeps both panels pickable.
auto parent = container->GetParent();
if (parent == m_left_marea_panel) {
can_pick_the_item = (m_show_type == ShowType::LEFT || m_show_type == ShowType::LEFT_AND_RIGHT);
} else if (parent == m_right_marea_panel) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id);
if (!can_pick_the_item) {
item_tooltip_msg = _L(
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
}
} else if (m_show_type != ShowType::RIGHT && m_show_type != ShowType::LEFT_AND_RIGHT) {
can_pick_the_item = false;
}
}
}
}
// check filament type
if (can_pick_the_item) {
if (tray_data[i].type == NORMAL && !is_match_material(tray_data[i].filament_type)){
can_pick_the_item = false;
} else if(tray_data[i].type == EMPTY){
// Mapping to an empty slot is only allowed from the multi-machine send page,
// which maps onto EMPTY placeholder trays.
can_pick_the_item = m_mapping_from_multi_machines;
}
}
wxColour display_color;
if (can_pick_the_item) {
display_color = tray_data[i].type == NORMAL ? tray_data[i].colour : wxColour(0xCE, 0xCE, 0xCE);
} else {
display_color = wxColour(0xEE, 0xEE, 0xEE);
}
wxString display_name;
if (tray_data[i].type == THIRD) {
display_name = "?";
} else if (tray_data[i].type == EMPTY) {
display_name = "-";
} else {
display_name = tray_data[i].name;
}
m_mapping_item->set_data(m_tag_material, display_color, display_name, remain_detect_flag, tray_data[i], !can_pick_the_item, item_tooltip_msg);
m_mapping_item->Bind(wxEVT_LEFT_DOWN, [this, can_pick_the_item, m_mapping_item](wxMouseEvent& e) {
if (can_pick_the_item) {
m_mapping_item->send_event(m_current_filament_id);
Dismiss();
}
});
sizer->Add(0, 0, 0, wxRIGHT, FromDIP(6));
sizer->Add(m_mapping_item, 0, wxTOP, FromDIP(1));
m_mapping_item_list.push_back(m_mapping_item);
}
}
void AmsMapingPopup::add_ext_ams_mapping(TrayData tray_data, MappingItem* item)
{
#ifdef __APPLE__
m_mapping_item_list.push_back(item);
#endif
// set button
if (tray_data.type == NORMAL) {
if (is_match_material(tray_data.filament_type)) {
item->set_data(m_tag_material, tray_data.colour, tray_data.name, false, tray_data);
} else {
item->set_data(m_tag_material, m_ext_mapping_filatype_check ? wxColour(0xEE, 0xEE, 0xEE) : tray_data.colour, tray_data.name, false, tray_data, true);
m_has_unmatch_filament = true;
}
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
if (m_ext_mapping_filatype_check && !is_match_material(tray_data.filament_type)) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
// temp
if (tray_data.type == EMPTY) {
item->set_data(m_tag_material, wxColour(0xCE, 0xCE, 0xCE), "-", false, tray_data);
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
// third party
if (tray_data.type == THIRD) {
item->set_data(m_tag_material, tray_data.colour, "?", false, tray_data);
//item->set_data(wxColour(0xCE, 0xCE, 0xCE), "?", tray_data);
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
item->set_tray_index("Ext");
}
} // namespace Slic3r::GUI

View File

@@ -813,7 +813,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
m_simplebook->SetSelection(0);
auto m_bind_job = std::make_unique<BindJob>(
m_machine_info->get_dev_id(), m_machine_info->get_dev_ip(), m_machine_info->bind_sec_link, m_machine_info->bind_ssdp_version);
m_machine_info->get_dev_id(), m_machine_info->get_dev_ip(), m_machine_info->get_show_printer_type(), m_machine_info->bind_sec_link, m_machine_info->bind_ssdp_version);
if (m_machine_info && (m_machine_info->get_printer_series() == PrinterSeries::SERIES_X1)) {
m_bind_job->set_improved(false);

View File

@@ -102,7 +102,7 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(0, 0, wxALL, 0);
}
// custom IP camera
// Orca: custom IP camera source — lets the user point Live Video at any camera URL (Orca feature; not in the reference)
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
@@ -132,7 +132,7 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
auto text = _L("Show \"Live Video\" guide page.");
wxBoxSizer* link_sizer = new wxBoxSizer(wxVERTICAL);
@@ -479,7 +479,7 @@ void CameraPopup::OnLeftUp(wxMouseEvent &event)
//hyper link
auto h_rect = vcamera_guide_link->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > h_rect.x && mouse_pos.y > h_rect.y && mouse_pos.x < (h_rect.x + vcamera_guide_link->GetSize().x) && mouse_pos.y < (h_rect.y + vcamera_guide_link->GetSize().y)) {
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
wxLaunchDefaultBrowser(url);
}
}

View File

@@ -60,6 +60,19 @@ list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceCore/DevUtil.cpp
GUI/DeviceCore/DevUtilBackend.h
GUI/DeviceCore/DevUtilBackend.cpp
GUI/DeviceCore/DevAxis.h
GUI/DeviceCore/DevAxis.cpp
GUI/DeviceCore/DevAxisCtrl.cpp
GUI/DeviceCore/DevCalib.h
GUI/DeviceCore/DevCalib.cpp
GUI/DeviceCore/DevChamber.h
GUI/DeviceCore/DevChamber.cpp
GUI/DeviceCore/DevChamberCtrl.cpp
GUI/DeviceCore/DevStatus.h
GUI/DeviceCore/DevStatus.cpp
GUI/DeviceCore/DevUpgrade.h
GUI/DeviceCore/DevUpgrade.cpp
GUI/DeviceCore/DevUpgradeCtrl.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)

View File

@@ -0,0 +1,25 @@
#include "DevAxis.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r
{
void DevAxis::ParseAxis(const json& print_json)
{
DevJsonValParser::ParseVal(print_json, "home_flag", m_home_flag);
if (print_json.contains("fun")) {
const std::string& fun = print_json["fun"].get<std::string>();
m_is_support_mqtt_homing = DevUtil::get_flag_bits(fun, 32);
m_is_support_mqtt_axis_ctrl = DevUtil::get_flag_bits(fun, 38);
}
}
bool DevAxis::IsArchCoreXY() const
{
return m_owner->get_printer_arch() == PrinterArch::ARCH_CORE_XY;
}
} // namespace Slic3r

View File

@@ -0,0 +1,39 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
namespace Slic3r {
class MachineObject;
class DevAxis
{
public:
static std::shared_ptr<DevAxis> Create(MachineObject *obj) { return std::shared_ptr<DevAxis>( new DevAxis(obj)); }
virtual ~DevAxis() = default;
public:
bool IsAxisAtHomeX() const { return m_home_flag == 0 ? true : (m_home_flag & 1) == 1; }
bool IsAxisAtHomeY() const { return m_home_flag == 0 ? true : ((m_home_flag >> 1) & 1) == 1; }
bool IsAxisAtHomeZ() const { return m_home_flag == 0 ? true : ((m_home_flag >> 2) & 1) == 1; }
bool IsArchCoreXY() const;
public:
void ParseAxis(const json &print_json);
int Ctrl_GoHome();
int Ctrl_Axis(std::string axis, double unit = 1.0f, double input_val = 1.0f, int speed = 3000); // xyz e
protected:
DevAxis(MachineObject *obj) noexcept : m_owner(obj) {}
private:
MachineObject *m_owner = nullptr;
int m_home_flag = 0; // bit0:X, bit1:Y, bit2:Z
bool m_is_support_mqtt_axis_ctrl = false;
bool m_is_support_mqtt_homing = false;
};
} // namespace Slic3r

View File

@@ -0,0 +1,60 @@
#include "DevAxis.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
namespace Slic3r
{
int DevAxis::Ctrl_GoHome()
{
if (m_is_support_mqtt_homing) {
json j;
j["print"]["command"] = "back_to_center";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return m_owner->publish_json(j);
}
// gcode command
return m_owner->is_in_printing() ? m_owner->publish_gcode("G28 X\n") : m_owner->publish_gcode("G28 \n");
}
int DevAxis::Ctrl_Axis(std::string axis, double unit, double input_val, int speed)
{
if (m_is_support_mqtt_axis_ctrl) {
int dir = input_val > 0 ? 1 : -1;
if (!IsArchCoreXY()) {
if (axis.compare("Y") == 0 || axis.compare("Z") == 0) {
dir = -dir;
}
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return m_owner->publish_json(j);
}
double value = input_val;
if (!IsArchCoreXY()) {
if (axis.compare("Y") == 0 || axis.compare("Z") == 0) {
value = -1.0 * input_val;
}
}
char cmd[256];
if (axis.compare("X") == 0 || axis.compare("Y") == 0 || axis.compare("Z") == 0) {
sprintf(cmd, "M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %s%0.1f F%d\nM1002 pop_ref_mode\nM211 R\n", axis.c_str(), value * unit, speed);
} else if (axis.compare("E") == 0) {
sprintf(cmd, "M83 \nG0 %s%0.1f F%d\n", axis.c_str(), value * unit, speed);
} else {
return -1;
}
// Orca: strip analytics telemetry (matches MachineObject::command_axis_control)
return m_owner->publish_gcode(cmd);
}
} // namespace Slic3r

View File

@@ -0,0 +1,337 @@
#include <boost/log/trivial.hpp>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/UserNotification.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <wx/dir.h>
#include "fast_float/fast_float.h"
#include "DevCalib.h"
#include "DevDefs.h"
#include "DevFilaSystem.h"
#include "DevConfig.h"
namespace Slic3r {
static float string_to_float(const std::string &str_value)
{
float value = 0.0;
fast_float::from_chars(str_value.c_str(), str_value.c_str() + str_value.size(), value);
return value;
}
static NozzleVolumeType convert_to_nozzle_type(const std::string &str)
{
if (str.size() < 8) {
assert(false && "invalid nozzle info");
return NozzleVolumeType::nvtStandard;
}
if (str[1] == 'S')
return NozzleVolumeType::nvtStandard;
else if (str[1] == 'H')
return NozzleVolumeType::nvtHighFlow;
else if (str[1] == 'U')
return NozzleVolumeType::nvtTPUHighFlow;
// Orca: no nvtE3DHighFlow in Orca's NozzleVolumeType; map 'B' to Standard
else
return NozzleVolumeType::nvtStandard;
}
static float get_number_flexible(const json& j, const std::string& key, float def = 0.0f)
{
if (!j.contains(key)) return def;
const auto& v = j[key];
if (v.is_number_float()) return v.get<float>();
if (v.is_number_integer()) return static_cast<float>(v.get<int>());
if (v.is_string()) return string_to_float(v.get<std::string>());
return def;
}
void from_json(const json& j, PACalibResult& cali) {
cali.extruder_id = j.value("extruder_id", 0);
cali.nozzle_volume_type = convert_to_nozzle_type(j.value("nozzle_id", "HS00-0.4"));
cali.tray_id = j.value("tray_id",0);
cali.ams_id = j.value("ams_id",0);
cali.slot_id = j.value("slot_id",0);
cali.cali_idx = j.value("cali_idx",-1);
cali.nozzle_pos_id = j.value("nozzle_pos",-1);
cali.nozzle_diameter = get_number_flexible(j, "nozzle_diameter", 0.4f);
cali.nozzle_sn = j.value("nozzle_sn","");
cali.filament_id = j.value("filament_id","");
cali.setting_id = j.value("setting_id","");
cali.name = j.value("name","");
cali.k_value = get_number_flexible(j, "k_value", 0.0f);
cali.n_coef = get_number_flexible(j, "n_coef", 0.0f);
cali.confidence = j.value("confidence", 0);
}
void from_json(const json& j, FlowRatioCalibResult& cali)
{
cali.tray_id = j.value("tray_id", 0);
cali.nozzle_diameter = string_to_float(j.value("nozzle_diameter", ""));
cali.filament_id = j.value("filament_id", "");
cali.setting_id = j.value("setting_id", "");
cali.flow_ratio = string_to_float(j.value("flow_ratio", ""));
cali.confidence = j.value("confidence", 0);
}
void DevCalib::ParseCalibVersion(const json& j, DevCalib* system)
{
if(system) system->m_calib_version = j.value("cali_version", -1);
}
bool DevCalib::IsVersionExpired() const
{
if (m_last_calib_version.has_value())
return m_last_calib_version.value() != m_calib_version;
else
return true;
}
void DevCalib::ParseSupportNewAutoCalib(int flag, DevCalib* system)
{
if(system) system->m_support_new_auto_cali = flag;
}
void DevCalib::RequestPAResult()
{
m_pa_results_status = CalibStatus::REQUEST;
}
void DevCalib::ResetPAResult()
{
m_pa_calib_results.clear();
m_pa_results_status = CalibStatus::IDLE;
}
int DevCalib::RequestPAHistory(const PACalibExtruderInfo &calib_info)
{
m_pa_calib_tab.clear();
m_pa_table_status = CalibStatus::REQUEST;
return GetOwner()->command_get_pa_calibration_tab(calib_info);
}
void DevCalib::ResetPAHistory()
{
m_pa_calib_tab.clear();
m_pa_table_status = CalibStatus::IDLE;
}
void DevCalib::RequestFlowRateResult()
{
m_flow_results_status = CalibStatus::REQUEST;
}
void DevCalib::ResetFlowRateResult()
{
m_flow_ratio_results.clear();
m_flow_results_status = CalibStatus::IDLE;
}
void calib_fail_message(MachineObject* obj, std::string cali_mode, std::string reason){
wxString info;
if (reason == "invalid nozzle_diameter" || reason == "nozzle_diameter is not supported") {
info = _L("This calibration does not support the currently selected nozzle diameter");
} else if (reason == "invalid handle_flowrate_cali param") {
info = _L("Current flowrate cali param is invalid");
} else if (reason == "nozzle_diameter is not matched") {
info = _L("Selected diameter and machine diameter do not match");
} else if (reason == "generate auto filament cali gcode failure") {
info = _L("Failed to generate cali gcode");
} else {
info = wxString(reason);
}
GUI::wxGetApp().push_notification(obj, info, _L("Calibration error"), UserNotificationStyle::UNS_WARNING_CONFIRM);
BOOST_LOG_TRIVIAL(info) << cali_mode << " result fail, reason = " << reason;
}
void DevCalib::ExtrusionCalibSetParse(const json & jj){
int tray_id = jj.value("tray_id", -1);
auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
int ams_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].first : -1;
int slot_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].second : -1;
if(tray_id == VIRTUAL_TRAY_MAIN_ID) {
GetOwner()->vt_slot[MAIN_EXTRUDER_ID].k = jj.value("k_value", GetOwner()->vt_slot[MAIN_EXTRUDER_ID].k);
GetOwner()->vt_slot[MAIN_EXTRUDER_ID].n = jj.value("n_value", GetOwner()->vt_slot[MAIN_EXTRUDER_ID].n);
}else{
auto tray_item = GetOwner()->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (tray_item) {
tray_item->k = jj.value("k_value", tray_item->k);
tray_item->n = jj.value("n_coef", tray_item->n);
}
}
GetOwner()->extrusion_cali_set_tray_id = tray_id;
GetOwner()->extrusion_cali_set_hold_start = std::chrono::system_clock::now();
}
/* calib select ack parse */
void DevCalib::ExtrusionCalibSelectParse(const json &jj){
try{
int tray_id = jj.value("tray_id", -1);
auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
int default_ams_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].first : -1;
int default_slot_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].second : -1;
int ams_id = jj.value("ams_id", default_ams_id);
int slot_id = jj.value("slot_id", default_slot_id);
BOOST_LOG_TRIVIAL(trace) << "extrusion_cali_sel: illegal ams_id = " << ams_id << "slot_id = " << slot_id;
std::vector<DevAmsTray> &vt_slot = GetOwner()->vt_slot;
if (ams_id == VIRTUAL_TRAY_MAIN_ID && vt_slot.size() > 0) {
vt_slot[MAIN_EXTRUDER_ID].cali_idx = jj.value("cali_idx", vt_slot[MAIN_EXTRUDER_ID].cali_idx);
vt_slot[MAIN_EXTRUDER_ID].set_hold_count();
} else if (ams_id == VIRTUAL_TRAY_DEPUTY_ID && vt_slot.size() > 1) {
vt_slot[DEPUTY_EXTRUDER_ID].cali_idx = jj.value("cali_idx", vt_slot[DEPUTY_EXTRUDER_ID].cali_idx);
vt_slot[DEPUTY_EXTRUDER_ID].set_hold_count();
} else {
auto tray_item = GetOwner()->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (tray_item) {
tray_item->cali_idx = jj.value("cali_idx", tray_item->cali_idx);
tray_item->set_hold_count();
}
}
} catch(...){
}
}
void DevCalib::ExtrusionCalibGetTableParse(const json &jj){
if (GetPAHistoryStatus() == CalibStatus::REQUEST ) {
m_pa_table_status = CalibStatus::WAITING;
/* request success */
if (!(jj.contains("result") && jj.contains("reason") && jj["result"].get<std::string>() == "fail")) {
SyncCalibVersion();
m_pa_table_status = CalibStatus::FINISHED;
}
try{
json filaments_json;
if(jj.contains("filaments"))
{
/* fill item->nozzle_diameter with command->nozzle_diameter */
filaments_json = jj["filaments"];
for (auto &f : filaments_json) {
if (!f.contains("nozzle_diameter") && jj.contains("nozzle_diameter")) {
f["nozzle_diameter"] = jj["nozzle_diameter"];
}
}
}
m_pa_calib_tab = filaments_json.get<std::vector<PACalibResult>>();
/* filter invalid pa_calib_tab */
m_pa_calib_tab.erase(std::remove_if(m_pa_calib_tab.begin(), m_pa_calib_tab.end(), [](auto &res) { return res.k_value < 0.0f || res.k_value > 10.0f; }), m_pa_calib_tab.end());
if (m_pa_calib_tab.empty()) { BOOST_LOG_TRIVIAL(info) << "empty pa calib history"; }
}catch(...){
m_pa_calib_tab.clear();
BOOST_LOG_TRIVIAL(error) << "pa calib history missing fields, current json:\n "<< jj.dump();
}
// notify cali history to update
}
}
void DevCalib::ExtrusionCalibGetResultParse(const json &jj)
{
m_pa_results_status = CalibStatus::WAITING;
if (!(jj.contains("result") && jj.contains("reason") && jj["result"].get<std::string>() == "fail" && jj.contains("err_code"))) {
m_pa_results_status = CalibStatus::FINISHED;
}
try {
json filaments_json;
if(jj.contains("filaments"))
{
/* fill item->nozzle_diameter with command->nozzle_diameter */
filaments_json = jj["filaments"];
for (auto &f : filaments_json) {
if (!f.contains("nozzle_diameter") && jj.contains("nozzle_diameter") ) {
f["nozzle_diameter"] = jj["nozzle_diameter"];
}
if (IsSupportNewAutoCali()) {
auto ams_id = f.value("ams_id", 0);
auto slot_id = f.value("slot_id", 0);
if(f.contains("tray_id")){
// Orca: Orca's DevFilaSystem has no GetTrayIdByAmsSlotId; mirror its
// semantics via a reverse lookup over GetTrayIndexMap() (correct for
// non-4-slot AMS layouts, unlike a fixed ams_id*4+slot_id formula).
int mapped_tray_id = -1;
const auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
for (const auto& item : tray_ams_slot_map) {
if (item.second.first == ams_id && item.second.second == slot_id) {
mapped_tray_id = item.first;
break;
}
}
f["tray_id"] = mapped_tray_id;
}
}
}
}
m_pa_calib_results = filaments_json.get<std::vector<PACalibResult>>();
m_pa_calib_results.erase(std::remove_if(m_pa_calib_results.begin(), m_pa_calib_results.end(), [](auto &res) { return res.k_value < 0.0f || res.k_value > 10.0f; }), m_pa_calib_results.end());
if (m_pa_calib_results.empty()) { BOOST_LOG_TRIVIAL(info) << "empty pa calib result"; }
} catch (...) {
m_pa_calib_results.clear();
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "pa calibration results missing fileds, current json: \n"<<jj.dump();
}
}
void DevCalib::FlowrateGetResultParse(const json &jj){
m_flow_results_status = CalibStatus::FINISHED;
m_flow_ratio_results.clear();
if(!jj.contains("filaments")) return;
try {
m_flow_ratio_results = jj["filaments"].get<std::vector<FlowRatioCalibResult>>();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "flow ratio calibration results missing fileds, current json:\n"<<jj.dump();
}
}
void DevCalib::ParseV1_0(const json &jj, DevCalib *system, bool key_field_only)
{
if(!jj.contains("command")) return;
if (jj["command"].get<std::string>() == "extrusion_cali" || jj["command"].get<std::string>() == "flowrate_cali") {
if (jj.contains("result")) {
if (jj["result"].get<std::string>() == "success") {
} else if (jj["result"].get<std::string>() == "fail") {
std::string cali_mode = jj["command"].get<std::string>();
std::string reason = jj["reason"].get<std::string>();
calib_fail_message(system->GetOwner(), cali_mode, reason);
}
}
} else if (jj["command"].get<std::string>() == "extrusion_cali_set") {
system->ExtrusionCalibSetParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_sel") {
system->ExtrusionCalibSelectParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_get") {
system->ExtrusionCalibGetTableParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_get_result") {
system->ExtrusionCalibGetResultParse(jj);
} else if (jj["command"].get<std::string>() == "flowrate_get_result" && !key_field_only) {
system->FlowrateGetResultParse(jj);
}
}
} // namespace Slic3r

View File

@@ -0,0 +1,127 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
#include "libslic3r/calib.hpp" // Orca: lowercase filename (Linux case-sensitive)
namespace Slic3r {
class MachineObject;
enum class CalibStatus{
IDLE = 0,
REQUEST,
WAITING,
FINISHED,
};
enum class ManualPaCaliMethod {
PA_LINE = 0,
PA_PATTERN,
};
class DevCalib
{
protected:
bool m_support_new_auto_cali{false};
int m_calib_version {-1};
std::optional<int> m_last_calib_version;
public:
DevCalib(MachineObject *obj) : m_owner(obj){};
MachineObject* GetOwner() const {return m_owner; };
void RequestPAResult();
CalibStatus GetPAResultStatus() const {return m_pa_results_status;}
bool IsPAResultReady() const { return m_pa_results_status == CalibStatus::FINISHED;}
void ResetPAResult();
/* calib history */
int RequestPAHistory(const PACalibExtruderInfo &calib_info);
CalibStatus GetPAHistoryStatus() const {return m_pa_table_status;}
bool IsPAHistoryReady() const { return m_pa_table_status == CalibStatus::FINISHED;}
void ResetPAHistory();
void RequestFlowRateResult();
CalibStatus GetFlowRateResultStatus() const {return m_flow_results_status;}
bool IsFlowRateReady() const { return m_flow_results_status == CalibStatus::FINISHED;}
void ResetFlowRateResult();
int GetCalibVersion() const {return m_calib_version;}
void SyncCalibVersion() { if (IsVersionInited()) { m_last_calib_version = m_calib_version; } }
void ResetCalibVersion() {m_last_calib_version.reset();}
bool IsVersionExpired() const;
bool IsVersionInited() const { return m_calib_version > -1;}
bool IsSupportNewAutoCali() const {return m_support_new_auto_cali;}
public:
void SetStashCalibFinished(bool finished) {m_calib_finished = finished;}
bool GetStashCalibFinished() { return m_calib_finished;}
void SetStashFlowRatio(float ratio) { m_flow_ratio = ratio;}
float GetStashFlowRatio() const {return m_flow_ratio;}
FlowRatioCalibrationType GetFlowRatioCalibType() {return m_flow_ratio_calibration_type;}
void SetFlowRatioCalibType(const FlowRatioCalibrationType &type) { m_flow_ratio_calibration_type = type; }
ManualPaCaliMethod GetManualPaCalibMethod() {return m_manual_pa_cali_method;}
void SetManualPaCalibMethod(const ManualPaCaliMethod& method) { m_manual_pa_cali_method = method;}
NozzleDiameterType GetSelectedNozzleDiameter() {return m_selected_nozzle_diameter;}
void SetSelectedNozzleDiameter(const NozzleDiameterType& diameter) { m_selected_nozzle_diameter = diameter;}
std::vector<CaliPresetInfo> GetSelectedCalibPreset() {return m_selected_calib_preset;}
void ResetSelectedCalibPreset() { m_selected_calib_preset.clear();}
void SetSelectedCalibPreset(const std::vector<CaliPresetInfo>& preset) { m_selected_calib_preset = preset;}
std::vector<PACalibResult> GetPAHistory() const {return m_pa_calib_tab; }
std::vector<PACalibResult> GetPAResult() const {return m_pa_calib_results; }
std::vector<FlowRatioCalibResult> GetFlowRatioResult() const {return m_flow_ratio_results; }
protected:
void ExtrusionCalibSetParse(const json &jj);
void ExtrusionCalibSelectParse(const json &jj);
void ExtrusionCalibGetTableParse(const json &jj);
void ExtrusionCalibGetResultParse(const json &jj);
void FlowrateGetResultParse(const json &jj);
private:
MachineObject* m_owner{nullptr};
std::vector<PACalibResult> m_pa_calib_tab;
std::vector<PACalibResult> m_pa_calib_results;
std::vector<FlowRatioCalibResult> m_flow_ratio_results;
CalibStatus m_pa_results_status{CalibStatus::IDLE};
CalibStatus m_pa_table_status{CalibStatus::IDLE};
CalibStatus m_flow_results_status{CalibStatus::IDLE};
FlowRatioCalibrationType m_flow_ratio_calibration_type{FlowRatioCalibrationType::COMPLETE_CALIBRATION};
ManualPaCaliMethod m_manual_pa_cali_method{ManualPaCaliMethod::PA_LINE};
// calibration page selected info
// 1: record when start calibration in preset page
// 2: reset when start calibration in start page
// 3: save tray_id, filament_id, setting_id, and name, nozzle_dia
// std::vector<CaliPresetInfo> selected_cali_preset;
NozzleDiameterType m_selected_nozzle_diameter{NozzleDiameterType::NONE_DIAMETER_TYPE};
std::vector<CaliPresetInfo> m_selected_calib_preset;
// stash calibrating info
float m_flow_ratio { 0.0 };
bool m_calib_finished{false};
public:
static void ParseCalibVersion(const json& j, DevCalib* system);
static void ParseSupportNewAutoCalib(int flag, DevCalib* system);
static void ParseV1_0(const json& print_json, DevCalib* system, bool key_field_only);
};
} // namespace Slic3r

View File

@@ -0,0 +1,50 @@
#include "DevChamber.h"
#include "DevConfig.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
bool DevChamber::HasChamber() const { return m_owner->GetConfig()->HasChamber(); }
bool DevChamber::SupportChamberTempDisplay() const { return m_owner->GetConfig()->SupportChamberTempDisplay();}
bool DevChamber::SupportChamberEdit() const { return m_owner->GetConfig()->SupportChamberEdit(); }
int DevChamber::GetChamberTempEditMin() const { return m_owner->GetConfig()->GetChamberTempEditMin(); }
int DevChamber::GetChamberTempEditMax() const { return m_owner->GetConfig()->GetChamberTempEditMax(); }
int DevChamber::GetChamberTempSwitchHeat() const { return m_owner->GetConfig()->GetChamberTempSwitchHeat(); }
void DevChamber::ParseChamber(const json &print_json)
{
ParseChamberV1_0(print_json);
ParseChamberV2_0(print_json);
}
void DevChamber::ParseChamberV1_0(const json &print_json)
{
DevJsonValParser::ParseVal(print_json, "chamber_temper", m_temp);
DevJsonValParser::ParseVal(print_json, "ctt", m_temp_target);
}
void DevChamber::ParseChamberV2_0(const json &print_json)
{
if (print_json.contains("device")) {
const json &device_jj = print_json["device"];
if (device_jj.contains("ctc")) {
const json &ctc = device_jj["ctc"];
int state = DevUtil::get_flag_bits(ctc["state"].get<int>(), 0, 4);
if (ctc.contains("info")) {
const json &info = ctc["info"];
m_temp = DevUtil::get_flag_bits(info["temp"].get<int>(), 0, 16);
m_temp_target = DevUtil::get_flag_bits(info["temp"].get<int>(), 16, 16);
}
}
}
}
} // namespace Slic3r

View File

@@ -0,0 +1,44 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
namespace Slic3r {
class MachineObject;
class DevChamber
{
public:
static std::shared_ptr<DevChamber> Create(MachineObject *obj) { return std::shared_ptr<DevChamber>( new DevChamber(obj)); }
public: // getter
bool HasChamber() const;
bool SupportChamberTempDisplay() const;
bool SupportChamberEdit() const;
int GetChamberTempEditMin() const;
int GetChamberTempEditMax() const;
int GetChamberTempSwitchHeat() const;
float GetChamberTemp() const { return m_temp; };
float GetChamberTempTarget() const { return m_temp_target; };
public:
// setter
void ParseChamber(const json &print_json);
void ParseChamberV1_0(const json& print_json);
void ParseChamberV2_0(const json& print_json);
// control
int CtrlSetChamberTemp(int temp);
protected:
DevChamber(MachineObject *obj) : m_owner(obj) {}
private:
float m_temp = 0.0f;
float m_temp_target = 0.0f;
MachineObject* m_owner = nullptr;
};
} // namespace Slic3r

View File

@@ -0,0 +1,16 @@
#include "DevChamber.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
int DevChamber::CtrlSetChamberTemp(int temp)
{
json j;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["command"] = "set_ctt";
j["print"]["ctt_val"] = temp;
return m_owner->publish_json(j, 1);
}
} // namespace Slic3r

View File

@@ -8,17 +8,30 @@ using namespace nlohmann;
namespace Slic3r
{
bool DevConfig::SupportChamberTempDisplay() const
{
if (!m_support_chamber_temp_display.has_value()) {
return m_has_chamber;
}
return m_support_chamber_temp_display.value();
}
void DevConfig::ParseConfig(const json& print_json)
{
ParseChamberConfig(print_json);
ParsePrintOptionsConfig(print_json);
ParseCalibrationConfig(print_json);
}
void DevConfig::ParseChamberConfig(const json& print_json)
{
DevJsonValParser::ParseVal(print_json, "support_chamber", m_has_chamber);
if (print_json.contains("support_chamber_temp_display")) {
m_support_chamber_temp_display = DevJsonValParser::GetVal<bool>(print_json, "support_chamber_temp_display");
}
DevJsonValParser::ParseVal(print_json, "support_chamber_temp_edit", m_support_chamber_edit);
if (m_support_chamber_edit)
{

View File

@@ -3,8 +3,9 @@
#include "slic3r/Utils/json_diff.hpp"
#include <wx/string.h>
#include <limits>
#include <limits>
#include <optional>
namespace Slic3r
{
@@ -22,6 +23,7 @@ public:
public:
// chamber
bool HasChamber() const { return m_has_chamber; }
bool SupportChamberTempDisplay() const;
bool SupportChamberEdit() const { return m_support_chamber_edit; }
int GetChamberTempEditMin() const { return m_chamber_temp_edit_min; }
int GetChamberTempEditMax() const { return m_chamber_temp_edit_max; }
@@ -57,6 +59,7 @@ private:
/*configure vals*/
// chamber
bool m_has_chamber = false; // whether the machine has a chamber
std::optional<bool> m_support_chamber_temp_display;
bool m_support_chamber_edit = false;
int m_chamber_temp_edit_min = 0;
int m_chamber_temp_edit_max = 60;

View File

@@ -1,16 +1,41 @@
#include "DevConfigUtil.h"
#include <algorithm>
#include <cctype>
#include <wx/dir.h>
#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>
#include "../I18N.hpp"
using namespace nlohmann;
namespace Slic3r
{
// Translation markers for xgettext extraction.
// These strings are configured in printers/*.json (tool_head_display_names)
// and passed to _L() at runtime. xgettext cannot scan dynamic strings,
// so we mark them here with L() for extraction into .pot file.
// This block is never executed at runtime.
static void _toolhead_translation_markers()
{
// Dynamic toolhead display names from JSON config — xgettext cannot scan these
L("Main Extruder"); L("Main extruder"); L("main extruder");
L("Auxiliary Extruder"); L("Auxiliary extruder"); L("auxiliary extruder");
L("Left Extruder"); L("Left extruder"); L("left extruder");
L("Right Extruder"); L("Right extruder"); L("right extruder");
L("Main Nozzle"); L("Main nozzle"); L("main nozzle");
L("Auxiliary Nozzle"); L("Auxiliary nozzle"); L("auxiliary nozzle");
L("Left Nozzle"); L("Left nozzle"); L("left nozzle");
L("Right Nozzle"); L("Right nozzle"); L("right nozzle");
L("Main Hotend"); L("Main hotend"); L("main hotend");
L("Auxiliary Hotend"); L("Auxiliary hotend"); L("auxiliary hotend");
L("Left Hotend"); L("Left hotend"); L("left hotend");
L("Right Hotend"); L("Right hotend"); L("right hotend");
// standalone position words (short_name=true runtime results)
L("main"); L("auxiliary");
L("Main"); L("Auxiliary");
L("left"); L("right");
L("Left"); L("Right");
}
std::string DevPrinterConfigUtil::m_resource_file_path = "";
@@ -91,7 +116,7 @@ std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_
{
if (has_nozzle_rack)
{
const auto &rack_vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image_nozzle_rack");
const auto &rack_vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image_nozzle_rack") ;
if (!rack_vec.empty())
{
if (ext_id >= 0 && static_cast<size_t>(ext_id) < rack_vec.size())
@@ -111,7 +136,6 @@ std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, const std::string& key)
{
std::vector<std::string> filaments;
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try
@@ -134,6 +158,25 @@ std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, cons
return std::string();
}
std::vector<std::string> DevPrinterConfigUtil::get_fan_text_params(const std::string& type_str, const std::string& key)
{
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try {
json jj;
if (json_file.is_open()) {
json_file >> jj;
if (jj.contains("00.00.00.00")) {
json const& printer = jj["00.00.00.00"];
if (printer.contains("fan") && printer["fan"].contains(key)) {
return printer["fan"][key].get<std::vector<std::string>>();
}
}
}
} catch (...) {}
return std::vector<std::string>();
}
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, int airduct_mode, int airduct_func, int submode)
{
std::vector<std::string> filaments;
@@ -181,6 +224,36 @@ std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, int
return std::string();
}
std::string DevPrinterConfigUtil::get_fan_mode_text(const std::string& type_str, int airduct_mode, const std::string& key)
{
std::vector<std::string> filaments;
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try {
json jj;
if (json_file.is_open()) {
json_file >> jj;
if (jj.contains("00.00.00.00")) {
json const& printer = jj["00.00.00.00"];
if (!printer.contains("fan")) {
return std::string();
}
json const& fan_item = printer["fan"];
const auto& airduct_mode_str = std::to_string(airduct_mode);
if (!fan_item.contains(airduct_mode_str)) {
return std::string();
}
if (fan_item[airduct_mode_str].contains(key)) {
return fan_item[airduct_mode_str][key].get<std::string>();
}
}
}
} catch (...) {}
return std::string();
}
std::map<std::string, std::vector<std::string>> DevPrinterConfigUtil::get_all_subseries(std::string type_str)
{
std::map<std::string, std::vector<std::string>> subseries;
@@ -284,18 +357,29 @@ std::string DevPrinterConfigUtil::get_toolhead_display_name(
{ ToolHeadComponent::Hotend, "hotend" }
};
const int case_index = static_cast<int>(name_case);
const std::string role_key = std::to_string(ext_id);
int case_index = static_cast<int>(name_case); // 0, 1, 2
// Try to read from printer config json
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
std::string role_key = std::to_string(ext_id); // "0" or "1"
const std::string& comp_key = comp_keys.at(component);
std::string result;
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
if (!names_json.is_null() && names_json.contains(role_key) && names_json[role_key].contains(comp_key)) {
if (!names_json.is_null()
&& names_json.contains(role_key)
&& names_json[role_key].contains(comp_key))
{
auto& arr = names_json[role_key][comp_key];
if (arr.is_array() && case_index < static_cast<int>(arr.size()))
if (arr.is_array() && case_index < static_cast<int>(arr.size())) {
result = arr[case_index].get<std::string>();
}
}
// Orca: models that ship no tool_head_display_names (e.g. H2D/H2S) must still get distinct
// per-extruder labels, so fall back to the previous programmatic Left/Right construction.
// Engages only when the config lookup above yields nothing, so models that ship the key
// behave exactly like the reference.
if (result.empty()) {
const std::string side = ext_id == DEPUTY_EXTRUDER_ID ? "Left" : "Right";
const std::string component_name = component == ToolHeadComponent::Extruder ? "Extruder" :
@@ -307,13 +391,15 @@ std::string DevPrinterConfigUtil::get_toolhead_display_name(
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
}
// short_name: return only the role prefix (e.g. "Main" from "Main Nozzle")
if (short_name) {
auto sp = result.find(' ');
if (sp != std::string::npos)
if (sp != std::string::npos) {
result = result.substr(0, sp);
}
}
return result;
}
};
};

View File

@@ -11,6 +11,8 @@
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <cctype>
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
@@ -23,6 +25,20 @@
namespace Slic3r
{
/// Toolhead component type (extruder / nozzle / hotend)
enum class ToolHeadComponent {
Extruder,
Nozzle,
Hotend
};
/// Name case style for toolhead display names
enum class ToolHeadNameCase {
TitleCase = 0, // [0] "Main Extruder" — panel titles, section headers
SentenceCase = 1, // [1] "Main extruder" — static box labels
LowerCase = 2 // [2] "main extruder" — inline text, sentence concatenation
};
class dePrinterConfigFactory
{
public:
@@ -30,17 +46,6 @@ public:
~dePrinterConfigFactory() = default;
};
enum class ToolHeadComponent {
Extruder,
Nozzle,
Hotend
};
enum class ToolHeadNameCase {
TitleCase = 0,
SentenceCase = 1,
LowerCase = 2
};
class DevPrinterConfigUtil
{
@@ -74,14 +79,19 @@ public:
static std::string get_printer_use_ams_type(std::string type_str) { return get_value_from_config<std::string>(type_str, "use_ams_type"); }
static std::string get_printer_ams_img(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_use_ams_image"); }
static std::string get_printer_ext_img(const std::string& type_str, int pos);//printer_ext_image
static bool support_ams_fila_change_abort(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_ams_filament_change_abort"); }
static std::string get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack = false);
/*fan*/
static std::string get_fan_text(const std::string& type_str, const std::string& key);
static std::string get_fan_text(const std::string& type_str, const std::string& key);
static std::vector<std::string> get_fan_text_params(const std::string& type_str, const std::string& key);
static std::string get_fan_text(const std::string& type_str, int airduct_mode, int airduct_func, int submode);
static std::string get_fan_mode_text(const std::string& type_str, int airduct_mode, const std::string& key);
/*extruder*/
static bool get_printer_can_set_nozzle(std::string type_str) { return get_value_from_config<bool>(type_str, "enable_set_nozzle_info"); }// can set nozzle from studio
/*toolhead display names (extruder / nozzle / hotend)*/
static std::string get_toolhead_display_name(
const std::string& type_str,
int ext_id,
@@ -90,10 +100,15 @@ public:
bool short_name = false);
/*print job*/
static bool support_print_check_firmware_for_tpu_left(std::string type_str){ return get_value_from_config<bool>(type_str, "print", "support_print_check_firmware_for_tpu_left"); }
static bool support_user_first_setup_tpu_check(std::string type_str){ return get_value_from_config<bool>(type_str, "print", "support_user_first_setup_tpu_check"); }
static std::string support_user_first_setup_tpu_check_url(std::string type_str){ return get_value_from_config<std::string>(type_str, "print", "support_user_first_setup_tpu_check_url"); }
static bool support_ams_ext_mix_print(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_ams_ext_mix_print"); }
static bool support_print_time_estimate_warning(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_print_time_estimate_warning"); }
/*calibration*/
static std::vector<std::string> get_unsupport_auto_cali_filaments(std::string type_str) { return get_value_from_config<std::vector<std::string>>(type_str, "auto_cali_not_support_filaments"); }
static bool support_disable_cali_flow_type(std::string type_str) { return get_value_from_config<bool>(type_str,"support_disable_cali_flow_type"); }
/*detection*/
static bool support_wrapping_detection(const std::string& type_str) { return get_value_from_config<bool>(type_str, "support_wrapping_detection"); }
@@ -103,6 +118,10 @@ public:
/*print check*/
static bool support_print_check_extension_fan_f000_mounted(const std::string& type_str) { return get_value_from_config<bool>(type_str, "print", "support_print_check_extension_fan_f000_mounted"); }
static std::string air_print_detection_position(const std::string &type_str) { return get_value_from_config<std::string>(type_str, "air_print_detection_position"); }
/*bed*/
static int get_bed_temperature_limit(const std::string &type_str) { return get_value_from_config<int>(type_str, "print", "bed_temperature_limit"); }
public:
template<typename T>
@@ -126,7 +145,7 @@ public:
}
}
}
catch (...) { assert(0 && "get_value_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
catch (...) { assert(0 && "get_value_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
return T();
};
@@ -178,7 +197,7 @@ public:
}
}
}
catch (...) { assert(0 && "get_json_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
catch (...) { assert(0 && "get_json_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
return nlohmann::json();
}
@@ -187,35 +206,25 @@ private:
};
/*special transform*/
static std::string _parse_printer_type(const std::string& type_str)
static std::string _parse_printer_type(const std::string &type_str)
{
if (type_str.compare("3DPrinter-X1") == 0)
{
return "BL-P002";
}
else if (type_str.compare("3DPrinter-X1-Carbon") == 0)
{
return "BL-P001";
}
else if (type_str.compare("BL-P001") == 0)
{
return type_str;
}
else if (type_str.compare("BL-P002") == 0)
{
return type_str;
}
else
{
std::string result = DevPrinterConfigUtil::get_printer_type(type_str);
if (!result.empty())
{
return result;
}
}
static std::unordered_map<std::string, std::string> s_printer_type_lazy_cache;
if (type_str == "3DPrinter-X1") return "BL-P002";
if (type_str == "3DPrinter-X1-Carbon") return "BL-P001";
if (type_str == "BL-P001" || type_str == "BL-P002") return type_str;
auto cache_it = s_printer_type_lazy_cache.find(type_str);
if (cache_it != s_printer_type_lazy_cache.end()) {
return cache_it->second;
}
std::string result = DevPrinterConfigUtil::get_printer_type(type_str);
if (!result.empty()) {
s_printer_type_lazy_cache[type_str] = result;
return result;
}
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Unsupported printer type: " << type_str;
return type_str;
}
};// namespace Slic3r
};// namespace Slic3r

View File

@@ -10,12 +10,21 @@
#pragma once
#include <string>
#include <utility>
#include <nlohmann/json.hpp>
// Reserved for future usage
#define DEV_RESERVED_FOR_FUTURE(...) /* stripped */
// Previous definitions
namespace Slic3r
{
class MachineObject;
}
enum PrinterArch
{
ARCH_CORE_XY,
ARCH_I3,
ARCH_CORE_XY,// move hotbed
ARCH_I3,//move z
};
enum PrinterSeries
@@ -36,6 +45,7 @@ enum AmsStatusMain
AMS_STATUS_MAIN_RFID_IDENTIFYING = 0x02,
AMS_STATUS_MAIN_ASSIST = 0x03,
AMS_STATUS_MAIN_CALIBRATION = 0x04,
AMS_STATUS_MAIN_COLD_PULL = 0x07,
AMS_STATUS_MAIN_SELF_CHECK = 0x10,
AMS_STATUS_MAIN_DEBUG = 0x20,
AMS_STATUS_MAIN_UNKNOWN = 0xFF,
@@ -51,10 +61,6 @@ enum DevAmsType : int
AMS_LITE_MIXED = 5, // AMS-Lite for N9
};
// Device-numbered filament-change step codes. Newer firmware reports the exact change-step
// sequence through the AMS (ams.cfs), keyed by these codes, instead of the client hardcoding
// steps per model. Distinct from the legacy GUI-side Slic3r::GUI::FilamentStep enum.
// STEP_CHECK_POSITION and STEP_CONFIRM_EXTRUDED share code 0x08 by device design.
enum DevFilamentStep
{
STEP_IDLE = 0x00,
@@ -83,37 +89,35 @@ enum DevFilamentStep
#define VIRTUAL_AMS_MAIN_ID_STR "255"
#define VIRTUAL_AMS_DEPUTY_ID_STR "254"
// Tray index offset for the AMS-Lite-mixed unit (A2L / N9). Its 4 trays occupy
// global tray indices 24..27.
#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24
// Tray index offsets for different AMS types
#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24 // Offset for AMS_LITE_MIXED tray index (24)
#define INVALID_AMS_TEMPERATURE std::numeric_limits<float>::min()
// (ams_id, slot_id) pair.
using DevAmsSlotId = std::pair<int, int>;
/* Extruder*/
#define MAIN_EXTRUDER_ID 0
#define DEPUTY_EXTRUDER_ID 1
#define UNIQUE_EXTRUDER_ID MAIN_EXTRUDER_ID
#define INVALID_EXTRUDER_ID -1
/* Logical extruder ids (multi-nozzle). */
// see PartPlate::get_physical_extruder_by_logical_extruder
#define LOGIC_UNIQUE_EXTRUDER_ID 0
#define LOGIC_L_EXTRUDER_ID 0
#define LOGIC_R_EXTRUDER_ID 1
// <ams_id, slot_id>
using DevAmsSlotId = std::pair<int, int>;
/* Nozzle*/
enum NozzleFlowType
enum NozzleFlowType : int
{
NONE_FLOWTYPE,
S_FLOW,
H_FLOW,
U_FLOW, // TPU 1.75 High Flow (device-reported; maps to nvtTPUHighFlow)
U_FLOW, // TPU 1.75 High Flow
E_FLOW, // E3D High Flow
};
// Discrete nozzle diameters reported by the device.
/* 0.2mm 0.4mm 0.6mm 0.8mm */
enum NozzleDiameterType : int
{
NONE_DIAMETER_TYPE,
@@ -135,7 +139,7 @@ enum DevPrintingSpeedLevel
};
/*Upgrade*/
enum class DevFirmwareUpgradingState : int
enum class DevFirmwareUpgradeState : int
{
DC = -1,
UpgradingUnavaliable = 0,
@@ -144,6 +148,10 @@ enum class DevFirmwareUpgradingState : int
UpgradingFinished = 3
};
// Orca: DeviceManager/UpgradePanel keep the Orca spelling of this enum; the alias to the
// reference name is a permanent compatibility surface.
using DevFirmwareUpgradingState = DevFirmwareUpgradeState;
class devPrinterUtil
{
public:
@@ -157,9 +165,6 @@ public:
namespace GUI
{
// Print source. Defined here (rather than in SelectMachine.hpp) so the shared device-mapping
// GUI headers — AmsMappingPopup, wgtDeviceNozzleSelect — can name it without pulling in
// SelectMachine.hpp, which would create an include cycle.
enum PrintFromType
{
FROM_NORMAL,
@@ -169,8 +174,6 @@ enum PrintFromType
};// namespace Slic3r
// A nozzle requirement of the sliced plate (or an installed nozzle's identity), compared in the
// pre-print slicing-vs-installed nozzle checks.
struct NozzleDef
{
float nozzle_diameter;
@@ -190,4 +193,7 @@ template<> struct std::hash<NozzleDef>
size_t h2 = std::hash<int>{}(v.nozzle_flow_type);
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
};
};
};
// key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)}
using ExtruderNozzleInfos = std::unordered_map<int, std::unordered_map<NozzleDef, int>>;

View File

@@ -271,7 +271,7 @@ namespace Slic3r
system->m_current_loading_extder_id = INVALID_EXTRUDER_ID;
if (!system->m_extders[MAIN_EXTRUDER_ID].m_star.ams_id.empty())
{
system->m_current_busy_for_loading = (system->m_extders[MAIN_EXTRUDER_ID].m_snow == system->m_extders[MAIN_EXTRUDER_ID].m_star);
system->m_current_busy_for_loading = (system->m_extders[MAIN_EXTRUDER_ID].m_snow != system->m_extders[MAIN_EXTRUDER_ID].m_star);
if (system->m_current_busy_for_loading)
{
system->m_current_loading_extder_id = MAIN_EXTRUDER_ID;

View File

@@ -22,6 +22,7 @@ struct DevAmsSlotInfo
std::string slot_id;
public:
bool operator!=(const DevAmsSlotInfo& other) const { return !(*this == other); }
bool operator==(const DevAmsSlotInfo& other) const { return ams_id == other.ams_id && slot_id == other.slot_id;}
};

View File

@@ -46,20 +46,10 @@ void Slic3r::DevFan::converse_to_duct(bool is_suppt_part_fun, bool is_suppt_aux_
}
}
static std::string _get_string_from_fantype(int type)
{
switch (type) {
case 1: return "cooling_fan";
case 2: return "big_cooling_fan";
case 3: return "chamber_fan";
default: return "";
}
return "";
}
// Old protocol
int Slic3r::DevFan::command_control_fan(int fan_type, int val)
{
// Orca: strip analytics telemetry
std::string gcode = (boost::format("M106 P%1% S%2% \n") % (int) fan_type % (val)).str();
return m_owner->publish_gcode(gcode);
}
@@ -166,8 +156,6 @@ void Slic3r::DevFan::ParseV3_0(const json &device)
m_air_duct_data.modes.clear();
m_air_duct_data.parts.clear();
m_air_duct_data.curren_mode = device["airduct"]["modeCur"].get<int>();
const json &airduct = device["airduct"];
if (airduct.contains("modeCur")) { m_air_duct_data.curren_mode = airduct["modeCur"].get<int>(); }
if (airduct.contains("subMode")) { m_air_duct_data.m_sub_mode = airduct["subMode"].get<int>(); }
@@ -193,7 +181,7 @@ void Slic3r::DevFan::ParseV3_0(const json &device)
}
}
if (AIR_DUCT(mode.id) == AIR_DUCT::AIR_DUCT_EXHAUST) { continue; } /*STUDIO-12796*/
if (AIR_DUCT(mode.id) == AIR_DUCT::AIR_DUCT_EXHAUST) { continue; }
m_air_duct_data.modes[mode.id] = mode;
}
}

View File

@@ -11,8 +11,6 @@ class MachineObject;
enum AirDuctType { AIR_FAN_TYPE, AIR_DOOR_TYPE };
typedef std::function<void(const json &)> CommandCallBack;
enum AIR_FUN : int {
FAN_HEAT_BREAK_0_IDX = 0,
FAN_COOLING_0_AIRDOOR = 1,
@@ -91,6 +89,13 @@ public:
bool IsSupportCoolingFilter() const { return m_support_cooling_filter; }
bool IsCoolingFilerOn() const { return m_sub_mode == 1; }
bool IsExaustFanExit() const
{
for (auto &p : parts) {
if (p.id == int(AIR_FUN::FAN_CHAMBER_0_IDX)) return true;
}
return false;
}
};
class DevFan

View File

@@ -1,5 +1,6 @@
#pragma once
#include <optional>
#include <unordered_map> // Orca: for m_firmwares / GetSuppotedFirmwares() — the unordered_map AMSSetting.cpp mirrors and iterates
#include <nlohmann/json.hpp>
#include "DevCtrl.h"
@@ -75,7 +76,7 @@ public:
DevAmsSystemIdx GetCurrentFirmwareIdxSel() const { return m_current_firmware_sel.m_firmare_idx; };
DevAmsSystemIdx GetCurrentFirmwareIdxRun() const { return m_current_firmware_run.m_firmare_idx; };
std::unordered_map<int, DevAmsSystemFirmware> GetSuppotedFirmwares() const { return m_firmwares;};
std::unordered_map<int, DevAmsSystemFirmware> GetSuppotedFirmwares() const { return m_firmwares;}; // Orca: unordered (mirrored by AMSSetting.cpp)
bool IsSwitching() const { return m_status == "SWITCHING";};
bool IsIdle() const { return m_status == "IDLE";};
@@ -94,7 +95,7 @@ private:
DevAmsSystemFirmware m_current_firmware_run;
DevAmsSystemFirmware m_current_firmware_sel;
std::unordered_map<int, DevAmsSystemFirmware> m_firmwares;
std::unordered_map<int, DevAmsSystemFirmware> m_firmwares; // Orca: unordered (mirrored by AMSSetting.cpp)
DevCtrlInfo m_ctrl_switching;
};

View File

@@ -9,19 +9,6 @@ namespace Slic3r {
DevFilaSwitch::DevFilaSwitch(MachineObject *owner) { m_owner = owner; }
bool DevFilaSwitch::IsReady() const
{
if (!m_is_installed) { return false; }
const auto& ams_list = m_owner->GetFilaSystem()->GetAmsList();
for (const auto& ams_item : ams_list) {
if (ams_item.second->GetBindedExtruderSet().empty()) { return false; }
if (!ams_item.second->GetSwitcherPos().has_value()) { return false; }
}
return true;
}
void DevFilaSwitch::Reset()
{
m_is_installed = false;
@@ -34,6 +21,24 @@ void DevFilaSwitch::Reset()
m_cali_status = CaliStatus::CALI_IDLE;
}
bool DevFilaSwitch::IsReady() const
{
if (!m_is_installed) {
BOOST_LOG_TRIVIAL(debug) << "[FilaSwitch] IsReady: not installed";
return false;
}
const auto& ams_list = m_owner->GetFilaSystem()->GetAmsList();
for (const auto& ams_item : ams_list) {
if (ams_item.second->GetBindedExtruderSet().size() == 0) { return false; }
if (!ams_item.second->GetSwitcherPos().has_value()) {
return false;
}
}
return true;
}
std::optional<DevAmsTray> DevFilaSwitch::GetInA_Slot() const
{
auto ams_id_opt = GetInA_SlotId();
@@ -128,4 +133,4 @@ void DevFilaSwitch::ParseFilaSwitchInfo(const nlohmann::json &print_jj)
}
}
}; // namespace Slic3r
}; // namespace Slic3r

View File

@@ -1,7 +1,6 @@
#pragma once
#include "DevDefs.h"
#include <optional>
#include <nlohmann/json.hpp>
namespace Slic3r
{
@@ -10,16 +9,11 @@ class MachineObject;
class DevExtder;
class DevAmsTray;
// Filament Track Switch (H2-family hardware).
// The filament blacklist consumes IsInstalled() for the `has_filament_switch`
// rule (e.g. PLA Silk). m_is_installed defaults to false and is only raised when
// the device reports the switch over MQTT (aux bit 29), so the rule stays inert on
// every printer that does not report an installed switch.
class DevFilaSwitch
{
public:
enum class CaliStatus : int
{
{
CALI_IDLE = 0,
CALI_STEPING = 1,
};
@@ -48,10 +42,6 @@ public:
public:
bool IsInstalled() const { return m_is_installed; };
// Ready once installed and every AMS has resolved both its extruder binding and its
// switcher track position from the device. The send dialog and sidebar sync UX only
// treat the switch as usable when this is true.
bool IsReady() const;
std::optional<bool> IsInA_HasFilament() const { return m_in_a_has_filament; };
@@ -88,4 +78,4 @@ private:
CaliStatus m_cali_status = CaliStatus::CALI_IDLE;
};
};
};

View File

@@ -270,10 +270,10 @@ private:
AmsType m_ams_type = AmsType::AMS;
std::string m_ams_id;
int m_ext_id;//extruder id
// Orca: keeps the legacy single m_ext_id for existing consumers and carries the
// switch-aware binding alongside it (BambuStudio stores only the set). Without a Filament
// Track Switch the set is {m_ext_id}; with one installed the device binds both extruders and
// records which input track (A/B) feeds this AMS.
// Orca: pull-mode / multi-vendor agent model — keeps the legacy single m_ext_id for existing
// consumers and carries the switch-aware binding alongside it. Without a Filament Track Switch
// the set is {m_ext_id}; with one installed the device binds both extruders and records which
// input track (A/B) feeds this AMS.
std::set<int> m_binded_extruder_set;
std::optional<DevFilaSwitch::SwitchPos> m_binded_switcher_pos;
bool m_exist = false;

View File

@@ -28,8 +28,8 @@ int DevFilaSystem::CtrlAmsStartDryingHour(int ams_id,
jj_command["print"]["command"] = "ams_filament_drying";
jj_command["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
jj_command["print"]["ams_id"] = ams_id;
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::OnTime);
jj_command["print"]["filament"] = filament_type;
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::OnTime); // Orca: cast scoped enum for json
jj_command["print"]["filament"] = filament_type; // Orca: fix comma-operator typo
jj_command["print"]["temp"] = tag_temp;
jj_command["print"]["duration"] = tag_duration_hour;
jj_command["print"]["humidity"] = 0;
@@ -45,8 +45,8 @@ int DevFilaSystem::CtrlAmsStopDrying(int ams_id) const
jj_command["print"]["command"] = "ams_filament_drying";
jj_command["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
jj_command["print"]["ams_id"] = ams_id;
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::Off);
jj_command["print"]["filament"] = "";
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::Off); // Orca: cast scoped enum for json
jj_command["print"]["filament"] = ""; // Orca: fix comma-operator typo
jj_command["print"]["temp"] = 0;
jj_command["print"]["duration"] = 0;
jj_command["print"]["humidity"] = 0;

View File

@@ -1,4 +1,7 @@
#pragma once
#include "DevFirmware.h"
#include "slic3r/Utils/json_diff.hpp"
#include <nlohmann/json.hpp>
#include <wx/string.h>
#include "slic3r/Utils/json_diff.hpp"
@@ -8,6 +11,7 @@ namespace Slic3r {
//Previous definitions
class MachineObject;
// Orca: firmware-type enum consumed by DeviceManager/UpgradePanel; no reference-side equivalent yet
enum PrinterFirmwareType
{
FIRMWARE_TYPE_ENGINEER = 0,
@@ -15,7 +19,6 @@ enum PrinterFirmwareType
FIRMEARE_TYPE_UKNOWN,
};
class FirmwareInfo
{
public:
@@ -40,12 +43,18 @@ public:
public:
bool isValid() const { return !sn.empty(); }
/*type check*/
bool isAirPump() const { return product_name.Contains("Air Pump"); }
bool isLaszer() const { return product_name.Contains("Laser"); }
bool isCuttingModule() const { return product_name.Contains("Cutting Module"); }
bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); }
bool isWTM() const { return name.find("wtm") != std::string::npos; } // nozzle (rack hotend) module firmware
bool isFilaTrackSwitch() const { return product_name.Contains("Filament Track"); }
bool isRotary() const { return product_name.Contains("Rotary"); }// Rotary Attachment
bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); }// Auto Fire Extinguishing System
bool isWTM() const { return name.find("wtm") != string::npos; } // nozzle
bool isExhaustFan() const { return product_name.Contains("Exhaust Fan"); }
bool isHmshub() const { return product_name.find("Filament Buffer") != string::npos; }
bool isFilaTrackSwitch() const { return product_name.find("Filament Track") != string::npos; }
};

View File

@@ -29,6 +29,7 @@ bool DevHMSItem::parse_hms_info(unsigned attr, unsigned code)
std::string DevHMSItem::get_long_error_code() const
{
char buf[64];
// Orca: HMS long-error-code format excludes the reserved byte and keys the Orca HMS wiki lookups
::sprintf(buf, "%02X%02X%02X00000%1X%04X",
this->m_module_id,
this->m_module_num,

View File

@@ -8,6 +8,9 @@ namespace Slic3r {
class MachineObject;
/* some static info of machine*/ /*TODO*/
// Orca: kept as a stub — connection_type/is_lan_mode_printer/is_cloud_mode_printer live inline on
// MachineObject (DeviceManager.hpp). Adopting the reference's full DevInfo would duplicate those
// definitions and require an m_dev_info member MachineObject does not have.
class DevInfo
{
public:

View File

@@ -162,8 +162,8 @@ namespace Slic3r
}
//first: left,nozzle=1,map=1 second: right,nozzle=0,map=2
bool right_ams_valid = ams->second->GetExtruderId() == 0 && map_opt[MappingOption::USE_RIGHT_AMS];
bool left_ams_valid = ams->second->GetExtruderId() == 1 && map_opt[MappingOption::USE_LEFT_AMS];
bool right_ams_valid = (ams->second->GetBindedExtruderSet().count(MAIN_EXTRUDER_ID) != 0) && map_opt[MappingOption::USE_RIGHT_AMS];
bool left_ams_valid = (ams->second->GetBindedExtruderSet().count(DEPUTY_EXTRUDER_ID) != 0) && map_opt[MappingOption::USE_LEFT_AMS];
if (right_ams_valid || left_ams_valid)
{
tray_filaments.emplace(std::make_pair(tray_index, info));

View File

@@ -16,10 +16,6 @@
#include "slic3r/GUI/GUI_App.hpp"
#include <algorithm>
#include <unordered_set>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <nlohmann/json.hpp>
using namespace nlohmann;
@@ -33,30 +29,6 @@ void MachineObject::clear_auto_nozzle_mapping()
}
}
static std::string s_get_diameter_str(float diameter)
{
return (boost::format("%.2f") % diameter).str();
}
static std::string s_get_diameter_str(const std::string& diameter)
{
try {
float dia = boost::lexical_cast<float>(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
return diameter;
}
try {
float dia = std::stof(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
return diameter;
}
}
// get auto nozzle mapping through AP
// warnings:
@@ -268,7 +240,7 @@ int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater
void DevNozzleMappingCtrl::ParseAutoNozzleMapping(const json& print_jj)
{
if (print_jj.contains("command") && print_jj["command"].get<std::string>() == "get_auto_nozzle_mapping") {
if (print_jj.contains("command") && print_jj["command"].get<string>() == "get_auto_nozzle_mapping") {
if (print_jj.contains("sequence_id") && print_jj["sequence_id"] == m_sequence_id) {
Clear();
DevJsonValParser::ParseVal(print_jj, "result", m_result);
@@ -482,4 +454,4 @@ float DevNozzleMappingCtrl::GetFlushWeight(Slic3r::MachineObject* obj) const
return total_flush_volume * 1.26 * 0.001;
}
} // namespace Slic3r
} // namespace Slic3r

View File

@@ -18,7 +18,7 @@ namespace GUI
class Plater;
}
struct FilamentInfo; // libslic3r/ProjectTask.hpp
struct FilamentInfo; // Orca: libslic3r/ProjectTask.hpp — forward-decl for CtrlGetAutoNozzleMappingV0 signature
class MachineObject;
class DevNozzleMappingCtrl

View File

@@ -1,11 +1,32 @@
#include "DevPrintOptions.h"
#include "DevUtil.h"
#include <cassert>
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r
{
DevPrintOptions::DevPrintOptions(MachineObject* obj) : m_obj(obj)
{
m_detection_list = {
{PrintOptionEnum::AI_Monitoring, &m_ai_monitoring_detection},
{PrintOptionEnum::First_Layer_Detection, &m_first_layer_detection},
{PrintOptionEnum::Buildplate_Mark_Detection, &m_buildplate_mark_detection},
{PrintOptionEnum::Buildplate_Align_Detection, &m_buildplate_align_detection},
{PrintOptionEnum::Auto_Recovery_Detection, &m_auto_recovery_detection},
{PrintOptionEnum::Allow_Prompt_Sound_Detection, &m_allow_prompt_sound_detection},
{PrintOptionEnum::Filament_Tangle_Detection, &m_filament_tangle_detection},
{PrintOptionEnum::Idle_Heating_Protect_Detection, &m_idel_heating_protect_detection},
{PrintOptionEnum::Purify_Air_At_Print_End, &m_purify_air_at_print_end},
{PrintOptionEnum::Snapshot_Detection, &m_snapshot_detection},
{PrintOptionEnum::FOD_Check_Detection, &m_fod_check_detection},
{PrintOptionEnum::Displacement_Detection, &m_displacement_detection},
{PrintOptionEnum::Smart_Nozzle_Blob_Detection, &m_smart_nozzle_blob_detection},
};
}
void DevPrintOptionsParser::Parse(DevPrintOptions* opts, const nlohmann::json& print_json)
{
try
@@ -20,6 +41,31 @@ void DevPrintOptionsParser::Parse(DevPrintOptions* opts, const nlohmann::json& p
const std::string& cfg = print_json["cfg"].get<std::string>();
opts->m_speed_level = (DevPrintingSpeedLevel)DevUtil::get_flag_bits(cfg, 8, 3);
}
// Orca: xcam.cfg is an integer bitfield (distinct from the top-level cfg string) that
// carries the plate-align/FOD/displacement current values.
if (print_json.contains("xcam") && print_json["xcam"].contains("cfg")) {
int xcam_cfg = print_json["xcam"]["cfg"].get<int>();
if (time(nullptr) - opts->m_buildplate_align_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_buildplate_align_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 20);
if (time(nullptr) - opts->m_fod_check_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_fod_check_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 21);
if (time(nullptr) - opts->m_displacement_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_displacement_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 22);
}
// Orca: fun2 support bits (distinct from the top-level cfg/fun fields). ORCA's DevUtil has no
// no-border extractor, so the MachineObject helper is used here instead.
if (print_json.contains("fun2") && print_json["fun2"].is_string()) {
std::string fun2 = print_json["fun2"].get<std::string>();
if (!fun2.empty()) {
opts->m_buildplate_align_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 2) == 1;
opts->m_purify_air_at_print_end.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 4);
opts->m_fod_check_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 13);
opts->m_displacement_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 14);
opts->m_smart_nozzle_blob_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 15);
}
}
}
catch (const std::exception& e)
{
@@ -31,32 +77,32 @@ void DevPrintOptionsParser::ParseDetectionV1_0(DevPrintOptions *opts, MachineObj
{
try {
if (print_json.contains("xcam")) {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("printing_monitor")) {
// new protocol
opts->xcam_ai_monitoring = print_json["xcam"]["printing_monitor"].get<bool>();
opts->m_ai_monitoring_detection.current_detect_value = print_json["xcam"]["printing_monitor"].get<bool>();
} else {
// old version protocol
if (print_json["xcam"].contains("spaghetti_detector")) {
opts->xcam_ai_monitoring = print_json["xcam"]["spaghetti_detector"].get<bool>();
opts->m_ai_monitoring_detection.current_detect_value = print_json["xcam"]["spaghetti_detector"].get<bool>();
if (print_json["xcam"].contains("print_halt")) {
bool print_halt = print_json["xcam"]["print_halt"].get<bool>();
if (print_halt) { opts->xcam_ai_monitoring_sensitivity = "medium"; }
if (print_halt) { opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; }
}
}
}
if (print_json["xcam"].contains("halt_print_sensitivity")) {
opts->xcam_ai_monitoring_sensitivity = print_json["xcam"]["halt_print_sensitivity"].get<std::string>();
opts->m_ai_monitoring_detection.current_detect_sensitivity_value = print_json["xcam"]["halt_print_sensitivity"].get<std::string>();
}
}
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("first_layer_inspector")) { opts->xcam_first_layer_inspector = print_json["xcam"]["first_layer_inspector"].get<bool>(); }
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("first_layer_inspector")) { opts->m_first_layer_detection.current_detect_value = print_json["xcam"]["first_layer_inspector"].get<bool>(); }
}
if (time(nullptr) - opts->xcam_buildplate_marker_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_buildplate_mark_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("buildplate_marker_detector")) {
opts->xcam_buildplate_marker_detector = print_json["xcam"]["buildplate_marker_detector"].get<bool>();
opts->m_buildplate_mark_detection.current_detect_value = print_json["xcam"]["buildplate_marker_detector"].get<bool>();
obj->is_support_build_plate_marker_detect = true;
} else {
obj->is_support_build_plate_marker_detect = false;
@@ -66,32 +112,31 @@ void DevPrintOptionsParser::ParseDetectionV1_0(DevPrintOptions *opts, MachineObj
} catch (...) {
;
}
}
void DevPrintOptionsParser::ParseDetectionV1_1(DevPrintOptions *opts, MachineObject *obj, const nlohmann::json &print_json,bool enable)
{
if (print_json["module_name"].get<std::string>() == "first_layer_inspector") {
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
opts->xcam_first_layer_inspector = enable;
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_first_layer_detection.current_detect_value = enable;
}
} else if (print_json["module_name"].get<std::string>() == "buildplate_marker_detector") {
if (time(nullptr) - opts->xcam_buildplate_marker_hold_start > HOLD_TIME_3SEC) {
opts->xcam_buildplate_marker_detector = enable;
if (time(nullptr) - opts->m_buildplate_mark_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_buildplate_mark_detection.current_detect_value = enable;
}
} else if (print_json["module_name"].get<std::string>() == "printing_monitor") {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
opts->xcam_ai_monitoring = enable;
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_ai_monitoring_detection.current_detect_value = enable;
if (print_json.contains("halt_print_sensitivity")) {
opts->xcam_ai_monitoring_sensitivity = print_json["halt_print_sensitivity"].get<std::string>();
opts->m_ai_monitoring_detection.current_detect_sensitivity_value = print_json["halt_print_sensitivity"].get<std::string>();
}
}
} else if (print_json["module_name"].get<std::string>() == "spaghetti_detector") {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
// old protocol
opts->xcam_ai_monitoring = enable;
opts->m_ai_monitoring_detection.current_detect_value = enable;
if (print_json.contains("print_halt")) {
if (print_json["print_halt"].get<bool>()) { opts->xcam_ai_monitoring_sensitivity = "medium"; }
if (print_json["print_halt"].get<bool>()) { opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; }
}
}
}
@@ -103,12 +148,12 @@ void DevPrintOptionsParser::ParseDetectionV1_2(DevPrintOptions *opts, MachineObj
if (print_json.contains("option")) {
if (print_json["option"].is_number()) {
int option = print_json["option"].get<int>();
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) { opts->xcam_auto_recovery_step_loss = ((option & 0x01) != 0); }
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) { opts->m_auto_recovery_detection.current_detect_value = ((option & 0x01) != 0); }
}
}
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) {
if (print_json.contains("auto_recovery")) { opts->xcam_auto_recovery_step_loss = print_json["auto_recovery"].get<bool>(); }
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json.contains("auto_recovery")) { opts->m_auto_recovery_detection.current_detect_value = print_json["auto_recovery"].get<bool>(); }
}
} catch (...) {}
@@ -117,38 +162,52 @@ void DevPrintOptionsParser::ParseDetectionV1_2(DevPrintOptions *opts, MachineObj
void DevPrintOptionsParser::ParseDetectionV2_0(DevPrintOptions *opts, std::string print_json)
{
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
opts->xcam_first_layer_inspector = DevUtil::get_flag_bits(print_json, 12);
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_first_layer_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 12);
}
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_COUNT_MAX) {
opts->xcam_ai_monitoring = DevUtil::get_flag_bits(print_json, 15);
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_COUNT_MAX) {
opts->m_ai_monitoring_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 15);
switch (DevUtil::get_flag_bits(print_json, 13, 2)) {
case 0: opts->xcam_ai_monitoring_sensitivity = "never_halt"; break;
case 1: opts->xcam_ai_monitoring_sensitivity = "low"; break;
case 2: opts->xcam_ai_monitoring_sensitivity = "medium"; break;
case 3: opts->xcam_ai_monitoring_sensitivity = "high"; break;
case 0: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "never_halt"; break;
case 1: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "low"; break;
case 2: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; break;
case 3: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "high"; break;
default: break;
}
}
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_COUNT_MAX){
opts->xcam_auto_recovery_step_loss =DevUtil::get_flag_bits(print_json, 16);
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_COUNT_MAX){
opts->m_auto_recovery_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 16);
}
if (time(nullptr) - opts->xcam_prompt_sound_hold_start > HOLD_TIME_3SEC) {
opts->xcam_allow_prompt_sound = DevUtil::get_flag_bits(print_json, 22);
if (time(nullptr) - opts->m_allow_prompt_sound_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_allow_prompt_sound_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 22);
}
if (time(nullptr) - opts->xcam_filament_tangle_detect_hold_start > HOLD_TIME_3SEC) {
opts->xcam_filament_tangle_detect = DevUtil::get_flag_bits(print_json, 23);
if (time(nullptr) - opts->m_filament_tangle_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_filament_tangle_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 23);
}
}
void DevPrintOptionsParser::ParseDetectionV2_1(DevPrintOptions *opts, std::string cfg) {
if (time(nullptr) - opts->idel_heating_protect_hold_strat > HOLD_TIME_3SEC)
opts->idel_heating_protect_enabled = DevUtil::get_flag_bits(cfg, 32, 2);
if (time(nullptr) - opts->m_idel_heating_protect_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_idel_heating_protect_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 32, 2);
// Orca: further top-level cfg-string detection bits, parsed here alongside idle-heating.
if (time(nullptr) - opts->m_purify_air_at_print_end.detect_hold_start > HOLD_TIME_3SEC)
opts->m_purify_air_at_print_end.current_detect_value = DevUtil::get_flag_bits(cfg, 36, 2);
// Smart nozzle blob detection mode: cfg bits 43-44 (2 bits), 0=off, 1=on, 2=auto
if (time(nullptr) - opts->m_smart_nozzle_blob_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_smart_nozzle_blob_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 43, 2);
if (time(nullptr) - opts->m_snapshot_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_snapshot_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 38, 2);
opts->m_snapshot_detection.is_support_detect =
opts->m_snapshot_detection.current_detect_value != 0 && opts->m_snapshot_detection.current_detect_value != 3;
}
}
void DevPrintOptions::SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level)
@@ -163,75 +222,85 @@ void DevPrintOptions::SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level)
}
}
PrintOptionData* DevPrintOptions::GetDetectionOption(PrintOptionEnum print_option)
{
auto it = m_detection_list.find(print_option);
if (it != m_detection_list.end())
{
return it->second;
}
return nullptr;
}
int DevPrintOptions::command_xcam_control_ai_monitoring(bool on_off, std::string lvl)
{
bool print_halt = (lvl == "never_halt") ? false : true;
xcam_ai_monitoring = on_off;
xcam_ai_monitoring_hold_start = time(nullptr);
xcam_ai_monitoring_sensitivity = lvl;
m_ai_monitoring_detection.current_detect_value = on_off;
m_ai_monitoring_detection.detect_hold_start = time(nullptr);
m_ai_monitoring_detection.current_detect_sensitivity_value = lvl;
return command_xcam_control("printing_monitor", on_off, m_obj, lvl);
}
int DevPrintOptions::command_xcam_control_idelheatingprotect_detector(bool on_off)
{
idel_heating_protect_enabled = on_off;
idel_heating_protect_hold_strat = time(nullptr);
m_idel_heating_protect_detection.current_detect_value = on_off;
m_idel_heating_protect_detection.detect_hold_start = time(nullptr);
return command_set_against_continued_heating_mode(on_off);
}
int DevPrintOptions::command_xcam_control_buildplate_marker_detector(bool on_off)
{
xcam_buildplate_marker_detector = on_off;
xcam_buildplate_marker_hold_start = time(nullptr);
m_buildplate_mark_detection.current_detect_value = on_off;
m_buildplate_mark_detection.detect_hold_start = time(nullptr);
return command_xcam_control("buildplate_marker_detector", on_off ,m_obj);
}
int DevPrintOptions::command_xcam_control_first_layer_inspector(bool on_off, bool print_halt)
{
xcam_first_layer_inspector = on_off;
xcam_first_layer_hold_start = time(nullptr);
m_first_layer_detection.current_detect_value = on_off;
m_first_layer_detection.detect_hold_start = time(nullptr);
return command_xcam_control("first_layer_inspector", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_auto_recovery_step_loss(bool on_off)
{
xcam_auto_recovery_step_loss = on_off;
xcam_auto_recovery_hold_start = time(nullptr);
m_auto_recovery_detection.current_detect_value = on_off;
m_auto_recovery_detection.detect_hold_start = time(nullptr);
return command_set_printing_option(on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_allow_prompt_sound(bool on_off)
{
xcam_allow_prompt_sound = on_off;
xcam_prompt_sound_hold_start = time(nullptr);
m_allow_prompt_sound_detection.current_detect_value = on_off;
m_allow_prompt_sound_detection.detect_hold_start = time(nullptr);
return command_set_prompt_sound(on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_filament_tangle_detect(bool on_off)
{
xcam_filament_tangle_detect = on_off;
xcam_filament_tangle_detect_hold_start = time(nullptr);
m_filament_tangle_detection.current_detect_value = on_off;
m_filament_tangle_detection.detect_hold_start = time(nullptr);
return command_set_filament_tangle_detect(on_off, m_obj);
}
void DevPrintOptions::parse_auto_recovery_step_loss_status(int flag) {
if (time(nullptr) - xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) {
xcam_auto_recovery_step_loss = ((flag >> 4) & 0x1) != 0;
if (time(nullptr) - m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_auto_recovery_detection.current_detect_value = ((flag >> 4) & 0x1) != 0;
}
}
void DevPrintOptions::parse_allow_prompt_sound_status(int flag)
{
if (time(nullptr) - xcam_prompt_sound_hold_start > HOLD_TIME_3SEC) {
xcam_allow_prompt_sound = ((flag >> 17) & 0x1) != 0;
if (time(nullptr) - m_allow_prompt_sound_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_allow_prompt_sound_detection.current_detect_value = ((flag >> 17) & 0x1) != 0;
}
}
void DevPrintOptions::parse_filament_tangle_detect_status(int flag)
{
if (time(nullptr) - xcam_filament_tangle_detect_hold_start > HOLD_TIME_3SEC) {
xcam_filament_tangle_detect = ((flag >> 20) & 0x1) != 0;
if (time(nullptr) - m_filament_tangle_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_filament_tangle_detection.current_detect_value = ((flag >> 20) & 0x1) != 0;
}
}
@@ -289,11 +358,75 @@ int DevPrintOptions::command_set_filament_tangle_detect(bool filament_tangle_det
return obj->publish_json(j);
}
int DevPrintOptions::command_xcam_control_build_plate_align_detector(bool on_off)
{
m_buildplate_align_detection.current_detect_value = on_off;
m_buildplate_align_detection.detect_hold_start = time(nullptr);
return command_xcam_control("plate_offset_switch", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_fod_check(bool on_off)
{
m_fod_check_detection.current_detect_value = on_off;
m_fod_check_detection.detect_hold_start = time(nullptr);
return command_xcam_control("fod_check", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_displacement_detection(bool on_off)
{
m_displacement_detection.current_detect_value = on_off;
m_displacement_detection.detect_hold_start = time(nullptr);
return command_xcam_control("model_movement_check", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_purify_air_at_print_end(int on_off)
{
m_purify_air_at_print_end.current_detect_value = on_off;
m_purify_air_at_print_end.detect_hold_start = time(nullptr);
return command_set_purify_air_at_print_end((PurifyAirAtPrintEndState) on_off, m_obj);
}
int DevPrintOptions::command_set_purify_air_at_print_end(PurifyAirAtPrintEndState state, MachineObject *obj)
{
json j;
j["print"]["command"] = "print_option";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
switch (state)
{
case PurifyAirAtPrintEndState::PurifyAirDisable: j["print"]["air_purification"] = 0; break;
case PurifyAirAtPrintEndState::PurifyAirByInside: j["print"]["air_purification"] = 1; break;
case PurifyAirAtPrintEndState::PurifyAirByOutside: j["print"]["air_purification"] = 2; break;
default: assert(0);
}
return obj->publish_json(j);
}
int DevPrintOptions::command_snapshot_control(int on_off)
{
m_snapshot_detection.current_detect_value = on_off;
m_snapshot_detection.detect_hold_start = time(nullptr);
return command_set_snapshot_control(on_off, m_obj);
}
int DevPrintOptions::command_set_snapshot_control(int on_off, MachineObject *obj)
{
json j;
j["camera"]["command"] = "ipcam_cap_pic_set";
j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["camera"]["control"] = on_off ? "enable" : "disable";
return obj->publish_json(j);
}
int DevPrintOptions::command_smart_nozzle_blob_detect_mode(int mode)
{
json j;
j["print"]["command"] = "print_option";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["nozzle_blob_detect_v2"] = mode; // int: 0=off, 1=on, 2=auto
m_smart_nozzle_blob_detection.current_detect_value = mode;
m_smart_nozzle_blob_detection.detect_hold_start = time(nullptr);
return m_obj->publish_json(j);
}
}
// namespace Slic3r
// namespace Slic3r

View File

@@ -1,4 +1,5 @@
#pragma once
#include <map>
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include <wx/string.h>
@@ -9,17 +10,53 @@ namespace Slic3r {
class MachineObject;
// Identifies a print-detection option tracked in DevPrintOptions::m_detection_list.
enum class PrintOptionEnum
{
AI_Monitoring,
First_Layer_Detection,
Buildplate_Mark_Detection,
Buildplate_Align_Detection,
Auto_Recovery_Detection,
Allow_Prompt_Sound_Detection,
Filament_Tangle_Detection,
Idle_Heating_Protect_Detection,
Purify_Air_At_Print_End,
Snapshot_Detection,
FOD_Check_Detection,
Displacement_Detection,
Smart_Nozzle_Blob_Detection,
};
// State of a single print-detection option.
struct PrintOptionData
{
bool is_support_detect{false}; // some detections have no supporting field
int current_detect_value{-1}; // -1 until parsed; otherwise the reported value
std::string current_detect_sensitivity_value;
time_t detect_hold_start{0}; // suppresses parsed overwrites for a short hold
};
class DevPrintOptions
{
friend class DevPrintOptionsParser;
public:
DevPrintOptions(MachineObject* obj): m_obj(obj) {}
DevPrintOptions(MachineObject* obj);
// Tri-state for the purify-air-at-print-end firmware option (matches the MQTT air_purification value).
enum class PurifyAirAtPrintEndState : int {
PurifyAirDisable = 0,
PurifyAirByInside = 1,
PurifyAirByOutside = 2,
};
public:
void SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level);
DevPrintingSpeedLevel GetPrintingSpeedLevel() const { return m_speed_level;}
// Returns the holder for a tracked detection option, or nullptr if not tracked.
PrintOptionData* GetDetectionOption(PrintOptionEnum print_option);
// detect options
int command_xcam_control_ai_monitoring(bool on_off, std::string lvl);
int command_xcam_control_first_layer_inspector(bool on_off, bool print_halt);
@@ -29,6 +66,13 @@ public:
int command_xcam_control_filament_tangle_detect(bool on_off);
int command_xcam_control_idelheatingprotect_detector(bool on_off);
// Firmware print-option toggles (each gated on a fun2 capability bit).
int command_xcam_control_build_plate_align_detector(bool on_off);
int command_xcam_control_fod_check(bool on_off);
int command_xcam_control_displacement_detection(bool on_off);
int command_xcam_control_purify_air_at_print_end(int on_off);
int command_smart_nozzle_blob_detect_mode(int mode); // 0=off, 1=on, 2=auto
int command_snapshot_control(int on_off);
int command_xcam_control(std::string module_name, bool on_off, MachineObject *obj ,std::string lvl = "");
// set print option
@@ -39,42 +83,44 @@ public:
int command_set_filament_tangle_detect(bool fliament_tangle_detect, MachineObject *obj);
int command_set_against_continued_heating_mode(bool on_off);
int command_set_purify_air_at_print_end(PurifyAirAtPrintEndState state, MachineObject *obj);
int command_set_snapshot_control(int on_off, MachineObject *obj);
void parse_auto_recovery_step_loss_status(int flag);
void parse_allow_prompt_sound_status(int flag);
void parse_filament_tangle_detect_status(int flag);
bool GetAiMonitoring() const { return xcam_ai_monitoring; }
bool GetFirstLayerInspector() const{ return xcam_first_layer_inspector; }
bool GetBuildplateMarkerDetector() const { return xcam_buildplate_marker_detector; }
bool GetAutoRecoveryStepLoss() const { return xcam_auto_recovery_step_loss; }
bool GetAllowPromptSound() const { return xcam_allow_prompt_sound; }
bool GetFilamentTangleDetect() const { return xcam_filament_tangle_detect; }
int GetIdelHeatingProtectEenabled() const { return idel_heating_protect_enabled; }
string GetAiMonitoringSensitivity() const { return xcam_ai_monitoring_sensitivity; }
// Thin wrappers preserved for existing callers; read from the detection-option map.
bool GetAiMonitoring() const { return m_ai_monitoring_detection.current_detect_value == 1; }
bool GetFirstLayerInspector() const{ return m_first_layer_detection.current_detect_value == 1; }
bool GetBuildplateMarkerDetector() const { return m_buildplate_mark_detection.current_detect_value == 1; }
bool GetAutoRecoveryStepLoss() const { return m_auto_recovery_detection.current_detect_value == 1; }
bool GetAllowPromptSound() const { return m_allow_prompt_sound_detection.current_detect_value == 1; }
bool GetFilamentTangleDetect() const { return m_filament_tangle_detection.current_detect_value == 1; }
int GetIdelHeatingProtectEenabled() const { return m_idel_heating_protect_detection.current_detect_value; }
std::string GetAiMonitoringSensitivity() const { return m_ai_monitoring_detection.current_detect_sensitivity_value; }
private:
// print option
DevPrintingSpeedLevel m_speed_level = SPEED_LEVEL_INVALID;
// detect options
bool xcam_ai_monitoring{false};
// detection options
PrintOptionData m_ai_monitoring_detection;
PrintOptionData m_first_layer_detection;
PrintOptionData m_buildplate_mark_detection;
PrintOptionData m_buildplate_align_detection;
PrintOptionData m_auto_recovery_detection;
PrintOptionData m_allow_prompt_sound_detection;
PrintOptionData m_filament_tangle_detection;
PrintOptionData m_idel_heating_protect_detection;
PrintOptionData m_purify_air_at_print_end;
PrintOptionData m_snapshot_detection;
PrintOptionData m_fod_check_detection;
PrintOptionData m_displacement_detection;
PrintOptionData m_smart_nozzle_blob_detection;
std::string xcam_ai_monitoring_sensitivity;
bool xcam_buildplate_marker_detector{false};
bool xcam_first_layer_inspector{false};
bool xcam_auto_recovery_step_loss{false};
bool xcam_allow_prompt_sound{false};
bool xcam_filament_tangle_detect{false};
int idel_heating_protect_enabled = -1;
time_t xcam_ai_monitoring_hold_start = 0;
time_t xcam_buildplate_marker_hold_start = 0;
time_t xcam_first_layer_hold_start = 0;
time_t xcam_auto_recovery_hold_start = 0;
time_t xcam_prompt_sound_hold_start = 0;
time_t xcam_filament_tangle_detect_hold_start = 0;
time_t idel_heating_protect_hold_strat = 0;
std::map<PrintOptionEnum, PrintOptionData*> m_detection_list;
MachineObject* m_obj;/*owner*/
};
@@ -93,4 +139,4 @@ public:
static void ParseDetectionV2_1(DevPrintOptions *opts, std::string cfg);
};
} // namespace Slic3r
} // namespace Slic3r

View File

@@ -0,0 +1,33 @@
#include "DevStatus.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
void DevStatus::ParseStatus(const nlohmann::json& print_jj)
{
try {
if (print_jj.contains("job")) {
const nlohmann::json& job_jj = print_jj.at("job");
if (job_jj.contains("job_state")) {
auto new_state = (DevJobState)job_jj.at("job_state").get<int>();
if (m_job_state != new_state) {
m_job_state = new_state;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": job_state-> " << (int)new_state;
}
if (m_job_state == DevJobState::JobStateFinishing) {
m_job_state = DevJobState::JobStateFinish;
}
}
}
} catch (const std::exception& e) {
#if BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get exception";
#else
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get exception=" << e.what();
#endif
}
}
} // namespace Slic3r

View File

@@ -0,0 +1,43 @@
#pragma once
#include <optional>
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
namespace Slic3r {
enum DevJobState
{
JobStateIdle = 0,
JobStateSlicing = 1,
JobStatePrepare = 2,
JobStateStarting = 3,
JobStateRunning = 4,
JobStatePause = 5,
JobStatePausing = 6,
JobStateResuming = 7,
JobStateFinish = 8,
JobStateFailed = 9,
JobStateFinishing = 10,
JobStateStoppping = 11,
};
class MachineObject;
class DevStatus
{
public:
DevStatus(MachineObject* owner) : m_owner(owner) {};
public:
std::optional<DevJobState> GetJobState() const { return m_job_state; }
// Parse
void ParseStatus(const nlohmann::json& print_jj);
private:
MachineObject *m_owner = nullptr;
std::optional<DevJobState> m_job_state; // could be nullopt for some old firmware
};
} // namespace Slic3r

View File

@@ -19,16 +19,42 @@ DevStorage::SdcardState Slic3r::DevStorage::set_sdcard_state(int state)
{
if (system)
{
if (print_json.contains("sdcard")) {
if (print_json["sdcard"].get<bool>())
system->m_sdcard_state = DevStorage::SdcardState::HAS_SDCARD_NORMAL;
else
try {
if (print_json.contains("sdcard")) {
if (print_json["sdcard"].get<bool>())
system->m_sdcard_state = DevStorage::SdcardState::HAS_SDCARD_NORMAL;
else
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
} else {
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
} else {
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
}
}
}
// parse timelapse storage space from cam push data
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; // Orca: 20MB (20480 KB; corrected mislabeled comment)
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

View File

@@ -26,11 +26,21 @@ public:
SdcardState get_sdcard_state() const { return m_sdcard_state; };
SdcardState set_sdcard_state(int state);
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:
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

View File

@@ -0,0 +1,130 @@
#include "DevUpgrade.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
namespace Slic3r {
int DevUpgrade::GetUpgradeProgressInt() const
{
try {
return std::stoi(m_upgrade_progress);
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": " << e.what();
}
return 0;
}
void DevUpgrade::ParseUpgrade_V1_0(const json &print_jj)
{
if (print_jj.contains("upgrade_state")) {
const auto &upgrade_jj = print_jj["upgrade_state"];
// status and steps
DevJsonValParser::ParseVal(upgrade_jj, "status", m_upgrade_status);
DevJsonValParser::ParseVal(upgrade_jj, "progress", m_upgrade_progress);
DevJsonValParser::ParseVal(upgrade_jj, "message", m_upgrade_message);
DevJsonValParser::ParseVal(upgrade_jj, "module", m_upgrade_module);
DevJsonValParser::ParseVal(upgrade_jj, "err_code", m_upgrade_err_code);
// version requested
DevJsonValParser::ParseVal(upgrade_jj, "new_version_state", m_upgrade_new_version_state);
DevJsonValParser::ParseVal(upgrade_jj, "consistency_request", m_upgrade_consistency_request);
DevJsonValParser::ParseVal(upgrade_jj, "force_upgrade", m_upgrade_force_upgrade);
// version info
DevJsonValParser::ParseVal(upgrade_jj, "ota_new_version_number", m_ota_new_version_number);
// version list
if (upgrade_jj.contains("new_ver_list")) {
m_new_ver_list.clear();
for (auto ver_item = upgrade_jj["new_ver_list"].begin(); ver_item != upgrade_jj["new_ver_list"].end(); ver_item++) {
DevFirmwareVersionInfo ver_info;
DevJsonValParser::ParseVal(*ver_item, "name", ver_info.name);
DevJsonValParser::ParseVal(*ver_item, "cur_ver", ver_info.sw_ver);
DevJsonValParser::ParseVal(*ver_item, "new_ver", ver_info.sw_new_ver);
m_new_ver_list[ver_info.name] = ver_info;
if (ver_info.name == "ota" && m_ota_new_version_number.empty()) { m_ota_new_version_number = ver_info.sw_new_ver; }
}
} else {
m_new_ver_list.clear();
}
// try to parse display state
ParseUpgradeDisplayState(upgrade_jj);
}
if (print_jj.contains("cfg")) {
const std::string &cfg = print_jj["cfg"].get<std::string>();
if (!cfg.empty()) { m_upgrade_force_upgrade = DevUtil::get_flag_bits(cfg, 2); }
}
}
void DevUpgrade::ParseUpgradeDisplayState(const json &upgrade_state_jj)
{
if (upgrade_state_jj.contains("dis_state")) {
if ((int) m_upgrade_display_state != upgrade_state_jj["dis_state"].get<int>()) {
DevJsonValParser::ParseVal(upgrade_state_jj, "dis_state", m_upgrade_display_state);
// update the version after upgrade finished
if (m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingFinished) {
Slic3r::GUI::wxGetApp().CallAfter([this] {
m_owner->command_get_version();
});
}
// lan mode printer, hide upgrade avaliable state
if (m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingAvaliable &&
m_owner->is_lan_mode_printer()) {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingUnavaliable;
}
}
} else {
// BBS compatibility with old version
if (m_upgrade_status == "DOWNLOADING" ||
m_upgrade_status == "FLASHING" ||
m_upgrade_status == "UPGRADE_REQUEST" ||
m_upgrade_status == "PRE_FLASH_START" ||
m_upgrade_status == "PRE_FLASH_SUCCESS") {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingInProgress;
} else if (m_upgrade_status == "UPGRADE_SUCCESS" ||
m_upgrade_status == "DOWNLOAD_FAIL" ||
m_upgrade_status == "FLASH_FAIL" ||
m_upgrade_status == "PRE_FLASH_FAIL" ||
m_upgrade_status == "UPGRADE_FAIL") {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingFinished;
} else {
if (HasNewVersion()) {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingAvaliable;
} else {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingUnavaliable;
}
}
}
}
#define UpgradeNoError 0
#define UpgradeDownloadFailed -1
#define UpgradeVerfifyFailed -2
#define UpgradeFlashFailed -3
#define UpgradePrinting -4
wxString DevUpgrade::GetUpgradeErrCodeStr() const
{
switch (m_upgrade_err_code) {
case UpgradeNoError: return _L("Update successful.");
case UpgradeDownloadFailed: return _L("Downloading failed.");
case UpgradeVerfifyFailed: return _L("Verification failed.");
case UpgradeFlashFailed: return _L("Update failed.");
case UpgradePrinting: return _L("Update failed.");
default: return _L("Update failed.");
}
return "";
}
} // namespace Slic3r

View File

@@ -0,0 +1,108 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
#include "DevFirmware.h"
namespace Slic3r {
class MachineObject;
class DevUpgrade
{
public:
static std::shared_ptr<DevUpgrade> Create(MachineObject *owner) { return std::shared_ptr<DevUpgrade>(new DevUpgrade(owner)); }
public:
// upgrade status
bool IsUpgrading() const { return m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingInProgress; }
bool IsUpgradeAvaliable() const { return m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingAvaliable; }
DevFirmwareUpgradeState GetUpgradeState() const { return m_upgrade_display_state; }
std::string GetUpgradeStatusStr() const { return m_upgrade_status; }
// upgrade request
bool IsUpgradeConsistencyRequest() const { return m_upgrade_consistency_request; }
bool IsUpgradeForceUpgrade() const { return m_upgrade_force_upgrade; }
bool HasNewVersion() const { return m_upgrade_new_version_state == 1; }
std::string GetOtaNewVersion() const { return m_ota_new_version_number; }
std::map<std::string, DevFirmwareVersionInfo> GetNewVersionList() const { return m_new_ver_list; }
// upgrading
std::string GetUpgradeModuleStr() const { return m_upgrade_module; }
std::string GetUpgradeProgressStr() const { return m_upgrade_progress; }
int GetUpgradeProgressInt() const;
std::string GetUpgradeMessageStr() const { return m_upgrade_message; }
wxString GetUpgradeErrCodeStr() const;
public:
// ctrls
int CtrlUpgradeConfirm();
int CtrlUpgradeConsistencyConfirm();
int CtrlUpgradeFirmware(FirmwareInfo info);
int CtrlUpgradeModule(std::string url, std::string module_type, std::string version);
// parser
void ParseUpgrade_V1_0(const json &print_jj);
void ParseUpgradeDisplayState(const json &upgrade_state_jj);
protected:
DevUpgrade(MachineObject *owner) : m_owner(owner) {}
private:
MachineObject *m_owner = nullptr;
int m_upgrade_new_version_state = 0; // 0: invalid version, 1: new version available, 2: no new version
bool m_upgrade_consistency_request = false;
bool m_upgrade_force_upgrade = false;
std::string m_ota_new_version_number;
std::map<std::string, DevFirmwareVersionInfo> m_new_ver_list; // some protcols have version list
DevFirmwareUpgradeState m_upgrade_display_state = DevFirmwareUpgradeState::DC;
int m_upgrade_err_code = 0;
std::string m_upgrade_status;
std::string m_upgrade_progress;
std::string m_upgrade_message;
std::string m_upgrade_module;
};
#if 0 /*TODO*/
class DevUpgradeGetVersionInfo
{
public:
DevUpgradeGetVersionInfo(MachineObject* obj) : m_owner(obj) {} ;
public:
bool IsEmpty() const { m_module_version_map.empty();}
DevFirmwareVersionInfo GetAirPumpVersionInfo() const { return m_air_pump_version_info; }
DevFirmwareVersionInfo GetLaserVersionInfo() const { return m_laser_version_info; }
DevFirmwareVersionInfo GetCuttingModuleVersionInfo() const { return m_cutting_module_version_info; }
DevFirmwareVersionInfo GetExtinguishVersionInfo() const { return m_extinguish_version_info; }
DevFirmwareVersionInfo GetRotaryVersionInfo() const { return m_rotary_version_info; }
std::map<std::string, DevFirmwareVersionInfo> GetModuleVersionInfoMap() const { return m_module_version_map; }
public:
int CtrlGetVersion(bool with_retry = true);
void ParseGetVersion(const json &print_jj);
private:
MachineObject *m_owner;
// some version info
DevFirmwareVersionInfo m_air_pump_version_info;
DevFirmwareVersionInfo m_laser_version_info;
DevFirmwareVersionInfo m_cutting_module_version_info;
DevFirmwareVersionInfo m_extinguish_version_info;
DevFirmwareVersionInfo m_rotary_version_info;
std::map<std::string, DevFirmwareVersionInfo> m_module_version_map;
// ctrl retry
int m_retry_count = 0;
int m_max_retry = 3;
};
#endif
} // namespace Slic3r

View File

@@ -0,0 +1,56 @@
#include "DevUpgrade.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
int DevUpgrade::CtrlUpgradeConfirm()
{
json j;
j["upgrade"]["command"] = "upgrade_confirm";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["src_id"] = 1; // 1 for slicer
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeConsistencyConfirm()
{
json j;
j["upgrade"]["command"] = "consistency_confirm";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["src_id"] = 1; // 1 for slicer
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeFirmware(FirmwareInfo info)
{
std::string version = info.version;
std::string dst_url = info.url;
std::string module_name = info.module_type;
json j;
j["upgrade"]["command"] = "start";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["url"] = info.url;
j["upgrade"]["module"] = info.module_type;
j["upgrade"]["version"] = info.version;
j["upgrade"]["src_id"] = 1;
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeModule(std::string url, std::string module_type, std::string version)
{
json j;
j["upgrade"]["command"] = "start";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["url"] = url;
j["upgrade"]["module"] = module_type;
j["upgrade"]["version"] = version;
j["upgrade"]["src_id"] = 1;
return m_owner->publish_json(j);
}
} // namespace Slic3r

View File

@@ -24,6 +24,79 @@ int DevUtil::get_flag_bits(std::string str, int start, int count)
return 0;
}
uint32_t DevUtil::get_flag_bits_no_border(std::string str, int start_idx, int count)
{
if (start_idx < 0 || count <= 0) return 0;
try {
// --- 1) trim ---
auto ltrim = [](std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); };
auto rtrim = [](std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end()); };
ltrim(str);
rtrim(str);
// --- 2) remove 0x/0X prefix ---
if (str.size() >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { str.erase(0, 2); }
// --- 3) keep only hex digits ---
std::string hex;
hex.reserve(str.size());
for (char c : str) {
if (std::isxdigit(static_cast<unsigned char>(c))) hex.push_back(c);
}
if (hex.empty()) return 0;
// --- 4) use size_t for all index/bit math ---
const size_t total_bits = hex.size() * 4ULL;
const size_t ustart = static_cast<size_t>(start_idx);
if (ustart >= total_bits) return 0;
const int int_bits = std::numeric_limits<uint32_t>::digits; // typically 32
const size_t need_bits = static_cast<size_t>(std::min(count, int_bits));
// [first_bit, last_bit]
const size_t first_bit = ustart;
const size_t last_bit = std::min(ustart + need_bits, total_bits) - 1ULL;
if (last_bit < first_bit) return 0;
const size_t right_index = hex.size() - 1ULL;
const size_t first_nibble = first_bit / 4ULL;
const size_t last_nibble = last_bit / 4ULL;
const size_t start_idx = right_index - last_nibble;
const size_t end_idx = right_index - first_nibble;
if (end_idx < start_idx) return 0;
const size_t sub_len = end_idx - start_idx + 1ULL;
if (end_idx >= hex.size()) return 0;
const std::string sub_hex = hex.substr(start_idx, sub_len);
unsigned long long chunk = std::stoull(sub_hex, nullptr, 16);
const unsigned nibble_offset = static_cast<unsigned>(first_bit % 4ULL);
const unsigned long long shifted = (nibble_offset == 0U) ? chunk : (chunk >> nibble_offset);
uint32_t mask;
if (need_bits >= static_cast<size_t>(std::numeric_limits<uint32_t>::digits)) {
mask = std::numeric_limits<uint32_t>::max();
} else {
mask = static_cast<uint32_t>((1ULL << need_bits) - 1ULL);
}
const uint32_t val = static_cast<uint32_t>(shifted & mask);
return val;
} catch (const std::invalid_argument &) {
return 0;
} catch (const std::out_of_range &) {
return 0;
} catch (...) {
return 0;
}
}
int DevUtil::get_flag_bits(int num, int start, int count, int base)
{

View File

@@ -12,8 +12,15 @@
#include <stdexcept>
#include <boost/log/trivial.hpp>
#include <boost/lexical_cast.hpp>
#include "nlohmann/json.hpp"
/* Sequence Id*/
#define STUDIO_START_SEQ_ID 20000
#define STUDIO_END_SEQ_ID 30000
#define CLOUD_SEQ_ID 0
namespace Slic3r
{
@@ -27,14 +34,18 @@ public:
public:
static int get_flag_bits(std::string str, int start, int count = 1);
static int get_flag_bits(int num, int start, int count = 1, int base = 10);
static uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1);
// Extract the hex digit at position `pos` (0 = least-significant).
// eg. get_hex_bits(16, 1, 10) = 1
static int get_hex_bits(int num, int pos, int input_num_base = 10) { return get_flag_bits(num, pos * 4, 4, input_num_base); }
static int get_hex_bits(int num, int pos, int input_num_base = 10) { return get_flag_bits(num, pos * 4, 4, input_num_base);};
static float string_to_float(const std::string& str_value);
static std::string convertToIp(long long ip);
// sequence id check
static bool is_studio_cmd(int seq) { return seq >= STUDIO_START_SEQ_ID && seq < STUDIO_END_SEQ_ID;};
static bool is_cloud_cmd(int seq) { return seq == CLOUD_SEQ_ID;};
};
@@ -108,4 +119,94 @@ struct NumericStrCompare
}
};
enum class DirtyMode{
COUNTER,
TIMER
};
template<typename T>
class DevDirtyHandler{
public:
DevDirtyHandler(T init_value, int setting_threshold, DirtyMode mode): m_value(init_value), m_setting_threshold(setting_threshold), m_mode(mode)
{
m_threshold = setting_threshold;
}
~DevDirtyHandler(){};
T GetValue() const { return m_value; };
void SetOptimisticValue(const T& data)
{
m_value = data;
m_start_time = time(nullptr);
m_threshold = m_setting_threshold;
}
void UpdateValue(const T& data)
{
if (m_mode == DirtyMode::COUNTER)
{
if (m_threshold > 0)
m_threshold--;
else
m_value = data;
}
else if (m_mode == DirtyMode::TIMER)
{
if (time(nullptr) - m_start_time > m_threshold)
m_value = data;
}
}
private:
T m_value;
DirtyMode m_mode;
int m_setting_threshold{0};
int m_start_time{0};
int m_threshold{0};
};
static std::string s_get_diameter_str(float diameter)
{
return (boost::format("%.2f") % diameter).str();
}
static std::string s_get_diameter_str(const std::string& diameter)
{
try {
float dia = boost::lexical_cast<float>(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
return diameter;
}
try {
float dia = std::stof(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
return diameter;
}
}
static float s_get_diameter(const std::string& diameter)
{
try {
return boost::lexical_cast<float>(diameter);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
}
try {
return std::stof(diameter);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
}
return 0.0;
}
}; // namespace Slic3r

View File

@@ -2,15 +2,28 @@
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include <boost/log/trivial.hpp>
#include <boost/lexical_cast.hpp>
#include <wx/string.h>
namespace Slic3r
{
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozzleGroupResult(Slic3r::GUI::Plater* plater)
Slic3r::MultiNozzleUtils::NozzleInfo DevUtilBackend::GetNozzleInfo(const DevNozzle& dev_nozzle)
{
MultiNozzleUtils::NozzleInfo info;
info.diameter = dev_nozzle.GetNozzleDiameterStr().ToStdString();
info.volume_type = DevNozzle::ToNozzleVolumeType(dev_nozzle.GetNozzleFlowType());
info.extruder_id = dev_nozzle.GetLogicExtruderId();
return info;
}
std::shared_ptr<Slic3r::MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozzleGroupResult(Slic3r::GUI::Plater *plater)
{
if (plater && plater->background_process().get_current_gcode_result()) {
return plater->background_process().get_current_gcode_result()->nozzle_group_result;
@@ -19,6 +32,29 @@ std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozz
return nullptr;
}
std::unordered_map<NozzleDef, int> DevUtilBackend::CollectNozzleInfo(MultiNozzleUtils::NozzleGroupResultBase *nozzle_group_res, int logic_ext_id)
{
std::unordered_map<NozzleDef, int> need_nozzle_map;
if (!nozzle_group_res) {
return need_nozzle_map;
}
const std::vector<Slic3r::MultiNozzleUtils::NozzleInfo>& nozzle_vec = nozzle_group_res->get_used_nozzles_in_extruder(logic_ext_id);
for (auto slicing_nozzle : nozzle_vec) {
try {
NozzleDef data;
data.nozzle_diameter = boost::lexical_cast<float>(slicing_nozzle.diameter);
data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(slicing_nozzle.volume_type);
need_nozzle_map[data]++;
} catch (const std::exception& e) {
assert(0);
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "exception: " << e.what();
}
}
return need_nozzle_map;
}
static std::unordered_map<std::string, DevAmsType> s_ams_type_map = {
{"0", DevAmsType::N3F},
{"1", DevAmsType::N3S},
@@ -41,6 +77,7 @@ std::optional<Slic3r::DevFilamentDryingPreset> DevUtilBackend::GetFilamentDrying
std::vector<std::string> types = config.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations")->values;
for (auto type : types) {
if (s_ams_type_map.count(type) == 0) {
// assert(0);
continue;
}
info.ams_limitations.insert(s_ams_type_map[type]);
@@ -62,10 +99,10 @@ std::optional<Slic3r::DevFilamentDryingPreset> DevUtilBackend::GetFilamentDrying
}
if (config.has("filament_dev_drying_softening_temperature")) {
info.filament_dev_drying_softening_temperature = config.option<ConfigOptionFloats>("filament_dev_drying_softening_temperature")->get_at(0);
info.filament_dev_drying_softening_temperature = config.option<ConfigOptionFloats>("filament_dev_drying_softening_temperature")->get_at(0);
}
if (config.has("filament_dev_ams_drying_heat_distortion_temperature")) {
if (config.has("filament_dev_ams_drying_heat_distortion_temperature")){
info.filament_dev_ams_drying_heat_distortion_temperature = config.option<ConfigOptionFloats>("filament_dev_ams_drying_heat_distortion_temperature")->get_at(0);
}
@@ -83,4 +120,4 @@ std::optional<Slic3r::DevFilamentDryingPreset> DevUtilBackend::GetFilamentDrying
return std::nullopt;
}
}; // namespace Slic3r
};// namespace Slic3r

View File

@@ -1,19 +1,32 @@
/**
* @file DevUtilBackend.h
* @brief Provides common static utility methods for backend (preset/slicing).
*
* This class offers a collection of static helper functions such as string manipulation,
* file operations, and other frequently used utilities.
*/
#pragma once
#include "DevDefs.h"
#include "DevNozzleSystem.h"
#include "DevFilaSystem.h"
#include "libslic3r/MultiNozzleUtils.hpp"
#include <optional>
#include <string>
#include <unordered_map>
// Forward declarations
namespace Slic3r
{
class MachineObject;
namespace GUI
{
class Plater;
}
}; // namespace Slic3r::GUI
namespace Slic3r
{
namespace GUI { class Plater; }
class DevUtilBackend
{
@@ -21,14 +34,14 @@ public:
DevUtilBackend() = delete;
public:
static MultiNozzleUtils::NozzleInfo GetNozzleInfo(const DevNozzle& dev_nozzle);
// for rack: the slicer's per-filament -> logical-nozzle grouping for the current plate, read off
// the post-slice GCodeProcessorResult (plater->background_process().get_current_gcode_result()).
// Returns nullptr when there is no plater / no current result.
static std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> GetNozzleGroupResult(Slic3r::GUI::Plater* plater);
// for rack
static std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> GetNozzleGroupResult(Slic3r::GUI::Plater *plater);
static std::unordered_map<NozzleDef, int> CollectNozzleInfo(MultiNozzleUtils::NozzleGroupResultBase *nozzle_group_res, int logic_ext_id);
// for filament preset
static std::optional<DevFilamentDryingPreset> GetFilamentDryingPreset(const std::string& fila_id);
};
}; // namespace Slic3r
}; // namespace Slic3r

View File

@@ -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,123 @@ void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
}
}
void DeviceErrorDialog::on_request_timeout(wxTimerEvent& event)
{
if (m_request_cancelled.load()) { return; }
// Same fallback order as the cloud callback: local/HTTP illustration first, placeholder last.
if (!get_fail_snapshot_from_local(m_local_img_url)) {
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.
// m_request_seq guards against a callback from a previous error painting over the
// current one when the dialog is reused by a later show_error_code().
auto alive = m_alive;
const int seq = m_request_seq;
int ret = agent->get_hms_snapshot(m_obj->get_dev_id(), m_obj->m_print_error_img_id,
[this, alive, seq](std::string body, int status) {
if (!alive->load()) { return; }
wxGetApp().CallAfter([this, alive, seq, body = std::move(body), status]() {
if (!alive->load()) { return; }
if (seq != m_request_seq) { 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)
@@ -170,6 +310,7 @@ void DeviceErrorDialog::init_button_list()
init_button(FILAMENT_LOAD_RESUME, _L("Filament Loaded, Resume"));
init_button(JUMP_TO_LIVEVIEW, _L("View Liveview"));
init_button(NO_REMINDER_NEXT_TIME, _L("No Reminder Next Time"));
init_button(REFRESH_NOZZLE, _L("Recheck"));
init_button(IGNORE_NO_REMINDER_NEXT_TIME, _L("Ignore. Don't Remind Next Time"));
init_button(IGNORE_RESUME, _L("Ignore this and Resume"));
init_button(PROBLEM_SOLVED_RESUME, _L("Problem Solved and Resume"));
@@ -178,6 +319,10 @@ void DeviceErrorDialog::init_button_list()
init_button(CANCEL, _L("Cancel"));
init_button(STOP_DRYING, _L("Stop Drying"));
init_button(PROCEED, _L("Proceed"));
init_button(OK_JUMP_RACK, _L("OK"));
init_button(ABORT, _L("Abort"));
init_button(DISABLE_PURIFICATION, _L("Disable Purification for This Print"));
init_button(DONT_REMIND_NEXT_TIME, _L("Don't Remind Me"));
init_button(DBL_CHECK_CANCEL, _L("Cancel"));
init_button(DBL_CHECK_DONE, _L("Done"));
init_button(DBL_CHECK_RETRY, _L("Retry"));
@@ -241,6 +386,7 @@ wxString DeviceErrorDialog::show_error_code(int error_code)
Raise();
#ifndef __linux__
// Orca: skip RequestUserAttention on Linux/Wayland (urgency-hint deadlock).
// On Linux (especially Wayland) RequestUserAttention(wxUSER_ATTENTION_ERROR) maps to
// gtk_window_set_urgency_hint(TRUE) which can leave the window in an urgent-but-unfocused
// state — clicks no longer reach any widget in the app and the user has to kill the
@@ -305,22 +451,23 @@ 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);
++m_request_seq; // invalidate any snapshot fetch still in flight for the previous error
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 +496,10 @@ 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: reserve the image area exactly when an image is coming (cloud snapshot,
// local illustration, or the loading placeholder) - m_error_picture's visibility
// was decided above, so no blank slot is reserved when neither source is usable.
if (m_error_picture->IsShown())
{
m_scroll_area->SetMinSize(wxSize(FromDIP(320), text_size.y + FromDIP(220)));
}
@@ -462,6 +612,29 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
break;
}
case DeviceErrorDialog::REFRESH_NOZZLE: {
m_obj->command_refresh_nozzle();
break;
}
case DeviceErrorDialog::OK_JUMP_RACK: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_Rack();
break;
}
case DeviceErrorDialog::ABORT: {
m_obj->command_ams_control("abort");
break;
}
case DeviceErrorDialog::DISABLE_PURIFICATION: {
m_obj->command_purification_disable();
break;
}
case DeviceErrorDialog::DONT_REMIND_NEXT_TIME: {
if (!m_action_json.is_null()) {
m_obj->command_dont_remind_next_time(m_action_json);
}
break;
}
case DeviceErrorDialog::ERROR_BUTTON_COUNT: break;
case DeviceErrorDialog::DBL_CHECK_CANCEL: {

View File

@@ -1,7 +1,10 @@
#pragma once
#include <unordered_set>
#include <atomic>
#include <memory>
#include <wx/statbmp.h>
#include <wx/timer.h>
#include <wx/webrequest.h>
#include "GUI_Utils.hpp"
@@ -35,6 +38,7 @@ public:
JUMP_TO_LIVEVIEW,
NO_REMINDER_NEXT_TIME = 23,
REFRESH_NOZZLE = 24,
IGNORE_NO_REMINDER_NEXT_TIME = 25,
//LOAD_FILAMENT = 26*/
IGNORE_RESUME = 27,
@@ -46,6 +50,10 @@ public:
CANCEL = 37,
REMOVE_CLOSE_BTN = 39, // special case, do not show close button
PROCEED = 41,
OK_JUMP_RACK = 49,
ABORT = 51,
DISABLE_PURIFICATION = 54,
DONT_REMIND_NEXT_TIME = 57,
ERROR_BUTTON_COUNT,
@@ -84,6 +92,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 +106,14 @@ private:
std::unordered_set<Button*> m_used_button;
wxWebRequest web_request;
wxTimer* m_request_timer{ nullptr };
std::atomic<bool> m_request_cancelled{ false };
// UI-thread-only generation counter: bumped on every update_contents() so a cloud
// snapshot callback from a previous error cannot paint over the current one.
int m_request_seq{ 0 };
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 };

View File

@@ -44,6 +44,12 @@
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevUtil.h"
// Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules
#include "DeviceCore/DevAxis.h"
#include "DeviceCore/DevChamber.h"
#include "DeviceCore/DevStatus.h"
#include "DeviceCore/DevUpgrade.h"
#define CALI_DEBUG
#define MINUTE_30 1800000 //ms
@@ -68,6 +74,44 @@ int get_tray_id_by_ams_id_and_slot_id(int ams_id, int slot_id)
}
}
namespace {
// Stringing-prone filament IDs per nozzle-diameter bucket.
// Mirrors the printer firmware tables (see g_leak_pron_idx_for_0_4 / _0_6_0_8).
// Keep these in sync with firmware when new stringing-prone filaments are added.
const std::unordered_set<std::string> g_stringing_prone_for_0_4 = {
"GFA11", // PLA Aero
"GFU90", // TPU 90A
"GFU00", // TPU 95A HF
"GFU02", // Generic TPU for AMS
"GFU98", // TPU for AMS
};
const std::unordered_set<std::string> g_stringing_prone_for_0_6_0_8 = {
"GFA11", // PLA Aero
"GFU00", // TPU 95A HF
};
// Pick the right table for the given nozzle diameter; returns nullptr if the
// nozzle bucket has no entries (e.g. 0.2 mm) or the diameter is invalid.
const std::unordered_set<std::string>* pick_stringing_set(float nozzle_diameter)
{
if (!(nozzle_diameter > 0.f)) return nullptr;
if (nozzle_diameter < 0.3f) return nullptr; // 0.2 nozzle: empty
if (nozzle_diameter < 0.5f) return &g_stringing_prone_for_0_4;
return &g_stringing_prone_for_0_6_0_8; // 0.6 / 0.8 nozzles
}
} // namespace
bool Slic3r::is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter)
{
if (filament_id.empty()) return false;
const auto* set = pick_stringing_set(nozzle_diameter);
if (!set) return false;
return set->count(filament_id) > 0;
}
wxString Slic3r::get_stage_string(int stage)
{
switch(stage) {
@@ -553,6 +597,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
@@ -577,6 +622,13 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
m_print_options = new DevPrintOptions(this);
m_nozzle_mapping_ptr = std::make_shared<DevNozzleMappingCtrl>(this);
// Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules
m_axis = DevAxis::Create(this);
m_chamber = DevChamber::Create(this);
m_upgrade = DevUpgrade::Create(this);
m_status = new DevStatus(this);
m_calib = new DevCalib(this);
}
}
@@ -629,6 +681,13 @@ MachineObject::~MachineObject()
delete m_print_options;
m_print_options = nullptr;
// Orca: adopt DeviceCore split
delete m_calib;
m_calib = nullptr;
delete m_status;
m_status = nullptr;
}
}
@@ -698,6 +757,33 @@ std::string MachineObject::get_filament_display_type(const std::string& ams_id,
return this->get_tray(ams_id, tray_id).get_display_filament_type();
}
bool MachineObject::any_loaded_filament_is_stringing_prone() const
{
if (print_job_filament_mapping.empty()) return false;
std::vector<float> nozzle_diameters;
if (m_extder_system) {
for (const auto& ext : m_extder_system->GetExtruders()) {
const float d = ext.GetNozzleDiameter();
if (d > 0.f) nozzle_diameters.push_back(d);
}
}
if (nozzle_diameters.empty()) return false;
for (uint16_t v : print_job_filament_mapping) {
if (v == 0xFFFF) continue;
const int ams_id = (v >> 8) & 0xFF;
const int slot_id = v & 0xFF;
const std::string fid = this->get_filament_id(std::to_string(ams_id), std::to_string(slot_id));
if (fid.empty()) continue;
for (float d : nozzle_diameters) {
if (Slic3r::is_stringing_prone_filament(fid, d))
return true;
}
}
return false;
}
void MachineObject::_parse_ams_status(int ams_status)
{
ams_status_sub = ams_status & 0xFF;
@@ -716,6 +802,8 @@ void MachineObject::_parse_ams_status(int ams_status)
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_SELF_CHECK;
} else if (ams_status_main_int == (int) AmsStatusMain::AMS_STATUS_MAIN_DEBUG) {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_DEBUG;
} else if (ams_status_main_int == (int) AmsStatusMain::AMS_STATUS_MAIN_COLD_PULL) {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_COLD_PULL;
} else {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_UNKNOWN;
}
@@ -867,6 +955,9 @@ void MachineObject::clear_version_info()
laser_version_info = DevFirmwareVersionInfo();
cutting_module_version_info = DevFirmwareVersionInfo();
extinguish_version_info = DevFirmwareVersionInfo();
rotary_version_info = DevFirmwareVersionInfo();
exhaustfan_version_info = DevFirmwareVersionInfo();
amshub_version_info = DevFirmwareVersionInfo();
filatrack_version_info = DevFirmwareVersionInfo();
module_vers.clear();
// Drop cached rack-hotend (WTM) firmware alongside the module list.
@@ -884,11 +975,17 @@ void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
cutting_module_version_info = info;
} else if (info.isExtinguishSystem()) {
extinguish_version_info = info;
} else if (info.isRotary()) {
rotary_version_info = info;
} else if (info.isWTM()) {
// Route rack-hotend / extruder-nozzle firmware into the nozzle system so
// the rack upgrade UI can read per-nozzle versions. isWTM() is false for every non-rack
// printer's modules, so this branch never fires outside H2C.
m_nozzle_system->AddFirmwareInfoWTM(info);
} else if (info.isExhaustFan()) {
exhaustfan_version_info = info;
} else if (info.isHmshub()) {
amshub_version_info = info;
} else if (info.isFilaTrackSwitch()) {
filatrack_version_info = info;
}
@@ -1207,7 +1304,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) {
@@ -1857,10 +1954,17 @@ int MachineObject::command_axis_control(std::string axis, double unit, double in
{
if (m_support_mqtt_axis_control)
{
int dir = input_val > 0 ? 1 : -1;
// i3-arch printers move the bed for Y/Z, so the on-screen direction is
// reversed — same negation the g-code fallback below applies.
if (!is_core_xy() && (axis.compare("Y") == 0 || axis.compare("Z") == 0)) {
dir = -dir;
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = input_val > 0 ? 1 : -1;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
@@ -2199,6 +2303,14 @@ int MachineObject::command_ack_proceed(json& proceed) {
} else {
proceed["err_ignored"] = std::vector<int>{proceed["err_index"]};
}
for (auto& item : proceed["err_ignored"]) {
json error_item;
error_item["idx"] = item.get<int>();
error_item["mode"] = 0;
proceed["rm_idx"].push_back(error_item);
}
proceed["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
json j;
@@ -2206,6 +2318,78 @@ 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;
j["print"]["command"] = "close_air_filt";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j, 1);
}
int MachineObject::command_dont_remind_next_time(json& mqtt_guard_json)
{
if (!mqtt_guard_json.contains("command") ||
!mqtt_guard_json.contains("err_index") ||
mqtt_guard_json["err_index"].empty()) return -1;
json j;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
try {
j["print"]["command"] = mqtt_guard_json["command"].get<std::string>();
int err_index = mqtt_guard_json["err_index"].get<int>();
if (mqtt_guard_json.contains("err_ignored") &&
mqtt_guard_json["err_ignored"].is_array()) {
j["print"]["err_ignored"] = mqtt_guard_json["err_ignored"];
j["print"]["err_ignored"].push_back(err_index);
} else {
j["print"]["err_ignored"] = std::vector<int>{err_index};
}
for (auto& item : j["print"]["err_ignored"]) {
if (!item.is_number_integer()) continue;
json item_json;
item_json["idx"] = item.get<int>();
item_json["mode"] = 1; // 1-next time ignore, 2-always ignore
j["print"]["rm_idx"].push_back(item_json);
}
} catch (const json::exception& e) {
BOOST_LOG_TRIVIAL(error) << "JSON parsing error in command_dont_remind_next_time: " << e.what();
return -1;
}
return this->publish_json(j, 1);
}
int MachineObject::command_xcam_control_ai_monitoring(bool on_off, std::string lvl)
{
bool print_halt = (lvl == "never_halt") ? false:true;
@@ -2915,6 +3099,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
//supported function
m_config->ParseConfig(jj);
m_status->ParseStatus(jj); // Orca: adopt DeviceCore split — populate DevStatus module
if (jj.contains("support_build_plate_marker_detect")) {
if (jj["support_build_plate_marker_detect"].is_boolean()) {
@@ -3142,6 +3327,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);
@@ -3163,6 +3359,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
int flag3 = jj["flag3"].get<int>();
is_support_filament_setting_inprinting = get_flag_bits(flag3, 3);
is_enable_ams_np = get_flag_bits(flag3, 9);
is_support_fila_change_abort = get_flag_bits(flag3, 13); // filament-change Stop button
is_support_ext_change_assist_old = get_flag_bits(flag3, 16); // A/P-series multi-color external change assist
is_support_filament_32_colors = get_flag_bits(flag3, 17);
}
}
if (!key_field_only) {
@@ -3328,6 +3527,16 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (!key_field_only) {
/* temperature */
// Orca: adopt DeviceCore split — populate DevAxis/DevChamber modules
// alongside the inline handling (side-effect-free; inline stays authoritative).
// Contained locally so a malformed field cannot abort the whole status parse.
try {
m_axis->ParseAxis(jj);
m_chamber->ParseChamber(jj);
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << "parse_json: DevAxis/DevChamber parse failed";
}
DevBed::ParseV1_0(jj,m_bed);
if (jj.contains("frame_temper")) {
@@ -4336,6 +4545,17 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
this->camera_resolution = j["camera"]["resolution"].get<std::string>();
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"].value("result", -1);
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;
}
}
}
}
@@ -4940,6 +5160,15 @@ bool MachineObject::check_enable_np(const json& print) const
return false;
}
// Max filament color count for the send gate; 0 = no explicit upper bound.
int MachineObject::get_max_filament_color_count() const
{
if (is_support_filament_32_colors) return 32;
if (is_enable_ams_np && !is_series_x()) return 20;
if (!is_series_x() && !is_series_o()) return 16;
return 0;
}
void MachineObject::parse_new_info(json print)
{
is_enable_np = check_enable_np(print);
@@ -5093,8 +5322,23 @@ void MachineObject::parse_new_info(json print)
// fun2 may have infinite length, use get_flag_bits_no_border
if (!fun2.empty()) {
is_support_print_with_emmc = get_flag_bits_no_border(fun2, 0) == 1;
is_support_pa_mode = (get_flag_bits_no_border(fun2, 3) == 1);
is_support_remote_dry = (get_flag_bits_no_border(fun2, 5) == 1);
is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
if (DevPrinterConfigUtil::support_print_check_firmware_for_tpu_left(printer_type)) {
m_firmware_support_print_tpu_left = get_flag_bits_no_border(fun2, 7) == 1;
}
}
/* Per-filament-index AMS slot mapping reported by the printer (task-level state) */
if (print.contains("mapping") && print["mapping"].is_array()) {
std::vector<uint16_t> new_mapping;
new_mapping.reserve(print["mapping"].size());
for (const auto& v : print["mapping"]) {
new_mapping.push_back(static_cast<uint16_t>(v.get<unsigned>()));
}
print_job_filament_mapping = std::move(new_mapping);
}
/*aux*/
@@ -5104,6 +5348,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*/
@@ -5564,7 +5809,9 @@ wxString MachineObject::get_nozzle_replace_url() const
return link_map["en"].get<wxString>();
}/*retry with en*/
return "https://wiki.bambulab.com/en/h2/maintenance/replace-hotend";
// Orca: no neutral wiki equivalent for this fallback — return empty so the caller hides the link
// (PrinterPartsDialog::OnWikiClicked reports "No wiki link available" instead of opening a browser)
return wxEmptyString;
}
std::string MachineObject::get_error_code_str(int error_code)

View File

@@ -3,6 +3,7 @@
#include <map>
#include <mutex>
#include <atomic>
#include <vector>
#include <string>
#include <memory>
@@ -21,6 +22,7 @@
#include "DeviceCore/DevDefs.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevFirmware.h"
#include "DeviceCore/DevCalib.h" // Orca: adopt DeviceCore split (defines DevCalib, CalibStatus, ManualPaCaliMethod)
#include "DeviceErrorDialog.hpp"
#include <wx/object.h>
@@ -62,11 +64,7 @@ class DeviceErrorDialog; // Previous definitions
}
class NetworkAgent;
enum ManualPaCaliMethod {
PA_LINE = 0,
PA_PATTERN,
};
// Orca: ManualPaCaliMethod now provided by DeviceCore/DevCalib.h (enum class)
#define UpgradeNoError 0
#define UpgradeDownloadFailed -1
@@ -77,7 +75,9 @@ enum ManualPaCaliMethod {
// Previous definitions
class DevAms;
class DevAmsTray;
class DevAxis; // Orca: adopt DeviceCore split
class DevBed;
class DevChamber; // Orca: adopt DeviceCore split
class DevConfig;
class DevCtrl;
class DevExtensionTool;
@@ -91,9 +91,15 @@ class DevLamp;
class DevNozzleSystem;
class DevNozzleMappingCtrl;
class DeviceManager;
class DevStatus; // Orca: adopt DeviceCore split
class DevStorage;
class DevUpgrade; // Orca: adopt DeviceCore split
struct DevPrintTaskRatingInfo;
// Returns true when filament_id (e.g. "GFA11", "GFU00") is on the stringing-prone list for the
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
class MachineObject
{
@@ -123,6 +129,15 @@ private:
DevBed * m_bed;
DevStorage* m_storage;
/* Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules alongside
MachineObject's inline handling of these concerns, which stays authoritative here (Orca keeps
the inline model permanently; the split modules coexist with it via accessors). */
std::shared_ptr<DevAxis> m_axis;
std::shared_ptr<DevChamber> m_chamber;
DevCalib* m_calib{ nullptr };
DevStatus* m_status{ nullptr };
std::shared_ptr<DevUpgrade> m_upgrade;
/*Ctrl*/
DevCtrl* m_ctrl;
@@ -145,6 +160,18 @@ public:
~MachineObject();
void set_agent(NetworkAgent* agent) { m_agent = agent; }
NetworkAgent* get_agent() const { return m_agent; } // Orca: needed by DeviceCore modules (DevAxisCtrl)
// Orca: these DeviceCore module accessors are unwired on the read side — axis/chamber/status
// are fed every MQTT push but no GUI consumer reads them yet, and for calib/upgrade the inline
// parse in DeviceManager.cpp remains authoritative. Do not wire DevUpgrade naively: its
// ParseUpgradeDisplayState would duplicate the inline block's dis_state==3 ->
// command_get_version CallAfter side effect.
std::shared_ptr<DevAxis> GetAxis() const { return m_axis; }
std::shared_ptr<DevChamber> GetChamber() const { return m_chamber; }
DevCalib* GetCalib() const { return m_calib; }
DevStatus* GetStatus() const { return m_status; }
std::weak_ptr<DevUpgrade> GetUpgrade() const { return m_upgrade; }
public:
enum ActiveState {
@@ -379,6 +406,9 @@ public:
DevFirmwareVersionInfo laser_version_info;
DevFirmwareVersionInfo cutting_module_version_info;
DevFirmwareVersionInfo extinguish_version_info;
DevFirmwareVersionInfo rotary_version_info;
DevFirmwareVersionInfo exhaustfan_version_info;
DevFirmwareVersionInfo amshub_version_info;
DevFirmwareVersionInfo filatrack_version_info;
std::map<std::string, DevFirmwareVersionInfo> module_vers;
std::map<std::string, DevFirmwareVersionInfo> new_ver_list;
@@ -417,6 +447,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); }
@@ -557,6 +588,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};
@@ -633,10 +665,18 @@ public:
bool is_support_airprinting_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
bool is_support_print_with_emmc{false};
bool is_support_remote_dry = false;
bool is_support_check_track_switch_match_slice_printer{false};
bool is_support_pa_mode{false};
std::optional<bool> m_firmware_support_print_tpu_left;
bool installed_upgrade_kit{false};
int bed_temperature_limit = -1;
@@ -666,6 +706,11 @@ public:
boost::thread* get_slice_info_thread { nullptr };
boost::thread* get_model_task_thread { nullptr };
// Per-filament-index AMS slot mapping reported by the printer in print.mapping. Up to
// 32 entries, each value packs (ams_id << 8) | slot_id; 0xFFFF means unused.
std::vector<uint16_t> print_job_filament_mapping;
bool any_loaded_filament_is_stringing_prone() const;
/* job attr */
int jobState_ = 0;
@@ -703,6 +748,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);
@@ -714,6 +760,8 @@ public:
int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter);
int command_get_access_code();
int command_ack_proceed(json& proceed);
int command_purification_disable();
int command_dont_remind_next_time(json& mqtt_guard_json);
/* command upgrade */
int command_upgrade_confirm();
@@ -805,6 +853,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
@@ -878,6 +928,12 @@ public:
/*for more extruder*/
bool is_enable_np{ false };
bool is_enable_ams_np{ false };
bool is_support_filament_32_colors{ false };
bool is_support_fila_change_abort{ false }; // filament-change Stop button
bool is_support_ext_change_assist_old{ false }; // A/P-series multi-color external change assist
// Max filament color count for the send gate; returns 0 when there is no explicit upper bound.
int get_max_filament_color_count() const;
/**
* Virtual Tray (vt_slot) - External/manual filament loading slots.

View File

@@ -2,10 +2,10 @@
list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceTab/uiAmsHumidityPopup.h
GUI/DeviceTab/uiAmsHumidityPopup.cpp
GUI/DeviceTab/uiAMSBestPositionPopup.hpp
GUI/DeviceTab/uiAMSBestPositionPopup.cpp
GUI/DeviceTab/uiDeviceUpdateVersion.h
GUI/DeviceTab/uiDeviceUpdateVersion.cpp
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp
GUI/DeviceTab/wgtDeviceNozzleRack.h
GUI/DeviceTab/wgtDeviceNozzleRack.cpp
GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h
@@ -14,5 +14,7 @@ list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceTab/wgtDeviceNozzleSelect.cpp
GUI/DeviceTab/wgtMsgBox.h
GUI/DeviceTab/wgtMsgBox.cpp
GUI/DeviceTab/wgtMsgPanel.h
GUI/DeviceTab/wgtMsgPanel.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
//**********************************************************/
/* File: uiAMSBestPositionPopup.hpp
* Description: The popup with suggest best ams position
*
//**********************************************************/
#pragma once
#include "slic3r/GUI/Widgets/AMSItem.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/PopupWindow.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
#include <algorithm>
#include <tuple>
namespace Slic3r { namespace GUI {
class UiStyledAMSPanel : public wxPanel
{
public:
UiStyledAMSPanel(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
const wxColour& borderColor = wxColour(200, 200, 200),
const wxColour& bgColor = wxColour(255, 255, 255),
bool borderDashed = true,
wxString name = "",
bool isTop = false);
protected:
void OnPaint(wxPaintEvent& event);
private:
bool m_borderDashed;
int m_borderWidth;
int m_radius;
wxColour m_borderColor;
wxColour m_bgColor;
wxString m_name;
bool m_isTop;
};
class UiStyledSwitchPanel : public wxPanel
{
public:
UiStyledSwitchPanel(wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
const wxColour& borderColor,
const wxColour& bgColor,
bool borderDashed,
int borderWidth,
int radius,
bool isTop);
void AddToLeft(wxWindow* window, int proportion = 0, int flag = wxEXPAND, int border = 0);
void AddToRight(wxWindow* window, int proportion = 0, int flag = wxEXPAND, int border = 0);
void Clear(bool deleteWindows);
wxSizer* GetLeftSizer() { return m_leftSizer; }
wxSizer* GetRightSizer() { return m_rightSizer; }
void LayoutAndFit()
{
m_leftSizer->Layout();
// m_leftSizer->Fit();
m_rightSizer->Layout();
// m_rightSizer->Fit();
m_splitSizer->Layout();
// m_splitSizer->Fit();
m_contentSizer->Layout();
// m_contentSizer->Fit();
m_mainSizer->Layout();
// m_mainSizer->Fit();
Fit();
}
protected:
void OnPaint(wxPaintEvent& event);
private:
bool m_borderDashed;
int m_borderWidth;
int m_radius;
wxColour m_borderColor;
wxColour m_bgColor;
bool m_isTop;
wxBoxSizer* m_mainSizer{nullptr};
wxBoxSizer* m_contentSizer{nullptr};
wxBoxSizer* m_splitSizer{nullptr};
wxSizer* m_leftSizer{nullptr};
wxSizer* m_rightSizer{nullptr};
static constexpr int labelHeight = 30;
};
enum DataStatusType {
ADJUST,
OK,
UNMATCHED
};
class UiAMSSlot : public wxPanel
{
public:
UiAMSSlot(wxWindow* parent,
const std::vector<wxColour>& bgColours,
const wxString& text,
DataStatusType status,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
double colourFactor = 1.0,
double scaleFactor = 1.0);
private:
void OnPaint(wxPaintEvent&);
void DrawRectangle(wxPaintDC& dc, const wxSize& cli);
void DrawLine(wxPaintDC& dc, const wxSize& cli);
wxColour LightenColour(const wxColour& original);
bool IsDark(const wxColour& c)
{
int brightness = (c.Red() * 299 + c.Green() * 587 + c.Blue() * 114) / 1000;
return brightness < 128; // 0-255 range, 128 mid
}
private:
// Copied, not referenced: the slot repaints long after construction, so it must not
// depend on the caller's vector staying alive.
std::vector<wxColour> m_bgColours;
wxString m_text;
DataStatusType m_status;
wxSize m_size;
ScalableBitmap *m_ams_slot_readonly{nullptr};
int m_rectangleW = 44;
int m_rectangleH = 62;
int rectangleW = 44;
int rectangleH = 62;
double m_colourFactor = 1.0f;
double m_scaleFactor = 1.0f;
// wxDECLARE_EVENT_TABLE();
};
struct DataAmsSlotInfo
{
wxString amsName;
wxString name;
std::vector<wxColour> colours;
double colourFactor;
double scaleFactor;
DataStatusType status;
};
class UiAMS : public UiStyledAMSPanel
{
public:
UiAMS(wxWindow* parent,
const std::vector<DataAmsSlotInfo>& amsInfo,
wxWindowID id,
const wxPoint& pos,
const wxSize& minSize);
private:
void init();
std::vector<DataAmsSlotInfo> m_amsInfo;
wxString m_amsTitle;
wxSize m_minSize;
};
struct DataStatusParam {
// int count = 0;
int width = 0;
int height = 0;
std::vector<DataAmsSlotInfo> slots;
};
class ReselectMachineDialog : public wxDialog
{
public:
ReselectMachineDialog(wxWindow* parent);
~ReselectMachineDialog();
void Update(MachineObject* obj,
const std::map<int, int>& best_pos_map,
const std::vector<FilamentInfo>& ams_mapping,
wxString save_time);
private:
int CaculateSwitcherDistribution(MachineObject* obj, const std::map<int, int>& best_pos_map, const std::vector<FilamentInfo>& ams_mapping);
wxString getTrayID(MachineObject* obj, const std::string& amsID, const std::string& slotID);
void OnRefreshButton(wxCommandEvent& event);
private:
int saveTimes{0};
wxBoxSizer* mainSizer{nullptr};
wxPanel* textPanel{nullptr};
wxBoxSizer* textSizer{nullptr};
Label* suggestText{nullptr};
// wxHyperlinkCtrl* linkwiki{nullptr};
Label* linkwiki{nullptr};
Label* summaryText{nullptr};
UiStyledSwitchPanel* filamentSwitch{nullptr};
wxStaticText* filamentTips{nullptr};
std::vector<std::vector<DataAmsSlotInfo>> inAAMS{};
std::vector<std::vector<DataAmsSlotInfo>> inBAMS{};
wxPanel* statusBar{nullptr};
wxBoxSizer* btnSizer{nullptr};
Button* m_buttonClose{nullptr };
Button* m_buttonRefresh{ nullptr };
std::vector<wxColour>colourAdjust{wxColour("#675AFF")};
std::vector<wxColour>colourOK{wxColour("#FF8181")};
std::vector<wxColour>colourUnused{wxColour("#FF818140")};
// wxStaticBitmap* m_bitmapSelectMachine{nullptr};
};
wxDECLARE_EVENT(wxEVT_REFRESH_DATA, wxCommandEvent);
}} // namespace Slic3r::GUI

View File

@@ -23,16 +23,16 @@ uiAmsPercentHumidityDryPopup::uiAmsPercentHumidityDryPopup(wxWindow *parent)
: wxDialog(parent, wxID_ANY, "")
{
Create();
wxGetApp().UpdateDlgDarkUI(this);
wxGetApp().UpdateDlgDarkUI(this); // Orca: apply dark-mode theming to the popup
}
void uiAmsPercentHumidityDryPopup::Create()
{
// create images
idle_img = ScalableBitmap(this, "ams_drying", 16);
drying_img = ScalableBitmap(this, "ams_is_drying", 16);
drying_img = ScalableBitmap(this, "ams_is_drying", 16); // Orca: use the drying-state icon
// background
// background
SetBackgroundColour(*wxWHITE);
// create title sizer
@@ -147,7 +147,8 @@ void uiAmsPercentHumidityDryPopup::UpdateContents()
// table grid
const wxString& humidity_str = wxString::Format("%d%%", m_humidity_percent);
m_humidity_label->SetLabel(humidity_str);
const wxString& temp_str = wxString::Format(wxString::FromUTF8(u8"%d\u2103" /* °C */), (int)std::round(m_current_temperature));
// Orca: format the temperature with an explicit UTF-8 \u2103 (no translation of a units-only format)
const wxString& temp_str = wxString::Format(wxString::FromUTF8(u8"%d\u2103" /* \u00b0C */), (int)std::round(m_current_temperature));
m_temperature_label->SetLabel(temp_str);
if (m_left_dry_time > 0)

View File

@@ -20,7 +20,7 @@ namespace Slic3r { namespace GUI {
struct uiAmsHumidityInfo
{
std::string ams_id;
AMSModel ams_type;
AMSModel ams_type; // Orca: GUI-layer AMS enum; consumer (AMSItem.cpp) assigns an AMSModel
int humidity_display_idx = -1;
int humidity_percent = -1;
float current_temperature;

View File

@@ -9,7 +9,7 @@
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/Label.hpp" // Orca: explicit Label include
#include <wx/stattext.h>
@@ -81,7 +81,6 @@ void uiDeviceUpdateVersion::CreateWidgets()
serial_text->SetFont(font);
version_text->SetFont(font);
model_text->SetFont(font);
// The grid sizer
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(0, 2, 0, 0);
//grid_sizer->AddGrowableCol(1);
@@ -103,10 +102,10 @@ void uiDeviceUpdateVersion::CreateWidgets()
grid_sizer->Add(version_hsizer, 0, wxEXPAND, 0);
grid_sizer->Add(m_dev_version, 0, wxEXPAND | wxALL, FromDIP(5));
// Updating
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->AddSpacer(FromDIP(40));
main_sizer->AddSpacer(FromDIP(40)); // Orca: top spacing above the firmware-info grid
main_sizer->Add(grid_sizer, 0, wxALIGN_LEFT, FromDIP(5));
SetSizer(main_sizer);

View File

@@ -1,14 +1,11 @@
//**********************************************************/
/* File: wgtDeviceNozzleRack.cpp
* Description: The Device-tab panel with the toolhead nozzle and the H2C induction hotend rack.
* Description: The panel with rack info
*
* \n class wgtDeviceNozzleRack;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackArea;
* \n class wgtDeviceNozzleRackNozzleItem;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackPos;
*
* The wgtDeviceNozzleRackNozzleItem tile and the EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED event live in
* wgtDeviceNozzleRackNozzleItem.{h,cpp}, so they are not redefined here.
//**********************************************************/
#include "wgtDeviceNozzleRack.h"
@@ -17,29 +14,41 @@
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include <unordered_set>
#define WX_DIP_SIZE_18 wxSize(FromDIP(18), FromDIP(18))
#define WX_DIP_SIZE_46 wxSize(FromDIP(46), FromDIP(46))
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
#define WGT_RACK_NOZZLE_SIZE WX_DIP_SIZE(88, 100)
#define L_RAW_A_STR _L("Row A")
#define L_RAW_B_STR _L("Row B")
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#009688"); // Orca: accent green
static wxColour s_red_clr("#D01B1B");
// Orca: StateColor lacks these grey constants, so mirror the values here.
static const wxColour WGT_GREY200 = wxColour(248, 248, 248);
static const wxColour WGT_GREY300 = wxColour(238, 238, 238);
static wxColour s_hgreen_clr("#009688");
static std::vector<int> a_nozzle_seq = { 0, 2, 4, 1, 3, 5 };
static std::vector<int> b_nozzle_seq = { 1, 3, 5, 0, 2, 4 };
wxDEFINE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
// Tints the "yellow" template pixels of a nozzle bitmap with the loaded filament colour.
// Duplicates the file-local helper of the same purpose in wgtDeviceNozzleRackNozzleItem.cpp.
static wxBitmap SetNozzleBmpColor(const wxBitmap& bmp, const std::string& color_str) {
if(color_str.empty()) return bmp;
@@ -353,6 +362,36 @@ StaticBox* wgtDeviceNozzleRackArea::CreateNozzleBox(const std::vector<int> nozzl
return nozzle_box;
}
static void s_update_title(const std::shared_ptr<DevNozzleRack> rack, wgtDeviceNozzleRackTitle* title_label)
{
wxString title = _L("Induction Hotend Rack");
if (rack && (rack->GetReadingCount() > 0))
{
wxString pending = ": " + _L("Reading") + wxString::Format(" %d/%d", rack->GetReadingIdx(), rack->GetReadingCount());
title += pending;
}
title_label->SetLabel(title);
}
#if 0
static void s_update_readall_btn(const std::shared_ptr<DevNozzleRack> rack, Button* btn)
{
wxString label = _L("Read All");
if (rack && rack->HasUnreliableNozzles())
{
label += "*";
}
if (btn->GetLabel() != label)
{
btn->SetLabel(label);
}
btn->Enable(rack->CtrlCanReadAll());
};
#endif
void wgtDeviceNozzleRackArea::UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items,
std::shared_ptr<DevNozzleRack> nozzle_rack)
{
@@ -409,6 +448,7 @@ void wgtDeviceNozzleRackArea::UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack)
const auto& nozzle_rack = rack.lock();
if (nozzle_rack)
{
// s_update_title(nozzle_rack, m_title_nozzle_rack);
UpdateNozzleItems(m_nozzle_items, nozzle_rack);
m_rack_pos_panel->UpdateRackPos(nozzle_rack);
m_btn_read_all->Enable(nozzle_rack->CtrlCanReadAll());
@@ -571,7 +611,7 @@ static void s_show_label(Label* label, const wxString& text, const wxColour& tex
void wgtDeviceNozzleRackPos::UpdateRackPos(DevNozzleRack::RackPos new_pos,
DevNozzleRack::RackStatus new_status, bool is_reading)
{
// While reading, both rows show a "Running..." status and the move buttons are hidden.
/* di*/
if (is_reading)
{
s_show_label(m_label_rowup, L_RAW_A_STR, *wxBLACK);
@@ -697,4 +737,298 @@ void wgtDeviceNozzleRackPos::Rescale()
m_btn_homing->msw_rescale();
}
};// end of namespace Slic3r::GUI
wgtDeviceNozzleRackNozzleItem::wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id)
: StaticBox(parent, wxID_ANY), m_nozzle_id(nozzle_id)
{
CreateGui();
}
void wgtDeviceNozzleRackNozzleItem::CreateGui()
{
// Background
SetCornerRadius(FromDIP(5));
SetBackgroundColor(*wxWHITE);
// Top H
wxSizer *top_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_id = new Label(this);
m_nozzle_label_id->SetFont(Label::Body_12);
m_nozzle_label_id->SetBackgroundColour(*wxWHITE);
m_nozzle_label_id->SetLabel(wxString::Format("%d", m_nozzle_id + 1));
m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);
m_nozzle_icon = new wxStaticBitmap(this, wxID_ANY, m_nozzle_empty_image->bmp(), wxDefaultPosition, WX_DIP_SIZE_46);
m_nozzle_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_selected_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, WX_DIP_SIZE(20, 20));
m_nozzle_selected_bitmap->SetBackgroundColour(*wxWHITE);
top_h_sizer->Add(m_nozzle_label_id, 0, wxTOP | wxLEFT, FromDIP(6));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_icon, 0, wxTOP, FromDIP(10));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_selected_bitmap, 0, wxTOP | wxRIGHT, FromDIP(2));
// Bottom V
wxBoxSizer* bottom_v = new wxBoxSizer(wxVERTICAL);
wxSizer* label_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_1 = new Label(this);
m_nozzle_label_1->SetFont(Label::Body_12);
m_nozzle_label_1->SetBackgroundColour(*wxWHITE);
m_nozzle_label_1->SetLabel(_L("Empty"));
label_h_sizer->Add(m_nozzle_label_1, 0, wxALIGN_LEFT);
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon = new wxStaticBitmap(this, wxID_ANY, status_icon, wxDefaultPosition, WX_DIP_SIZE(14, 14));
m_nozzle_status_icon->Bind(wxEVT_LEFT_DOWN, &wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus, this);
m_nozzle_status_icon->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
m_nozzle_status_icon->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
m_nozzle_status_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_status_icon->Show(false);
label_h_sizer->Add(m_nozzle_status_icon, 0, wxALIGN_CENTER | wxLEFT, FromDIP(2));
bottom_v->Add(label_h_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(2));
m_nozzle_label_2 = new Label(this);
m_nozzle_label_2->SetFont(Label::Body_12);
m_nozzle_label_2->SetBackgroundColour(*wxWHITE);
bottom_v->Add(m_nozzle_label_2, 0, wxALIGN_CENTER_HORIZONTAL);
// Main sizer
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(top_h_sizer, 0, wxEXPAND);
main_sizer->Add(bottom_v, 0, wxALIGN_CENTER_HORIZONTAL);
SetSizer(main_sizer);
SetMinSize(WGT_RACK_NOZZLE_SIZE);
SetMaxSize(WGT_RACK_NOZZLE_SIZE);
SetSize(WGT_RACK_NOZZLE_SIZE);
Layout();
};
void wgtDeviceNozzleRackNozzleItem::SetSelected(bool selected)
{
if (!m_enable_select){
assert(false && "not support select");
return;
}
if (m_is_selected != selected) {
m_is_selected = selected;
if (selected) {
if (!m_nozzle_selected_image) {
m_nozzle_selected_image = new ScalableBitmap(this, "dev_rack_nozzle_selected", 20);
}
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
SetBorderColor(StateColor::darkModeColorFor(s_hgreen_clr));
} else {
m_nozzle_selected_bitmap->SetBitmap(wxNullBitmap);
SetBorderColor(StateColor::darkModeColorFor(s_gray_clr));
}
Refresh();
}
}
void wgtDeviceNozzleRackNozzleItem::Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/)
{
m_rack = rack;
if (rack) {
const auto &nozzle_info = on_rack ? rack->GetNozzle(m_nozzle_id) : rack->GetNozzleSystem()->GetExtNozzle(m_nozzle_id);
const wxString &diameter_str = nozzle_info.GetNozzleDiameterStr();
const wxString &flowtype_str = nozzle_info.GetNozzleFlowTypeStr();
const std::string &color = nozzle_info.GetFilamentColor();
/*check empty first*/
if (nozzle_info.IsEmpty()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_EMPTY, _L("Empty"), wxEmptyString, color);
} else if (nozzle_info.IsNormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_NORMAL, diameter_str, flowtype_str, color);
} else if (nozzle_info.IsAbnormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_ERROR, _L("Error"), wxEmptyString, color);
} else if (nozzle_info.IsUnknown()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_UNKNOWN, _L("Unknown"), wxEmptyString, color);
}
}
}
void wgtDeviceNozzleRackNozzleItem::SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color)
{
if (m_status != status || m_filament_color != color)
{
m_status = status;
m_filament_color = color;
switch (status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
if (!m_nozzle_empty_image) { m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
if (!m_nozzle_normal_image) { m_nozzle_normal_image = new ScalableBitmap(this, "dev_rack_nozzle_normal", 46);}
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
if (!m_nozzle_unknown_image) { m_nozzle_unknown_image = new ScalableBitmap(this, "dev_rack_nozzle_unknown", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
if (!m_nozzle_error_image) { m_nozzle_error_image = new ScalableBitmap(this, "dev_rack_nozzle_error", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
}
if (status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(s_red_clr));
m_nozzle_status_icon->Show(true);
}
else
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
m_nozzle_status_icon->Show(false);
}
}
bool update_layout = (m_nozzle_label_1->GetLabel() != str1 || m_nozzle_label_2->GetLabel() != str2);
m_nozzle_label_1->SetLabel(str1);
m_nozzle_label_2->SetLabel(str2);
if (update_layout) {
Layout();
}
}
void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
{
if (m_is_disabled) {
return;
}
auto rack = m_rack.lock();
if (rack && m_status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
MessageDialog dlg(nullptr, _L("The hotend is in an abnormal state and currently unavailable. "
"Please go to 'Device -> Upgrade' to upgrade firmware."), _L("Abnormal Hotend"), wxICON_WARNING);
dlg.AddButton(wxID_CANCEL, _L("Cancel"), false);
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
if (dlg.ShowModal() == wxID_OK) {
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
};
}
}
void wgtDeviceNozzleRackNozzleItem::Rescale()
{
if (m_nozzle_normal_image) { m_nozzle_normal_image->msw_rescale(); }
if (m_nozzle_empty_image) { m_nozzle_empty_image->msw_rescale(); }
if (m_nozzle_unknown_image) { m_nozzle_unknown_image->msw_rescale(); }
if (m_nozzle_error_image) { m_nozzle_error_image->msw_rescale(); }
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon->SetBitmap(status_icon);
m_nozzle_status_icon->Refresh();
if (m_nozzle_selected_image) {
m_nozzle_selected_image->msw_rescale();
if (m_is_selected) {
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
}
};
switch (m_status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
};
};
void wgtDeviceNozzleRackNozzleItem::EnableSelect()
{
if (m_enable_select == true) {
return;
};
m_enable_select = true;
m_nozzle_icon->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_id->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_1->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_2->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
}
void wgtDeviceNozzleRackNozzleItem::OnItemSelected(wxMouseEvent& evt)
{
if (m_enable_select && !m_is_disabled){
SetSelected(true);
wxCommandEvent command_evt(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, GetId());
command_evt.SetEventObject(this);
ProcessEvent(command_evt);
}
evt.Skip();
}
void wgtDeviceNozzleRackNozzleItem::SetDisable(bool disabled)
{
if (m_is_disabled == disabled) {
return;
}
m_is_disabled = disabled;
auto bg_clr = disabled ? StateColor::darkModeColorFor("#E5E7EB") : StateColor::darkModeColorFor(*wxWHITE);
m_nozzle_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_id->SetBackgroundColour(bg_clr);
m_nozzle_label_1->SetBackgroundColour(bg_clr);
m_nozzle_status_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_2->SetBackgroundColour(bg_clr);
m_nozzle_selected_bitmap->SetBackgroundColour(bg_clr);
SetBackgroundColor(bg_clr);
Refresh();
};
};

View File

@@ -1,31 +1,22 @@
//**********************************************************/
/* File: wgtDeviceNozzleRack.h
* Description: The Device-tab panel with the toolhead nozzle and the H2C induction hotend rack.
* Description: The panel with rack and nozzles
*
* \n class wgtDeviceNozzleRack; // toolhead panel + rack area, side by side
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackArea;
* \n class wgtDeviceNozzleRackNozzleItem;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackPos;
*
* The single-nozzle tile class wgtDeviceNozzleRackNozzleItem lives in its own translation unit
* (wgtDeviceNozzleRackNozzleItem.{h,cpp}), so it is included here rather than redeclared.
* The Hotends-Info upgrade dialog (wgtDeviceNozzleRackUpgradeDlg) is intentionally not wired yet
* (see the TODO markers in the .cpp).
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "wgtDeviceNozzleRackNozzleItem.h"
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include "slic3r/GUI/Widgets/AnimaController.hpp"
#include <wx/panel.h>
#include <wx/simplebook.h>
#include <memory>
#include <unordered_map>
#include <vector>
// Previous definitions
class Button;
@@ -47,6 +38,9 @@ namespace GUI
}
};
// Events
wxDECLARE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
class wgtDeviceNozzleRack : public wxPanel
@@ -148,8 +142,6 @@ private:
Label* m_progress_refresh{ nullptr };
AnimaIcon* m_refresh_icon{ nullptr };
// "Hotends Info" upgrade dialog. Owned for its ShowModal lifetime by
// OnBtnHotendsInfos; UpdateRackInfo forwards live device pushes to it while it is shown.
wgtDeviceNozzleRackUpgradeDlg* m_rack_upgrade_dlg = nullptr;
};
@@ -193,4 +185,71 @@ private:
ScalableButton* m_btn_homing{ nullptr };
};
};// end of namespace Slic3r::GUI
class wgtDeviceNozzleRackNozzleItem : public StaticBox
{
public:
enum NOZZLE_STATUS
{
NOZZLE_EMPTY,
NOZZLE_NORMAL,
NOZZLE_UNKNOWN,
NOZZLE_ERROR
};
public:
wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id);
public:
void Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack = true); // on_rack is false means extruder nozzle
int GetNozzleId() const { return m_nozzle_id; }
void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);};
void EnableSelect();;
void SetSelected(bool selected);
bool IsSelected() const { return m_is_selected; }
bool IsDisabled() const { return m_is_disabled; }
void SetDisable(bool disabled);
void Rescale();
private:
void CreateGui();
void SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color);
void OnBtnNozzleStatus(wxMouseEvent& evt);
void OnItemSelected(wxMouseEvent& evt);
private:
std::weak_ptr<DevNozzleRack> m_rack;
int m_nozzle_id; // internal id, from 0 to 5
std::string m_filament_color;
NOZZLE_STATUS m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
// select
bool m_is_selected = false;
bool m_enable_select = false;
ScalableBitmap* m_nozzle_selected_image{ nullptr };
wxStaticBitmap* m_nozzle_selected_bitmap{ nullptr };
// enable or disable
bool m_is_disabled = false;
// Images
ScalableBitmap* m_nozzle_normal_image{ nullptr };
ScalableBitmap* m_nozzle_empty_image{ nullptr };
ScalableBitmap* m_nozzle_unknown_image{ nullptr };
ScalableBitmap* m_nozzle_error_image{ nullptr };
// GUI
wxStaticBitmap* m_nozzle_icon{ nullptr };
Label* m_nozzle_label_id { nullptr };
Label* m_nozzle_label_1{ nullptr };
wxStaticBitmap* m_nozzle_status_icon = nullptr;
Label* m_nozzle_label_2{ nullptr };
};
};// end of namespace Slic3r::GUI

View File

@@ -1,347 +0,0 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackNozzleItem.cpp
* Description: One nozzle cell of the H2C hotend rack view. Holds the
* wgtDeviceNozzleRackNozzleItem widget, the SetNozzleBmpColor helper and the layout
* constants it needs.
//**********************************************************/
#include "wgtDeviceNozzleRackNozzleItem.h"
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#define WX_DIP_SIZE_46 wxSize(FromDIP(46), FromDIP(46))
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
#define WGT_RACK_NOZZLE_SIZE WX_DIP_SIZE(88, 100)
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#009688");
static wxColour s_red_clr("#D01B1B");
wxDEFINE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
static wxBitmap SetNozzleBmpColor(const wxBitmap& bmp, const std::string& color_str) {
if(color_str.empty()) return bmp;
wxImage img = bmp.ConvertToImage();
wxColour color("#" + color_str);
for (int y = 0; y < img.GetHeight(); ++y) {
for (int x = 0; x < img.GetWidth(); ++x) {
unsigned char r = img.GetRed(x, y);
unsigned char g = img.GetGreen(x, y);
unsigned char b = img.GetBlue(x, y);
/*replace yellow with color*/
if ( r >= 180 && g >= 180 && b <= 150) {
img.SetRGB(x, y, color.Red(), color.Green(), color.Blue());
}
}
}
return wxBitmap(img, -1, bmp.GetScaleFactor());
}
wgtDeviceNozzleRackNozzleItem::wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id)
: StaticBox(parent, wxID_ANY), m_nozzle_id(nozzle_id)
{
CreateGui();
}
void wgtDeviceNozzleRackNozzleItem::CreateGui()
{
// Background
SetCornerRadius(FromDIP(5));
SetBackgroundColor(*wxWHITE);
// Top H
wxSizer *top_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_id = new Label(this);
m_nozzle_label_id->SetFont(Label::Body_12);
m_nozzle_label_id->SetBackgroundColour(*wxWHITE);
m_nozzle_label_id->SetLabel(wxString::Format("%d", m_nozzle_id + 1));
m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);
m_nozzle_icon = new wxStaticBitmap(this, wxID_ANY, m_nozzle_empty_image->bmp(), wxDefaultPosition, WX_DIP_SIZE_46);
m_nozzle_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_selected_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, WX_DIP_SIZE(20, 20));
m_nozzle_selected_bitmap->SetBackgroundColour(*wxWHITE);
top_h_sizer->Add(m_nozzle_label_id, 0, wxTOP | wxLEFT, FromDIP(6));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_icon, 0, wxTOP, FromDIP(10));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_selected_bitmap, 0, wxTOP | wxRIGHT, FromDIP(2));
// Bottom V
wxBoxSizer* bottom_v = new wxBoxSizer(wxVERTICAL);
wxSizer* label_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_1 = new Label(this);
m_nozzle_label_1->SetFont(Label::Body_12);
m_nozzle_label_1->SetBackgroundColour(*wxWHITE);
m_nozzle_label_1->SetLabel(_L("Empty"));
label_h_sizer->Add(m_nozzle_label_1, 0, wxALIGN_LEFT);
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon = new wxStaticBitmap(this, wxID_ANY, status_icon, wxDefaultPosition, WX_DIP_SIZE(14, 14));
m_nozzle_status_icon->Bind(wxEVT_LEFT_DOWN, &wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus, this);
m_nozzle_status_icon->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
m_nozzle_status_icon->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
m_nozzle_status_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_status_icon->Show(false);
label_h_sizer->Add(m_nozzle_status_icon, 0, wxALIGN_CENTER | wxLEFT, FromDIP(2));
bottom_v->Add(label_h_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(2));
m_nozzle_label_2 = new Label(this);
m_nozzle_label_2->SetFont(Label::Body_12);
m_nozzle_label_2->SetBackgroundColour(*wxWHITE);
bottom_v->Add(m_nozzle_label_2, 0, wxALIGN_CENTER_HORIZONTAL);
// Main sizer
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(top_h_sizer, 0, wxEXPAND);
main_sizer->Add(bottom_v, 0, wxALIGN_CENTER_HORIZONTAL);
SetSizer(main_sizer);
SetMinSize(WGT_RACK_NOZZLE_SIZE);
SetMaxSize(WGT_RACK_NOZZLE_SIZE);
SetSize(WGT_RACK_NOZZLE_SIZE);
Layout();
};
void wgtDeviceNozzleRackNozzleItem::SetSelected(bool selected)
{
if (!m_enable_select){
assert(false && "not support select");
return;
}
if (m_is_selected != selected) {
m_is_selected = selected;
if (selected) {
if (!m_nozzle_selected_image) {
m_nozzle_selected_image = new ScalableBitmap(this, "dev_rack_nozzle_selected", 20);
}
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
SetBorderColor(StateColor::darkModeColorFor(s_hgreen_clr));
} else {
m_nozzle_selected_bitmap->SetBitmap(wxNullBitmap);
SetBorderColor(StateColor::darkModeColorFor(s_gray_clr));
}
Refresh();
}
}
void wgtDeviceNozzleRackNozzleItem::Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/)
{
m_rack = rack;
if (rack) {
const auto &nozzle_info = on_rack ? rack->GetNozzle(m_nozzle_id) : rack->GetNozzleSystem()->GetExtNozzle(m_nozzle_id);
const wxString &diameter_str = nozzle_info.GetNozzleDiameterStr();
const wxString &flowtype_str = nozzle_info.GetNozzleFlowTypeStr();
const std::string &color = nozzle_info.GetFilamentColor();
/*check empty first*/
if (nozzle_info.IsEmpty()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_EMPTY, _L("Empty"), wxEmptyString, color);
} else if (nozzle_info.IsNormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_NORMAL, diameter_str, flowtype_str, color);
} else if (nozzle_info.IsAbnormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_ERROR, _L("Error"), wxEmptyString, color);
} else if (nozzle_info.IsUnknown()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_UNKNOWN, _L("Unknown"), wxEmptyString, color);
}
}
}
void wgtDeviceNozzleRackNozzleItem::SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color)
{
if (m_status != status || m_filament_color != color)
{
m_status = status;
m_filament_color = color;
switch (status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
if (!m_nozzle_empty_image) { m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
if (!m_nozzle_normal_image) { m_nozzle_normal_image = new ScalableBitmap(this, "dev_rack_nozzle_normal", 46);}
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
if (!m_nozzle_unknown_image) { m_nozzle_unknown_image = new ScalableBitmap(this, "dev_rack_nozzle_unknown", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
if (!m_nozzle_error_image) { m_nozzle_error_image = new ScalableBitmap(this, "dev_rack_nozzle_error", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
}
if (status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(s_red_clr));
m_nozzle_status_icon->Show(true);
}
else
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
m_nozzle_status_icon->Show(false);
}
}
bool update_layout = (m_nozzle_label_1->GetLabel() != str1 || m_nozzle_label_2->GetLabel() != str2);
m_nozzle_label_1->SetLabel(str1);
m_nozzle_label_2->SetLabel(str2);
if (update_layout) {
Layout();
}
}
void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
{
if (m_is_disabled) {
return;
}
auto rack = m_rack.lock();
if (rack && m_status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
// Orca: show the abnormal-hotend warning as an informational dialog only, with no
// "Jump to the upgrade page" button, since Orca's MessageDialog and device Upgrade UI
// have no such entry point. Fires when tapping an error-state rack nozzle's status icon.
MessageDialog dlg(nullptr, _L("The hotend is in an abnormal state and currently unavailable. "
"Please go to 'Device -> Upgrade' to upgrade firmware."), _L("Abnormal Hotend"), wxICON_WARNING | wxOK);
dlg.ShowModal();
}
}
void wgtDeviceNozzleRackNozzleItem::Rescale()
{
if (m_nozzle_normal_image) { m_nozzle_normal_image->msw_rescale(); }
if (m_nozzle_empty_image) { m_nozzle_empty_image->msw_rescale(); }
if (m_nozzle_unknown_image) { m_nozzle_unknown_image->msw_rescale(); }
if (m_nozzle_error_image) { m_nozzle_error_image->msw_rescale(); }
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon->SetBitmap(status_icon);
m_nozzle_status_icon->Refresh();
if (m_nozzle_selected_image) {
m_nozzle_selected_image->msw_rescale();
if (m_is_selected) {
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
}
};
switch (m_status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
};
};
void wgtDeviceNozzleRackNozzleItem::EnableSelect()
{
if (m_enable_select == true) {
return;
};
m_enable_select = true;
m_nozzle_icon->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_id->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_1->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_2->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
}
void wgtDeviceNozzleRackNozzleItem::OnItemSelected(wxMouseEvent& evt)
{
if (m_enable_select && !m_is_disabled){
SetSelected(true);
wxCommandEvent command_evt(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, GetId());
command_evt.SetEventObject(this);
ProcessEvent(command_evt);
}
evt.Skip();
}
void wgtDeviceNozzleRackNozzleItem::SetDisable(bool disabled)
{
if (m_is_disabled == disabled) {
return;
}
m_is_disabled = disabled;
auto bg_clr = disabled ? StateColor::darkModeColorFor("#E5E7EB") : StateColor::darkModeColorFor(*wxWHITE);
m_nozzle_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_id->SetBackgroundColour(bg_clr);
m_nozzle_label_1->SetBackgroundColour(bg_clr);
m_nozzle_status_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_2->SetBackgroundColour(bg_clr);
m_nozzle_selected_bitmap->SetBackgroundColour(bg_clr);
SetBackgroundColor(bg_clr);
Refresh();
};
};// end of namespace Slic3r::GUI

View File

@@ -1,99 +0,0 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackNozzleItem.h
* Description: One nozzle cell of the H2C hotend rack view.
*
* This header defines only the single-nozzle-cell widget (and the selection event it emits), which
* is the one dependency MultiNozzleSync's HotEndTable needs. The rest of the Device-tab rack panel
* (wgtDeviceNozzleRack / ...Area / ...ToolHead / ...Pos) is not provided here.
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include "slic3r/GUI/Widgets/Label.hpp" // complete type required by the inline SetDisplayIdText below
#include <wx/panel.h>
#include <memory>
// Previous definitions
class ScalableBitmap;
namespace Slic3r
{
struct DevNozzle;
class DevNozzleRack;
};
// Events
wxDECLARE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
class wgtDeviceNozzleRackNozzleItem : public StaticBox
{
public:
enum NOZZLE_STATUS
{
NOZZLE_EMPTY,
NOZZLE_NORMAL,
NOZZLE_UNKNOWN,
NOZZLE_ERROR
};
public:
wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id);
public:
void Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack = true); // on_rack is false means extruder nozzle
int GetNozzleId() const { return m_nozzle_id; }
void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);};
void EnableSelect();;
void SetSelected(bool selected);
bool IsSelected() const { return m_is_selected; }
bool IsDisabled() const { return m_is_disabled; }
void SetDisable(bool disabled);
void Rescale();
private:
void CreateGui();
void SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color);
void OnBtnNozzleStatus(wxMouseEvent& evt);
void OnItemSelected(wxMouseEvent& evt);
private:
std::weak_ptr<DevNozzleRack> m_rack;
int m_nozzle_id; // internal id, from 0 to 5
std::string m_filament_color;
NOZZLE_STATUS m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
// select
bool m_is_selected = false;
bool m_enable_select = false;
ScalableBitmap* m_nozzle_selected_image{ nullptr };
wxStaticBitmap* m_nozzle_selected_bitmap{ nullptr };
// enable or disable
bool m_is_disabled = false;
// Images
ScalableBitmap* m_nozzle_normal_image{ nullptr };
ScalableBitmap* m_nozzle_empty_image{ nullptr };
ScalableBitmap* m_nozzle_unknown_image{ nullptr };
ScalableBitmap* m_nozzle_error_image{ nullptr };
// GUI
wxStaticBitmap* m_nozzle_icon{ nullptr };
Label* m_nozzle_label_id { nullptr };
Label* m_nozzle_label_1{ nullptr };
wxStaticBitmap* m_nozzle_status_icon = nullptr;
Label* m_nozzle_label_2{ nullptr };
};
};// end of namespace Slic3r::GUI

View File

@@ -1,6 +1,6 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackUpdate.cpp
* Description: The dialog for reading/upgrading the H2C induction-hotend-rack firmware.
* Description: The panel with rack updating
*
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
@@ -8,6 +8,7 @@
#include "wgtDeviceNozzleRackUpdate.h"
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/DeviceCore/DevUpgrade.h"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/GUI_App.hpp"
@@ -103,7 +104,7 @@ void wgtDeviceNozzleRackUprade::CreateGui()
wxPanel* separator = new wxPanel(this);
separator->SetMaxSize(wxSize(-1, FromDIP(1)));
separator->SetMinSize(wxSize(-1, FromDIP(1)));
separator->SetBackgroundColour(wxColour(238, 238, 238));// Orca: grey300 spelled out as a literal RGB, since Orca has no equivalent grey-300 colour macro
separator->SetBackgroundColour(wxColour(238, 238, 238)); // Orca: grey300 as a literal, no equivalent colour macro in Orca
main_sizer->Add(separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(12));
}
}
@@ -215,10 +216,13 @@ void wgtDeviceNozzleRackHotendUpdate::CreateGui()
m_colour_box->SetMinSize(WX_DIP_SIZE(16, 16));
m_colour_box->SetCornerRadius(FromDIP(2));
m_colour_box->SetSize(wxSize(FromDIP(16), FromDIP(16)));
// m_colour_box->SetBackgroundColour(*wxRED);
// type_sizer_row_1->AddStretchSpacer();
type_sizer_row_1->Add(m_colour_box, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
type_sizer_row_1->AddStretchSpacer(1);
type_sizer_row_1->Add(m_material_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
// type_sizer_row_1->AddStretchSpacer();
m_diameter_label = new Label(type_panel);
m_diameter_label->SetFont(Label::Body_12);
@@ -277,19 +281,20 @@ void wgtDeviceNozzleRackHotendUpdate::CreateGui()
m_used_time->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_refresh_icon = new ScalableBitmap(this, "refresh_printer", 12);
// m_in_refreh_icon = new ScalableBitmap(this, "refresh_nozzle", 12);
m_error_icon = new ScalableBitmap(this, "error", 14);
m_status_bitmap = new wxStaticBitmap(this, wxID_ANY, m_refresh_icon->bmp());
m_status_bitmap->Bind(wxEVT_LEFT_UP, &wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick, this);
std::vector<std::string> list{"refresh_nozzle_1", "refresh_nozzle_2", "refresh_nozzle_3", "refresh_nozzle_4"};
// Orca: AnimaIcon has no per-instance size argument (it fixes 25px internally), so no size is
// passed here.
// Orca: AnimaIcon has no per-instance size argument (it fixes the size internally), so none is passed.
m_refreshing_icon = new AnimaIcon(this, wxID_ANY, list, "refresh_nozzle", 100);
m_refreshing_icon->Show(false);
m_status_label = new Label(this);
m_status_label->SetFont(Label::Body_12);
// m_status_label->SetForegroundColour(wxColour("#00AE42"));
content_sizer->Add(info_panel, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
content_sizer->Add(m_used_time, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
@@ -312,7 +317,8 @@ void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
m_status_label->SetForegroundColour(wxColour("#A3A3A3"));
m_status_label->SetLabel(_L("Refreshing"));
m_status_bitmap->Show(false);
if(!m_refreshing_icon->IsPlaying())
// m_status_bitmap->Refresh();
if(!m_refreshing_icon->IsPlaying())
{
m_refreshing_icon->Play();
m_refreshing_icon->Show();
@@ -333,17 +339,17 @@ void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
dlg.AddButton(wxID_CANCEL, _L("Cancel"), false);
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
if (dlg.ShowModal() == wxID_OK)
if (dlg.ShowModal() == wxID_OK)
{
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
wxCommandEvent evt(wxEVT_NOZZLE_JUMP_UPGRADE, GetId());
evt.SetEventObject(this);
wxWindow* target = GetParent();
if (target)
if (target)
{
target = target->GetParent();
if (target)
if (target)
{
wxPostEvent(target, evt);
}
@@ -370,8 +376,8 @@ void wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverEnter(wxMouseEvent& event)
scaledBmp = m_scaled_nozzle_image;
}
m_hoverFrame = new wxFrame(nullptr, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
m_hoverFrame = new wxFrame(nullptr, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
wxFRAME_NO_TASKBAR | wxBORDER_NONE | wxTRANSPARENT_WINDOW);
m_hoverFrame->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_hoverFrame->SetSize(scaledW, scaledH);
@@ -506,11 +512,11 @@ void wgtDeviceNozzleRackHotendUpdate::UpdateInfo(const DevNozzle& nozzle)
}
wxString filamentDisplayName{};
for (auto iter = GUI::wxGetApp().preset_bundle->filaments.begin(); iter != GUI::wxGetApp().preset_bundle->filaments.end(); ++iter)
for (auto iter = GUI::wxGetApp().preset_bundle->filaments.begin(); iter != GUI::wxGetApp().preset_bundle->filaments.end(); ++iter)
{
const Preset& filament_preset = *iter;
// const auto& config = filament_preset.config;
if (filament_preset.filament_id == nozzle.GetFilamentId())
if (filament_preset.filament_id == nozzle.GetFilamentId())
{
filamentDisplayName = wxString(filament_preset.alias);
}
@@ -612,7 +618,7 @@ void wgtDeviceNozzleRackHotendUpdate::UpdateInfo(const DevNozzle& nozzle)
m_used_time->Show(true);
m_status_label->Show(true);
m_status_bitmap->Show(true);
m_status_label->SetForegroundColour(wxColour("#009688"));
m_status_label->SetForegroundColour(wxColour("#009688")); // Orca: accent green
m_status_label->SetLabel(_L("Refresh"));
m_status_bitmap->SetBitmap(m_refresh_icon->bmp());
m_status_bitmap->Refresh();

View File

@@ -1,16 +1,8 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackUpdate.h
* Description: The dialog for reading/upgrading the H2C induction-hotend-rack firmware.
* Description: The panel for updating hotends
*
* \n class wgtDeviceNozzleRackUpgradeDlg; // modal "Hotends Info" dialog
* \n class wgtDeviceNozzleRackUprade; // the dialog's content panel (per-nozzle rows)
* \n class wgtDeviceNozzleRackHotendUpdate; // one hotend row (toolhead "R" + rack 1..6)
*
* Opened from the Device-tab rack panel's "Hotends Info" button (wgtDeviceNozzleRack.cpp) and
* reused by the Device->Upgrade page "Hotends on Rack" section
* (UpgradePanel::createNozzleRackWidgets). All device data + MQTT commands live in
* DevNozzleRack / DevNozzleSystem, so this is additive GUI gated behind
* DevNozzleRack::IsSupported() — dormant for every non-rack printer.
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
#pragma once
@@ -22,8 +14,6 @@
#include <wx/panel.h>
#include <memory>
#include <unordered_map>
#include <vector>
// Previous definitions
class Button;
@@ -49,7 +39,7 @@ public:
wgtDeviceNozzleRackUpgradeDlg(wxWindow* parent, const std::shared_ptr<DevNozzleRack> rack);
public:
void UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack);
void UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack);;
public:
void on_dpi_changed(const wxRect& suggested_rect) override;
@@ -146,6 +136,7 @@ private:
ScalableBitmap* m_scaled_nozzle_image = nullptr;
ScalableBitmap* m_scaled_nozzle_empty_image = nullptr;
ScalableBitmap* m_refresh_icon = nullptr;
// ScalableBitmap* m_in_refreh_icon = nullptr;
AnimaIcon* m_refreshing_icon = nullptr;
ScalableBitmap* m_error_icon = nullptr;
@@ -159,7 +150,7 @@ private:
Label* m_status_label{ nullptr };
Label* m_used_time{ nullptr };
Label* m_diameter_label;
Label* m_flowtype_label;
Label* m_type_label;
@@ -172,4 +163,4 @@ private:
wxDECLARE_EVENT(wxEVT_NOZZLE_JUMP_UPGRADE, wxCommandEvent);
};// end of namespace Slic3r::GUI
};// end of namespace Slic3r::GUI

View File

@@ -6,14 +6,14 @@
//**********************************************************/
#include "wgtDeviceNozzleSelect.h"
#include "wgtDeviceNozzleRackNozzleItem.h" // the nozzle-item widget lives in its own header
#include "wgtDeviceNozzleRack.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/DeviceTab/wgtMsgBox.h"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/Label.hpp" // Orca: explicit Label include
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#009688");
static wxColour s_hgreen_clr("#009688"); // Orca: accent green
static wxColour s_red_clr("#D01B1B");
static std::vector<int> a_nozzle_seq = {16, 18, 20, 17, 19, 21};
@@ -132,7 +132,7 @@ void wgtDeviceNozzleRackSelect::UpdateNozzleInfos(std::shared_ptr<DevNozzleRack>
}
}
static void s_enable_item_if_match(wgtDeviceNozzleRackNozzleItem* item,
static void s_enable_item_if_match(wgtDeviceNozzleRackNozzleItem* item,
const DevNozzle& nozzle_info,
const DevNozzle& selected_nozzle)
{
@@ -214,7 +214,7 @@ void wgtDeviceNozzleRackSelect::UpdatSelectedNozzles(std::shared_ptr<DevNozzleRa
}
}
void wgtDeviceNozzleRackSelect::ClearSelection()
void wgtDeviceNozzleRackSelect::ClearSelection()
{
m_selected_nozzle = DevNozzle();
m_toolhead_nozzle_l->SetSelected(false);

View File

@@ -7,14 +7,11 @@
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h" // DevNozzle (value member) + DevDefs.h (PrintFromType, MAIN/DEPUTY_EXTRUDER_ID)
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include <wx/panel.h>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
// Previous definitions
class Label;
@@ -71,4 +68,4 @@ private:
std::unordered_map<int, wgtDeviceNozzleRackNozzleItem *> m_nozzle_items; // from 16 to 21
};
};// end of namespace Slic3r::GUI
};// end of namespace Slic3r::GUI

View File

@@ -28,4 +28,4 @@ private:
};
}
#endif // WGTMSGBOX_H
#endif // WGTMSGBOX_H

View File

@@ -0,0 +1,163 @@
#include "wgtMsgPanel.h"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/StateColor.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include <wx/sizer.h>
namespace Slic3r::GUI
{
// ===== wgtMsgPanelItem ======================================================
wgtMsgPanelItem::wgtMsgPanelItem(wxWindow* parent,
const wxColour& colour,
const wxString& text,
int max_width,
const wxString& wiki_url)
: wxPanel(parent, wxID_ANY)
, m_max_width(max_width)
, m_colour(colour)
, m_text(text)
, m_wiki_url(wiki_url)
{
CreateGui();
}
void wgtMsgPanelItem::CreateGui()
{
SetBackgroundColour(StateColor::darkModeColorFor(wxColour(*wxWHITE)));
wxBoxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
// Color bar on the left
//wxPanel* color_bar = new wxPanel(this, wxID_ANY);
//color_bar->SetMinSize(wxSize(FromDIP(4), -1));
//color_bar->SetBackgroundColour(m_colour);
//main_sizer->Add(color_bar, 0, wxEXPAND | wxRIGHT, FromDIP(4));
// Text and optional wiki link
wxBoxSizer* text_sizer = new wxBoxSizer(wxHORIZONTAL);
m_text_label = new ::Label(this, m_text);
m_text_label->SetFont(::Label::Body_13);
m_text_label->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(*wxWHITE)));
m_text_label->SetForegroundColour(m_colour);
if (!m_wiki_url.IsEmpty()) {
m_text_label->Wrap(m_max_width - FromDIP(20)); // Wrap text to fit within a reasonable width
} else {
m_text_label->Wrap(m_max_width - FromDIP(10)); // Wrap text to fit within a reasonable width
}
text_sizer->Add(m_text_label, 0, wxALIGN_CENTER_VERTICAL);
if (!m_wiki_url.IsEmpty()) {
m_wiki_link = new wxHyperlinkCtrl(this, wxID_ANY, "Wiki->", m_wiki_url);
m_wiki_link->SetNormalColour(m_colour);
m_wiki_link->SetHoverColour(wxColour(0, 0, 200));
m_wiki_link->SetVisitedColour(*wxBLUE);
Bind(wxEVT_HYPERLINK, &wgtMsgPanelItem::OnClickWiki, this, m_wiki_link->GetId());
text_sizer->AddSpacer(FromDIP(4));
text_sizer->Add(m_wiki_link, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND);
}
main_sizer->Add(text_sizer, 0, wxALIGN_LEFT | wxEXPAND);
SetSizer(main_sizer);
Layout();
Fit();
}
void wgtMsgPanelItem::SetColour(const wxColour& colour)
{
m_colour = colour;
// Simple handling: refresh the whole control to redraw the color bar
Refresh();
}
void wgtMsgPanelItem::SetText(const wxString& text)
{
m_text = text;
if (m_text_label) {
m_text_label->SetLabel(m_text);
}
}
void wgtMsgPanelItem::SetWiki(const wxString& wiki_url)
{
m_wiki_url = wiki_url;
if (m_wiki_link) {
m_wiki_link->SetURL(m_wiki_url);
}
}
void wgtMsgPanelItem::OnClickWiki(wxHyperlinkEvent& evt)
{
if (!m_wiki_url.IsEmpty()) {
wxLaunchDefaultBrowser(m_wiki_url);
}
}
// ===== wgtMsgPanel ==========================================================
wgtMsgPanel::wgtMsgPanel(wxWindow* parent)
: wxPanel(parent, wxID_ANY)
{
CreateGui();
}
void wgtMsgPanel::CreateGui()
{
SetBackgroundColour(StateColor::darkModeColorFor(wxColour(*wxWHITE)));
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
m_label_title = new Label(this, _L("Note:"));
m_label_title->SetFont(::Label::Body_14);
main_sizer->Add(m_label_title, 0, wxEXPAND | wxALL, FromDIP(4));
m_list_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_list_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(4));
SetSizer(main_sizer);
Layout();
}
void wgtMsgPanel::AddMessage(const wxString& text,
const wxColour& colour,
const wxString& wiki_url)
{
if (!m_list_sizer) return;
auto* label = new ::Label(this, wxString::Format("%d. ", GetMessageCount() + 1));
label->SetFont(::Label::Body_14);
label->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(*wxWHITE)));
label->SetForegroundColour(colour);
auto* item = new wgtMsgPanelItem(this, colour, text, GetSize().GetWidth(), wiki_url);
auto h_sizer = new wxBoxSizer(wxHORIZONTAL);
h_sizer->Add(label, 0, wxEXPAND);
h_sizer->Add(item, 0, wxEXPAND);
m_list_sizer->Add(h_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(2));
Layout();
}
void wgtMsgPanel::Clear()
{
if (!m_list_sizer) return;
Freeze();
wxSizerItemList children = m_list_sizer->GetChildren();
for (auto it = children.begin(); it != children.end(); ++it) {
if (wxWindow* win = (*it)->GetWindow()) {
win->Destroy();
}
}
m_list_sizer->Clear(true);
Thaw();
Layout();
}
} // namespace Slic3r::GUI

View File

@@ -0,0 +1,69 @@
#pragma once
#include <wx/panel.h>
#include <wx/hyperlink.h>
#include <wx/sizer.h>
#include <string>
class Label;
namespace Slic3r
{
namespace GUI
{
// Single message item, including color, text and optional wiki link
class wgtMsgPanelItem : public wxPanel
{
public:
wgtMsgPanelItem(wxWindow* parent,
const wxColour& colour,
const wxString& text,
int max_width,
const wxString& wiki_url = wxEmptyString);
void SetColour(const wxColour& colour);
void SetText(const wxString& text);
void SetWiki(const wxString& wiki_url);
private:
void CreateGui();
void OnClickWiki(wxHyperlinkEvent& evt);
private:
int m_max_width;
wxColour m_colour;
wxString m_text;
wxString m_wiki_url;
Label* m_text_label{ nullptr };
wxHyperlinkCtrl* m_wiki_link{ nullptr };
};
// Message panel based on wxWidget, shows wgtMsgPanelItem in a list
class wgtMsgPanel : public wxPanel
{
public:
explicit wgtMsgPanel(wxWindow* parent);
~wgtMsgPanel() override = default;
// Add a message (preferred API)
void AddMessage(const wxString& text,
const wxColour& colour,
const wxString& wiki_url = wxEmptyString);
void Clear();
// Const API
int GetMessageCount() const { return m_list_sizer ? m_list_sizer->GetItemCount() : 0; }
private:
void CreateGui();
private:
Label* m_label_title{ nullptr };
wxBoxSizer* m_list_sizer{ nullptr };
};
}
} // namespace Slic3r::GUI

View File

@@ -8,6 +8,26 @@
namespace Slic3r { namespace GUI {
void fill_gradient_rect_east(wxDC& dc, const wxRect& rect, const wxColour& from, const wxColour& to)
{
if (rect.width <= 0 || rect.height <= 0) return;
auto mix_channel = [](unsigned char a, unsigned char b, double t) {
return static_cast<unsigned char>(a + (b - a) * t + 0.5);
};
dc.SetPen(*wxTRANSPARENT_PEN);
for (int x = 0; x < rect.width; ++x) {
const double t = rect.width > 1 ? static_cast<double>(x) / (rect.width - 1) : 0.0;
const wxColour col(mix_channel(from.Red(), to.Red(), t),
mix_channel(from.Green(), to.Green(), t),
mix_channel(from.Blue(), to.Blue(), t),
mix_channel(from.Alpha(), to.Alpha(), t));
dc.SetBrush(wxBrush(col));
dc.DrawRectangle(rect.x + x, rect.y, 1, rect.height);
}
}
// Helper struct to hold bitmap and DC
struct BitmapDC {
wxBitmap bitmap;

View File

@@ -3,10 +3,17 @@
#include <wx/bitmap.h>
#include <wx/colour.h>
#include <wx/dc.h>
#include <wx/gdicmn.h>
#include <vector>
namespace Slic3r { namespace GUI {
// Fills a rect with a west->east linear gradient by drawing solid 1px columns.
// Use instead of wxDC::GradientFillLinear, whose CoreGraphics (CGShading) backend
// fails to render on some macOS builds; solid fills are unaffected.
void fill_gradient_rect_east(wxDC& dc, const wxRect& rect, const wxColour& from, const wxColour& to);
enum class FilamentRenderMode {
Single,
Dual,

View File

@@ -1256,13 +1256,14 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
// get_url
std::string url = get_plugin_url(name, app_config->get_country_code());
std::string download_url;
std::string online_version;
Slic3r::Http http_url = Slic3r::Http::get(url);
BOOST_LOG_TRIVIAL(info) << "[download_plugin]: check the plugin from " << url;
http_url.timeout_connect(TIMEOUT_CONNECT)
.timeout_max(TIMEOUT_RESPONSE)
.header("X-BBL-OS-Type", os_type)
.on_complete(
[&download_url](std::string body, unsigned status) {
[&download_url, &online_version](std::string body, unsigned status) {
try {
json j = json::parse(body);
std::string message = j["message"].get<std::string>();
@@ -1272,6 +1273,7 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
if (resource.is_array()) {
for (auto iter = resource.begin(); iter != resource.end(); iter++) {
Semver version;
std::string version_str;
std::string url;
std::string type;
std::string vendor;
@@ -1282,7 +1284,8 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
BOOST_LOG_TRIVIAL(info) << "[download_plugin]: get version of settings's type, " << sub_iter.value();
}
else if (boost::iequals(sub_iter.key(), "version")) {
version = *(Semver::parse(sub_iter.value()));
version_str = sub_iter.value();
version = *(Semver::parse(version_str));
}
else if (boost::iequals(sub_iter.key(), "description")) {
description = sub_iter.value();
@@ -1293,6 +1296,7 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
}
BOOST_LOG_TRIVIAL(info) << "[download_plugin 1]: get type " << type << ", version " << version.to_string() << ", url " << url;
download_url = url;
online_version = version_str;
}
}
}
@@ -1382,6 +1386,26 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
result = -1;
});
http.perform_sync();
// The cloud endpoint serves the newest build of the requested series, which may be
// newer than the configured version. Adopt the actual downloaded version so that
// install_plugin() names the library after what it really contains - otherwise the
// post-load config sync (on_init_network) points at a file name that does not exist
// and the plugin is "lost" on the following launch. Legacy mode is left untouched:
// is_legacy_version() matches exactly and drives the legacy struct layout.
if (result >= 0 && name == "plugins" && !online_version.empty() && !use_legacy_network_plugin()) {
std::string configured = app_config->get_network_plugin_version();
if (configured.empty())
configured = get_latest_network_version();
if (configured != online_version
&& configured.size() >= 8 && online_version.size() >= 8
&& configured.compare(0, 8, online_version, 0, 8) == 0) {
BOOST_LOG_TRIVIAL(info) << "[download_plugin] server returned " << online_version
<< " for configured " << configured << ", adopting it";
app_config->set_network_plugin_version(online_version);
}
}
j["result"] = result < 0 ? "failed" : "success";
j["error_msg"] = err_msg;
return result;
@@ -1958,6 +1982,13 @@ bool GUI_App::check_networking_version()
BOOST_LOG_TRIVIAL(info) << "check_networking_version: network_ver=" << network_ver << ", expected=" << studio_ver;
// A configured version outside the whitelisted series must never pass as compatible,
// even if it matches the loaded library - its ABI does not match this build.
if (!use_legacy_network_plugin() && !is_supported_network_version(studio_ver)) {
m_networking_compatible = false;
return false;
}
if (network_ver.length() >= 8 && studio_ver.length() >= 8) {
if (network_ver.substr(0,8) == studio_ver.substr(0,8)) {
m_networking_compatible = true;
@@ -3396,10 +3427,26 @@ void GUI_App::copy_network_if_available()
if (app_config->get("update_network_plugin") != "true")
return;
bool had_cache = false;
bool installed = install_network_plugin_from_ota(had_cache);
// Success consumes the cache and a missing cache leaves nothing to do; only a
// failed copy keeps the flag so the install is retried on the next launch.
if (installed || !had_cache)
app_config->set("update_network_plugin", "false");
}
// Installs the OTA-downloaded plug-in files from ota/plugins into the plugins folder
// (network library under its versioned name, and the configured version updated to
// match). Returns true when everything was installed; had_cache reports whether a
// usable download was present at all.
bool GUI_App::install_network_plugin_from_ota(bool& had_cache)
{
had_cache = false;
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / "plugins";
auto cache_folder = data_dir_path / "ota";
auto cache_folder = data_dir_path / "ota" / "plugins";
std::string changelog_file = cache_folder.string() + "/network_plugins.json";
std::string cached_version;
@@ -3417,10 +3464,10 @@ void GUI_App::copy_network_if_available()
}
if (cached_version.empty()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no version found in changelog, aborting copy";
app_config->set("update_network_plugin", "false");
return;
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no version found in changelog, nothing to install";
return false;
}
had_cache = true;
std::string network_library, player_library, live555_library, network_library_dst, player_library_dst, live555_library_dst;
#if defined(_MSC_VER) || defined(_WIN32)
@@ -3451,17 +3498,41 @@ void GUI_App::copy_network_if_available()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create directory " << plugin_folder.string();
boost::filesystem::create_directory(plugin_folder);
}
std::string error_message;
if (boost::filesystem::exists(network_library)) {
CopyFileResult cfr = copy_file(network_library, network_library_dst, error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message;
return;
}
// Replace a destination even while the running process still maps it: an in-use
// file cannot be deleted or overwritten on Windows, but it can be renamed aside;
// the stale ".old" copy is swept on the next launch (see on_init_network).
auto install_file = [](const std::string& src, const std::string& dst) -> bool {
boost::system::error_code ec;
if (boost::filesystem::exists(dst, ec)) {
boost::filesystem::remove(dst, ec);
if (ec) {
boost::filesystem::path aside(dst);
aside += ".old";
boost::system::error_code ec2;
boost::filesystem::remove(aside, ec2);
boost::filesystem::rename(dst, aside, ec2);
if (ec2) {
BOOST_LOG_TRIVIAL(error) << "install_network_plugin_from_ota: cannot replace in-use file " << dst << ": " << ec2.message();
return false;
}
}
}
std::string error_message;
CopyFileResult cfr = copy_file(src, dst, error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << "install_network_plugin_from_ota: copying " << src << " failed(" << cfr << "): " << error_message;
return false;
}
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(network_library_dst, perms);
fs::remove(network_library);
fs::permissions(dst, perms);
boost::filesystem::remove(src, ec);
return true;
};
if (boost::filesystem::exists(network_library)) {
if (!install_file(network_library, network_library_dst))
return false;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying network library from " << network_library << " to " << network_library_dst << " successfully.";
app_config->set_network_plugin_version(cached_version);
@@ -3469,33 +3540,24 @@ void GUI_App::copy_network_if_available()
}
if (boost::filesystem::exists(player_library)) {
CopyFileResult cfr = copy_file(player_library, player_library_dst, error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message;
return;
}
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(player_library_dst, perms);
fs::remove(player_library);
if (!install_file(player_library, player_library_dst))
return false;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying player library from " << player_library << " to " << player_library_dst << " successfully.";
}
if (boost::filesystem::exists(live555_library)) {
CopyFileResult cfr = copy_file(live555_library, live555_library_dst, error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message;
return;
}
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(live555_library_dst, perms);
fs::remove(live555_library);
if (!install_file(live555_library, live555_library_dst))
return false;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying live555 library from " << live555_library << " to " << live555_library_dst << " successfully.";
}
if (boost::filesystem::exists(changelog_file))
fs::remove(changelog_file);
app_config->set("update_network_plugin", "false");
// All cached files consumed - drop the whole ota/plugins cache folder.
try {
if (boost::filesystem::exists(cache_folder))
fs::remove_all(cache_folder);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to remove the plugin cache folder " << cache_folder.string();
}
return true;
}
bool GUI_App::on_init_network(bool try_backup)
@@ -3525,6 +3587,21 @@ bool GUI_App::on_init_network(bool try_backup)
std::string config_version = app_config->get_network_plugin_version();
if (should_load_networking_plugin) {
// A version outside the whitelisted series (e.g. 02.03.00.62 configured by an older
// Orca release) must not be loaded - its ABI no longer matches this build. Fall back
// to the latest supported build if it is already on disk; otherwise clear the
// configured version so the normal empty-version download flow takes over (the
// download URL and install adoption both derive from the configured version, so it
// must not keep pointing at the unsupported build).
if (!config_version.empty() && !is_supported_network_version(config_version)) {
std::string latest = get_latest_network_version();
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": configured plugin version " << config_version
<< " is no longer supported, falling back to " << latest;
config_version = BBLNetworkPlugin::versioned_library_exists(latest) ? latest : "";
app_config->set_network_plugin_version(config_version);
app_config->save();
}
if (config_version.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download";
m_networking_need_update = true;
@@ -3579,6 +3656,20 @@ bool GUI_App::on_init_network(bool try_backup)
}
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed";
// A failed install can leave the config naming a build that never made it to
// disk (download_plugin() adopts the downloaded version up front so that
// install_plugin() can name the library after it). If the whitelisted latest
// is still installed, fall back to it instead of dropping the user into the
// re-download flow without networking.
std::string latest = get_latest_network_version();
if (config_version != latest && BBLNetworkPlugin::versioned_library_exists(latest)) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": falling back to installed " << latest;
config_version = latest;
app_config->set_network_plugin_version(latest);
app_config->save();
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
goto __retry;
}
if (should_load_networking_plugin) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, need upload network module";
m_networking_need_update = true;

View File

@@ -764,6 +764,7 @@ public:
void show_network_plugin_download_dialog(bool is_update = false);
bool hot_reload_network_plugin();
bool install_network_plugin_from_ota(bool& had_cache);
std::string get_latest_network_version() const;
bool has_network_update_available() const;
// Orca: return the client version to report to Bambu servers. Pinned to

View File

@@ -12,9 +12,10 @@ static const char* HMS_PATH = "hms";
static const char* HMS_LOCAL_IMG_PATH = "hms/local_image";
// the local HMS info
// Orca: dev-id-type set trimmed to the devices Orca ships local HMS images for
static unordered_set<string> package_dev_id_types {"094", "239", "093", "22E"};
// HMS should be disabled when stealth mode is on or networking is not installed
// Orca: HMS should be disabled when stealth mode is on or networking is not installed
static bool should_disable_hms()
{
Slic3r::AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
@@ -251,7 +252,7 @@ int HMSQuery::save_to_local(std::string lang, std::string hms_type, std::string
std::string dir_str = (hms_folder / filename).make_preferred().string();
std::ofstream json_file(encode_path(dir_str.c_str()));
if (json_file.is_open()) {
json_file << save_json.dump(1, '\t') << std::endl;
json_file << save_json.dump(1, '\t') << std::endl; // Orca: tab-indented dump
json_file.close();
return 0;
}
@@ -266,9 +267,15 @@ std::string HMSQuery::hms_language_code()
// set language code to en by default
return "en";
std::string lang_code = wxGetApp().app_config->get_language_code();
// Orca: the HMS host ships no catalog for these locales, so fall back to english to avoid empty texts + retries
if (lang_code.compare("uk") == 0
|| lang_code.compare("cs") == 0
|| lang_code.compare("ru") == 0) {
|| lang_code.compare("ru") == 0
|| lang_code.compare("tr") == 0
|| lang_code.compare("pt") == 0
|| lang_code.compare("ko") == 0
)
{
BOOST_LOG_TRIVIAL(info) << "HMS: using english for lang_code = " << lang_code;
return "en";
}
@@ -577,6 +584,7 @@ wxImage HMSQuery::query_image_from_local(const wxString& image_name)
{
const fs::path& image_path = entry.path();
const fs::path& image_name = fs::relative(image_path, local_img_dir);
// Orca: from_path() for correct UTF-8 path encoding
m_hms_local_images[from_path(image_name)] = wxImage(from_path(image_path));
}
}

View File

@@ -18,10 +18,11 @@ static auto waiting_auth_str = _u8L("Logging in");
static auto login_failed_str = _u8L("Login failed");
BindJob::BindJob(std::string dev_id, std::string dev_ip, std::string sec_link, std::string ssdp_version)
BindJob::BindJob(std::string dev_id, std::string dev_ip, std::string dev_model, std::string sec_link, std::string ssdp_version)
:
m_dev_id(dev_id),
m_dev_ip(dev_ip),
m_dev_model(dev_model),
m_sec_link(sec_link),
m_ssdp_version(ssdp_version)
{
@@ -60,7 +61,7 @@ void BindJob::process(Ctl &ctl)
std::string timezone = get_timezone_utc_hm(offset);
m_agent->track_update_property("ssdp_version", m_ssdp_version, "string");
int result = m_agent->bind(m_dev_ip, m_dev_id, m_sec_link, timezone, m_improved,
int result = m_agent->bind(m_dev_ip, m_dev_id, m_dev_model, m_sec_link, timezone, m_improved,
[this, &ctl, &curr_percent, &msg, &result_code, &result_info](int stage, int code, std::string info) {
result_code = code;

View File

@@ -16,6 +16,7 @@ class BindJob : public Job
std::function<void()> m_success_fun{nullptr};
std::string m_dev_id;
std::string m_dev_ip;
std::string m_dev_model;
std::string m_sec_link;
std::string m_ssdp_version;
bool m_job_finished{ false };
@@ -23,7 +24,7 @@ class BindJob : public Job
bool m_improved{false};
public:
BindJob(std::string dev_id, std::string dev_ip, std::string sec_link, std::string ssdp_version);
BindJob(std::string dev_id, std::string dev_ip, std::string dev_model, std::string sec_link, std::string ssdp_version);
int status_range() const
{

View File

@@ -262,6 +262,7 @@ void PrintJob::process(Ctl &ctl)
params.task_vibration_cali = this->task_vibration_cali;
params.task_layer_inspect = this->task_layer_inspect;
params.task_record_timelapse= this->task_record_timelapse;
params.task_timelapse_use_internal = this->task_timelapse_use_internal;
params.nozzle_mapping = this->task_nozzle_mapping;
params.ams_mapping = this->task_ams_mapping;
params.ams_mapping2 = this->task_ams_mapping2;
@@ -274,6 +275,7 @@ void PrintJob::process(Ctl &ctl)
params.auto_bed_leveling = this->auto_bed_leveling;
params.auto_flow_cali = this->auto_flow_cali;
params.auto_offset_cali = this->auto_offset_cali;
params.extruder_cali_manual_mode = this->extruder_cali_manual_mode;
params.task_ext_change_assist = this->task_ext_change_assist;
// Allow disabling the eMMC print path via AppConfig. Plugin 02.03.00.62's
// eMMC tunnel code hangs indefinitely at the upload phase with some
@@ -489,7 +491,7 @@ void PrintJob::process(Ctl &ctl)
DeviceManager* dev = wxGetApp().getDeviceManager();
MachineObject* obj = dev->get_selected_machine();
auto wait_fn = [this, curr_percent, &obj](int state, std::string job_info) {
auto wait_fn = [this, &ctl, curr_percent, &obj](int state, std::string job_info) {
BOOST_LOG_TRIVIAL(info) << "print_job: get_job_info = " << job_info;
if (!obj->is_support_wait_sending_finish) {
@@ -521,6 +523,11 @@ void PrintJob::process(Ctl &ctl)
BOOST_LOG_TRIVIAL(info) << "print_job: printer has enter printing status, s = " << obj->print_status;
return true;
}
// Break the wait-for-print-start loop on user cancel.
if (ctl.was_canceled()) {
BOOST_LOG_TRIVIAL(info) << "print_job: user cancel the job " << obj->job_id_;
return true;
}
time_out++;
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
}
@@ -637,7 +644,11 @@ void PrintJob::process(Ctl &ctl)
if (result < 0) {
curr_percent = -1;
if (result == BAMBU_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || result == BAMBU_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST) {
// The printer is still fetching its encryption flag (a transient state), so ask the
// user to retry rather than showing a generic error.
if (result == BAMBU_NETOWRK_ERR_PRINT_SP_ENC_FLAG_NOT_READY) {
msg_text = _u8L("Retrieving printer information, please try again later.");
} else if (result == BAMBU_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || result == BAMBU_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST) {
msg_text = file_is_not_exists_str;
} else if (result == BAMBU_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE || result == BAMBU_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE) {
msg_text = file_over_size_str;

View File

@@ -80,6 +80,7 @@ public:
bool task_flow_cali;
bool task_vibration_cali;
bool task_record_timelapse;
bool task_timelapse_use_internal { false };
bool task_layer_inspect;
bool cloud_print_only { false };
bool has_sdcard { false };
@@ -92,11 +93,13 @@ public:
int auto_bed_leveling{0};
int auto_flow_cali{0};
int auto_offset_cali{0};
int extruder_cali_manual_mode{-1};
void set_print_config(std::string bed_type, bool bed_leveling, bool flow_cali, bool vabration_cali, bool record_timelapse, bool layer_inspect, bool ext_change_assist,
int auto_bed_levelingt,
int auto_flow_calit,
int auto_offset_calit)
int auto_offset_calit,
int extruder_cali_manual_modet = -1)
{
task_bed_type = bed_type;
task_bed_leveling = bed_leveling;
@@ -109,6 +112,7 @@ public:
auto_bed_leveling = auto_bed_levelingt;
auto_flow_cali = auto_flow_calit;
auto_offset_cali = auto_offset_calit;
extruder_cali_manual_mode = extruder_cali_manual_modet;
}

View File

@@ -117,6 +117,35 @@ void SendJob::process(Ctl &ctl)
ctl.call_on_main_thread([this] { prepare(); }).wait();
ctl.update_status(0, msg);
// In check mode (InputIpAddressDialog / lan-mode send) verify the connection with a dummy
// "verify_job" upload and fire the caller's success/fail callback; when not continuing, stop
// here. Cloud sends skip this.
if (m_is_check_mode) {
PrintParams verify_params;
verify_params.dev_ip = m_dev_ip;
verify_params.username = "bblp";
verify_params.password = m_access_code;
verify_params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
verify_params.use_ssl_for_mqtt = m_local_use_ssl;
verify_params.dev_id = m_dev_id;
verify_params.project_name = "verify_job";
verify_params.filename = job_data._temp_path.string();
verify_params.connection_type = this->connection_type;
int verify_result = agent->start_send_gcode_to_sdcard(verify_params, nullptr, nullptr, nullptr);
if (verify_result != 0) {
BOOST_LOG_TRIVIAL(error) << "send_job: access code / ip verification failed, result = " << verify_result;
if (m_enter_ip_address_fun_fail) m_enter_ip_address_fun_fail(verify_result);
m_job_finished = true;
return;
} else if (!m_check_and_continue) {
if (m_enter_ip_address_fun_success) m_enter_ip_address_fun_success();
m_job_finished = true;
return;
}
}
int total_plate_num = m_plater->get_partplate_list().get_plate_count();
PartPlate* plate = m_plater->get_partplate_list().get_plate(job_data.plate_idx);

View File

@@ -88,10 +88,7 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const w
m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &e) { TogglePlay(); });
m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { m_media_ctrl->Play(); });
// m_label_status->Bind(wxEVT_LEFT_UP, [this](auto &e) {
// auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/faq/live-view", L"en");
// wxLaunchDefaultBrowser(url);
// });
// Orca: live-view FAQ link binding removed (vendor URL)
Bind(wxEVT_RIGHT_UP, [this](auto & e) {
wxClipboard & c = *wxTheClipboard;

View File

@@ -541,6 +541,20 @@ void MonitorPanel::jump_to_LiveView()
m_status_info_panel->get_media_play_ctrl()->jump_to_play();
}
void MonitorPanel::jump_to_Rack()
{
if (!this->IsShown()) {
return;
}
auto page = m_tabpanel->GetCurrentPage();
if (page && page != m_status_info_panel) {
m_tabpanel->SetSelection(PT_STATUS);
}
m_status_info_panel->jump_to_Rack();
}
void MonitorPanel::update_network_version_footer()
{
std::string binary_version = Slic3r::NetworkAgent::get_version();

View File

@@ -158,6 +158,7 @@ public:
void jump_to_HMS();
void jump_to_Upgrade();
void jump_to_LiveView();
void jump_to_Rack();
void update_network_version_footer();
};

View File

@@ -219,9 +219,14 @@ void NetworkPluginDownloadDialog::setup_version_selector()
label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name);
} else {
label = wxString::FromUTF8(ver.display_name);
if (ver.is_latest) {
label += wxString(" ") + _L("(Latest)");
}
}
// Same labeling as the Preferences selector: "(Latest)" = highest listed
// version, "(installed)" = library already on disk; both can apply.
if (ver.is_latest) {
label += wxString(" ") + _L("(Latest)");
}
if (ver.is_installed) {
label += wxString(" ") + _L("(installed)");
}
m_version_combo->Append(label);
}

View File

@@ -11402,7 +11402,23 @@ void Plater::priv::update_plugin_when_launch(wxCommandEvent &event)
if (!app_config) return;
if (result == wxID_OK) {
app_config->set("update_network_plugin", "true");
// Apply the downloaded update right away and hot-reload the plug-in, the same
// way a manual version switch in Preferences behaves. When a file is still in
// use and cannot be replaced, fall back to installing on the next launch.
bool had_cache = false;
if (wxGetApp().install_network_plugin_from_ota(had_cache)) {
notification_manager->close_notification_of_type(NotificationType::BBLPluginUpdateAvailable);
app_config->set("update_network_plugin", "false");
if (wxGetApp().hot_reload_network_plugin()) {
MessageDialog dlg_ok(wxGetApp().mainframe, _L("Network plug-in switched successfully."), _L("Success"), wxOK | wxICON_INFORMATION);
dlg_ok.ShowModal();
} else {
MessageDialog dlg_fail(wxGetApp().mainframe, _L("Failed to load network plug-in. Please restart the application."), _L("Restart Required"), wxOK | wxICON_WARNING);
dlg_fail.ShowModal();
}
} else {
app_config->set("update_network_plugin", had_cache ? "true" : "false");
}
}
else if (result == wxID_NO) {
app_config->set("update_network_plugin", "false");

View File

@@ -65,6 +65,11 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status)
case PrintStatusHasUnreliableNozzleWarning: return "PrintStatusHasUnreliableNozzleWarning";
case PrintStatusWarningExtFilamentNotMatch: return "PrintStatusWarningExtFilamentNotMatch";
case PrintStatusFilamentWarningNozzleHRC: return "PrintStatusFilamentWarningNozzleHRC";
case PrintStatusTPUUnsupportCaliOn: return "PrintStatusTPUUnsupportCaliOn";
case PrintStatusTPUUnsuggestCali: return "PrintStatusTPUUnsuggestCali";
case PrintStatusSmartNozzleBlobNeedAuto: return "PrintStatusSmartNozzleBlobNeedAuto";
case PrintStatusFilamentWarningRemainNotEnough: return "PrintStatusFilamentWarningRemainNotEnough";
case PrintStatusPrintTimeEstimateWarning: return "PrintStatusPrintTimeEstimateWarning";
case PrintStatusReadingFinished: return "PrintStatusReadingFinished";
case PrintStatusSendingCanceled: return "PrintStatusSendingCanceled";
case PrintStatusAmsMappingSuccess: return "PrintStatusAmsMappingSuccess";
@@ -103,8 +108,16 @@ wxString PrePrintChecker::get_pre_state_msg(PrintDialogStatus status)
case PrintStatusTPUUnsupportAutoCali: return _L("TPU 90A/TPU 85A is too soft and does not support automatic Flow Dynamics calibration.");
case PrintStatusWarningKvalueNotUsed: return _L("Set dynamic flow calibration to 'OFF' to enable custom dynamic flow value.");
case PrintStatusNotSupportedPrintAll: return _L("This printer does not support printing all plates.");
case PrintStatusColorQuantityExceed: return _L("The current firmware supports a maximum of 16 materials. You can either reduce the number of materials to 16 or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support.");
// Orca: %s-parameterized so the real per-printer max color count can be filled in by the caller
// (SelectMachine formats it via get_pre_state_msg before add()); default templates keep the wording.
case PrintStatusColorQuantityExceed: return _L("The current firmware supports a maximum of %s materials. You can either reduce the number of materials to %s or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support.");
case PrintStatusWarningExtFilamentNotMatch: return _L("The type of external filament is unknown or does not match with the filament type in the slicing file. Please make sure you have installed the correct filament in the external spool.");
// Same text as the blocking "auto" case (PrintStatusTPUUnsupportAutoCali); CaliOn is a
// non-blocking advisory (Send stays enabled).
case PrintStatusTPUUnsupportCaliOn: return _L("TPU 90A/TPU 85A are too soft. It is recommended to perform manual flow calibration on the 'Calibration' page. If 'Dynamic Flow Calibration' is set to auto/on, the system will use the previous calibration value and skip the flow calibration process.");
case PrintStatusFilamentWarningRemainNotEnough: return _L("The filament in the AMS may be insufficient for this print. Please refill or replace it.");
// SmartNozzleBlobNeedAuto text is passed via add_with_link (clickable "Switch"), not here.
// PrintTimeEstimateWarning has no call site and no message string.
}
return wxEmptyString;
}
@@ -162,6 +175,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)
//{
@@ -230,7 +286,15 @@ bool PrinterMsgPanel::UpdateInfos(const std::vector<prePrintInfo>& 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);
}

View File

@@ -1,6 +1,7 @@
#ifndef slic3r_GUI_PRE_PRINT_CHECK_hpp_
#define slic3r_GUI_PRE_PRINT_CHECK_hpp_
#include <functional>
#include <wx/wx.h>
#include "Widgets/Label.hpp"
namespace Slic3r { namespace GUI {
@@ -23,13 +24,17 @@ struct prePrintInfo
wxString msg;
wxString tips;
wxString wiki_url;
int index;
wxString link_label; // optional: clickable text appended after msg
std::function<void()> link_callback; // optional: internal action for link_label click
int index{0};
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
}
};
@@ -48,6 +53,7 @@ enum PrintDialogStatus : unsigned int {
PrintStatusConnecting,
PrintStatusReconnecting,
PrintStatusInUpgrading,
PrintStatusFirmwareNotSupportTpuAtLeft,
PrintStatusModeNotFDM,
PrintStatusInSystemPrinting,
PrintStatusInPrinting,
@@ -93,6 +99,7 @@ enum PrintDialogStatus : unsigned int {
PrintStatusPrinterWarningBegin,
PrintStatusTimelapseNoSdcard,
PrintStatusTimelapseWarning,
PrintStatusTimelapseStorageLow,
PrintStatusMixAmsAndVtSlotWarning,
PrintStatusToolHeadCoolingFanWarning,
PrintStatusRackNozzleMappingWarning,
@@ -111,6 +118,15 @@ enum PrintDialogStatus : unsigned int {
PrintStatusFilamentWarningUnknownHighChamberTempSoft,
PrintStatusWarningExtFilamentNotMatch,
PrintStatusFilamentWarningNozzleHRC,
PrintStatusFilamentCrossExtruderWarning,
// Non-blocking advisories. FilamentWarningRemainNotEnough is declared but not wired (missing
// device-model surface); PrintTimeEstimateWarning has no call site or message and is kept only
// to match the reference enum table.
PrintStatusTPUUnsupportCaliOn,
PrintStatusTPUUnsuggestCali,
PrintStatusSmartNozzleBlobNeedAuto,
PrintStatusFilamentWarningRemainNotEnough,
PrintStatusPrintTimeEstimateWarning,
PrintStatusFilamentWarningEnd,
PrintStatusWarningEnd,//->end error<-
@@ -141,6 +157,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<void()> link_callback);
static ::std::string get_print_status_info(PrintDialogStatus status);
wxString get_pre_state_msg(PrintDialogStatus status);

View File

@@ -1270,9 +1270,14 @@ wxBoxSizer *PreferencesDialog::create_item_network_plugin_version(wxString title
label = wxString::FromUTF8(ver.display_name);
}
// "(Latest)" marks the highest listed version; "(installed)" marks versions
// whose library is already on disk. One entry can carry both.
if (ver.is_latest) {
label += " " + _L("(Latest)");
}
if (ver.is_installed) {
label += " " + _L("(installed)");
}
m_network_version_combo->Append(label);
if (current_version == ver.version) {
current_selection = i;

View File

@@ -1,4 +1,5 @@
#include "PrintOptionsDialog.hpp"
#include <initializer_list>
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "libslic3r/Utils.hpp"
@@ -8,6 +9,8 @@
#include "DeviceCore/DevConfig.h"
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevFan.h"
#include "DeviceCore/DevPrintOptions.h"
static const wxColour STATIC_BOX_LINE_COL = wxColour(238, 238, 238);
static const wxColour STATIC_TEXT_CAPTION_COL = wxColour(100, 100, 100);
@@ -183,6 +186,92 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
evt.Skip();
});
// Firmware print-option toggles (each gated on a fun2 capability bit, published via DevPrintOptions).
m_cb_plate_align->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& evt) {
if (obj) { obj->GetPrintOptions()->command_xcam_control_build_plate_align_detector(m_cb_plate_align->GetValue()); }
evt.Skip();
});
m_cb_fod_check->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& evt) {
if (obj) { obj->GetPrintOptions()->command_xcam_control_fod_check(m_cb_fod_check->GetValue()); }
evt.Skip();
});
m_cb_displacement_detection->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& evt) {
if (obj) { obj->GetPrintOptions()->command_xcam_control_displacement_detection(m_cb_displacement_detection->GetValue()); }
evt.Skip();
});
m_cb_purify_air_at_print_end->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& evt) {
if (obj) { obj->GetPrintOptions()->command_xcam_control_purify_air_at_print_end(m_cb_purify_air_at_print_end->GetValue()); }
evt.Skip();
});
purify_air_switch_board->Bind(wxCUSTOMEVT_SWITCH_POS, [this](wxCommandEvent& evt) {
if (evt.GetInt() == 0) {
if (obj) { obj->GetPrintOptions()->command_xcam_control_purify_air_at_print_end((int) DevPrintOptions::PurifyAirAtPrintEndState::PurifyAirByOutside); }
} else if (evt.GetInt() == 1) {
if (obj) { obj->GetPrintOptions()->command_xcam_control_purify_air_at_print_end((int) DevPrintOptions::PurifyAirAtPrintEndState::PurifyAirByInside); }
}
evt.Skip();
});
m_smart_nozzle_blob_mode_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](wxCommandEvent& evt) {
if (!obj || !obj->GetPrintOptions()) { evt.Skip(); return; }
int sel = m_smart_nozzle_blob_mode_switch->GetSelection();
// UI: 0=Auto, 1=On, 2=Off -> protocol: 0=off, 1=on, 2=auto
int mode_map[] = {2, 1, 0};
if (sel < 0 || sel > 2) { evt.Skip(); return; }
// If currently in Auto (current_detect_value == 2), a print is running, and a loaded
// filament is stringing-prone, confirm before enabling — turning blob detection on
// mid-print with such filament can degrade quality.
const auto* blob_opt = obj->GetPrintOptions()->GetDetectionOption(PrintOptionEnum::Smart_Nozzle_Blob_Detection);
const bool was_auto = blob_opt && blob_opt->current_detect_value == 2;
if (sel == 1 /*On*/ && was_auto && obj->is_in_printing()
&& obj->any_loaded_filament_is_stringing_prone()) {
wxString message = _L("There is stringing-prone filament in the current print job. "
"Enabling nozzle clumping detection now may degrade print quality. "
"Are you sure you want to enable it?");
wxString caption = _L("Enable Nozzle Clumping Detection");
// Themed warning dialog with Cancel as the highlighted default, so the safe choice (leave
// detection off) is the default and Confirm must be actively picked.
MessageDialog dialog(this, message, caption, wxICON_WARNING);
dialog.AddButton(wxID_CANCEL, _L("Cancel"), true);
dialog.AddButton(wxID_OK, _L("Confirm"), false);
if (dialog.ShowModal() != wxID_OK) {
// User cancelled: roll the switch back to Auto without sending the command.
m_smart_nozzle_blob_mode_switch->SetSelection(0);
update_smart_nozzle_blob_mode_desc(0);
evt.Skip();
return;
}
}
obj->GetPrintOptions()->command_smart_nozzle_blob_detect_mode(mode_map[sel]);
update_smart_nozzle_blob_mode_desc(sel);
evt.Skip();
});
m_cb_snapshot_enable->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& evt) {
if (!obj || !obj->GetPrintOptions()) { evt.Skip(); return; }
if (m_cb_snapshot_enable->GetValue()) {
wxString message = _L("When enabled, the printer will automatically capture photos of printed parts and upload them to the cloud. Would you like to enable this option?");
wxString caption = _L("Confirm Enable Print Status Snapshot");
wxMessageDialog dialog(this, message, caption, wxYES_NO | wxICON_QUESTION);
dialog.SetYesNoLabels(_L("Confirm"), _L("Cancel"));
if (dialog.ShowModal() == wxID_YES) {
obj->GetPrintOptions()->command_snapshot_control(true);
m_cb_snapshot_enable->SetValue(true);
} else {
m_cb_snapshot_enable->SetValue(false);
}
} else {
obj->GetPrintOptions()->command_snapshot_control(false);
}
evt.Skip();
});
wxGetApp().UpdateDlgDarkUI(this);
}
@@ -437,11 +526,44 @@ void PrintOptionsDialog::update_options(MachineObject* obj_)
line7->Hide();
}
// Orca: firmware print-option toggles, each shown only when its fun2 capability bit is
// reported. A printer reporting none of these bits renders the dialog as before.
auto* print_opts = obj_->GetPrintOptions();
auto show_row = [](bool support, std::initializer_list<wxWindow*> widgets, wxSizerItem* bottom_space) {
for (auto* w : widgets) { if (w) w->Show(support); }
if (bottom_space) bottom_space->Show(support);
};
auto* plate_align_opt = print_opts ? print_opts->GetDetectionOption(PrintOptionEnum::Buildplate_Align_Detection) : nullptr;
show_row(plate_align_opt && plate_align_opt->is_support_detect, {m_cb_plate_align, text_plate_align, text_plate_align_caption}, plate_align_bottom_space);
auto* fod_opt = print_opts ? print_opts->GetDetectionOption(PrintOptionEnum::FOD_Check_Detection) : nullptr;
show_row(fod_opt && fod_opt->is_support_detect, {m_cb_fod_check, text_fod_check, text_fod_check_caption}, fod_check_bottom_space);
auto* displacement_opt = print_opts ? print_opts->GetDetectionOption(PrintOptionEnum::Displacement_Detection) : nullptr;
show_row(displacement_opt && displacement_opt->is_support_detect, {m_cb_displacement_detection, text_displacement_detection, text_displacement_detection_caption}, displacement_bottom_space);
auto* smart_blob_opt = print_opts ? print_opts->GetDetectionOption(PrintOptionEnum::Smart_Nozzle_Blob_Detection) : nullptr;
show_row(smart_blob_opt && smart_blob_opt->is_support_detect, {m_smart_nozzle_blob_mode_switch, text_smart_nozzle_blob, text_smart_nozzle_blob_mode_desc}, smart_nozzle_blob_bottom_space);
UpdateOptionSavePrintFileToStorage(obj_);
UpdateOptionOpenDoorCheck(obj_);
UpdateOptionSnapshot(obj_);
this->Freeze();
if (plate_align_opt) m_cb_plate_align->SetValue(plate_align_opt->current_detect_value == 1);
if (fod_opt) m_cb_fod_check->SetValue(fod_opt->current_detect_value == 1);
if (displacement_opt) m_cb_displacement_detection->SetValue(displacement_opt->current_detect_value == 1);
if (smart_blob_opt && smart_blob_opt->is_support_detect) {
// Protocol: 0=off, 1=on, 2=auto -> UI: 0=Auto, 1=On, 2=Off
int mode = smart_blob_opt->current_detect_value;
int ui_map[] = {2, 1, 0};
int ui_sel = (mode >= 0 && mode <= 2) ? ui_map[mode] : 0;
m_smart_nozzle_blob_mode_switch->SetSelection(ui_sel);
update_smart_nozzle_blob_mode_desc(ui_sel);
}
m_cb_first_layer->SetValue(obj_->xcam_first_layer_inspector);
m_cb_plate_mark->SetValue(obj_->xcam_buildplate_marker_detector);
m_cb_auto_recovery->SetValue(obj_->xcam_auto_recovery_step_loss);
@@ -497,6 +619,7 @@ void PrintOptionsDialog::update_options(MachineObject* obj_)
update_purgechutepileup_detection_status();
update_nozzleclumping_detection_status();
update_airprinting_detection_status();
update_purify_air_at_print_end(obj_);
this->Thaw();
@@ -562,6 +685,109 @@ void PrintOptionsDialog::UpdateOptionOpenDoorCheck(MachineObject *obj)
open_door_switch_board->Show();
}
void PrintOptionsDialog::update_purify_air_at_print_end(MachineObject *obj_)
{
if (!obj_ || !obj_->GetPrintOptions()) return;
auto* opt = obj_->GetPrintOptions()->GetDetectionOption(PrintOptionEnum::Purify_Air_At_Print_End);
if (!opt || !opt->is_support_detect) {
purify_air_switch_board->Disable();
m_cb_purify_air_at_print_end->Hide();
text_purify_air->Hide();
text_purify_air_context->Hide();
purify_air_switch_board->Hide();
if (purify_air_bottom_space) purify_air_bottom_space->Show(false);
return;
}
if (purify_air_bottom_space) purify_air_bottom_space->Show(true);
m_cb_purify_air_at_print_end->Show();
text_purify_air->Show();
m_cb_purify_air_at_print_end->Enable();
purify_air_switch_board->Enable();
text_purify_air_context->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
text_purify_air->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
// Orca: this codebase's AirDuctData has no IsExaustFanExit() helper, so the exhaust/chamber
// fan is detected inline by scanning the air-duct parts for the chamber-fan id.
bool has_exhaust_fan = false;
for (const auto& part : obj_->GetFan()->GetAirDuctData().parts) {
if (part.id == int(AIR_FUN::FAN_CHAMBER_0_IDX)) { has_exhaust_fan = true; break; }
}
if (has_exhaust_fan) {
text_purify_air_context->SetLabel(_L("Purifies the chamber air as the print finishes, based on the selected mode."));
purify_air_switch_board->Show();
if (opt->current_detect_value != 0) {
m_cb_purify_air_at_print_end->SetValue(true);
purify_air_switch_board->updateState(opt->current_detect_value == 1 ? "left" : "right");
purify_air_switch_board->Refresh();
} else {
purify_air_switch_board->Disable();
m_cb_purify_air_at_print_end->SetValue(false);
}
} else {
text_purify_air_context->SetLabel(_L("Purifies the chamber air through internal circulation as each print finishes."));
purify_air_switch_board->Hide();
m_cb_purify_air_at_print_end->SetValue(opt->current_detect_value != 0);
}
text_purify_air_context->Show();
text_purify_air_context->Wrap(FromDIP(400));
// Orca: disabled while a print is running (matches the reference); the reference's
// "unavailable during the task" toast is not ported.
if (obj_->is_in_printing()) {
m_cb_purify_air_at_print_end->Disable();
purify_air_switch_board->Disable();
text_purify_air_context->SetForegroundColour(wxColour(170, 170, 170));
text_purify_air->SetForegroundColour(wxColour(170, 170, 170));
}
}
void PrintOptionsDialog::update_smart_nozzle_blob_mode_desc(int selection)
{
wxString desc;
switch (selection) {
case 0: // Auto
desc = _L("Automatically match the corresponding switch strategy for leak-prone filaments (disable blob detection) and regular filaments (enable blob detection).");
break;
case 1: // On
desc = _L("Detect whether the nozzle is wrapped by filament or other foreign matter.");
break;
case 2: // Off
desc = _L("After disabling, nozzle wrapping cannot be detected, which may lead to print failure or nozzle damage.");
break;
default:
desc = _L("Detect whether the nozzle is wrapped by filament or other foreign matter.");
break;
}
text_smart_nozzle_blob_mode_desc->SetLabel(desc);
text_smart_nozzle_blob_mode_desc->Wrap(FromDIP(400));
}
void PrintOptionsDialog::UpdateOptionSnapshot(MachineObject *obj)
{
if (!IsShown()) { return; }
auto* opt = (obj && obj->GetPrintOptions()) ? obj->GetPrintOptions()->GetDetectionOption(PrintOptionEnum::Snapshot_Detection) : nullptr;
if (!opt || !opt->is_support_detect) {
m_cb_snapshot_enable->Show(false);
m_snapshot_sizer->Show(false);
Layout();
return;
}
if (!m_cb_snapshot_enable->IsShown()) {
m_cb_snapshot_enable->Show(true);
m_snapshot_sizer->Show(true);
Layout();
}
if (time(nullptr) - opt->detect_hold_start > HOLD_TIME_6SEC) {
m_cb_snapshot_enable->SetValue(opt->current_detect_value == 2);
}
}
wxBoxSizer* PrintOptionsDialog::create_settings_group(wxWindow* parent)
{
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
@@ -875,7 +1101,7 @@ wxBoxSizer* PrintOptionsDialog::create_settings_group(wxWindow* parent)
line_sizer->Add(FromDIP(5), 0, 0, 0);
line_sizer->Add(m_cb_save_remote_print_file_to_storage, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
line_sizer->Add(text_save_remote_print_file_to_storage, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
text_save_remote_print_file_to_storage_explain = new Label(parent, _L("Save the printing files initiated from Bambu Studio, Bambu Handy and MakerWorld on External Storage"));
text_save_remote_print_file_to_storage_explain = new Label(parent, _L("Save the printing files sent from the slicer and other apps on External Storage")); // Orca: neutral wording (vendor app names removed)
text_save_remote_print_file_to_storage_explain->SetForegroundColour(STATIC_TEXT_EXPLAIN_COL);
text_save_remote_print_file_to_storage_explain->SetFont(Label::Body_12);
text_save_remote_print_file_to_storage_explain->Wrap(FromDIP(400));
@@ -951,6 +1177,125 @@ wxBoxSizer* PrintOptionsDialog::create_settings_group(wxWindow* parent)
text_nozzle_blob_caption->Hide();
line7->Hide();
// ---- Firmware print-option toggles (hidden until their fun2 capability bit is reported) ----
// Purify Air at Print End (tri-state: internal circulation / exhaust)
line_sizer = new wxBoxSizer(wxHORIZONTAL);
m_cb_purify_air_at_print_end = new CheckBox(parent);
text_purify_air = new Label(parent, _L("Purify Air at Print End"));
text_purify_air->SetFont(Label::Body_14);
text_purify_air_context = new Label(parent, wxEmptyString);
text_purify_air_context->SetFont(Label::Body_12);
text_purify_air_context->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
purify_air_switch_board = new SwitchBoard(parent, _L("Internal Circulation"), _L("Exhaust"), wxSize(FromDIP(300), FromDIP(26)));
purify_air_switch_board->Disable();
line_sizer->Add(FromDIP(5), 0, 0, 0);
line_sizer->Add(m_cb_purify_air_at_print_end, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
line_sizer->Add(text_purify_air, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
sizer->Add(text_purify_air_context, 0, wxLEFT, FromDIP(58));
sizer->Add(purify_air_switch_board, 0, wxLEFT, FromDIP(58));
purify_air_bottom_space = sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
m_cb_purify_air_at_print_end->Hide();
text_purify_air->Hide();
text_purify_air_context->Hide();
purify_air_switch_board->Hide();
purify_air_bottom_space->Show(false);
// Build plate alignment detection
line_sizer = new wxBoxSizer(wxHORIZONTAL);
m_cb_plate_align = new CheckBox(parent);
text_plate_align = new Label(parent, _L("Alignment Detection"));
text_plate_align->SetFont(Label::Body_14);
line_sizer->Add(FromDIP(5), 0, 0, 0);
line_sizer->Add(m_cb_plate_align, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
line_sizer->Add(text_plate_align, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
line_sizer = new wxBoxSizer(wxHORIZONTAL);
text_plate_align_caption = new Label(parent, _L("Pauses printing when build plate misalignment is detected."));
text_plate_align_caption->Wrap(FromDIP(400));
text_plate_align_caption->SetFont(Label::Body_12);
text_plate_align_caption->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
line_sizer->Add(FromDIP(38), 0, 0, 0);
line_sizer->Add(text_plate_align_caption, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
plate_align_bottom_space = sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
m_cb_plate_align->Hide();
text_plate_align->Hide();
text_plate_align_caption->Hide();
plate_align_bottom_space->Show(false);
// Foreign Object Detection
line_sizer = new wxBoxSizer(wxHORIZONTAL);
m_cb_fod_check = new CheckBox(parent);
text_fod_check = new Label(parent, _L("Foreign Object Detection"));
text_fod_check->SetFont(Label::Body_14);
line_sizer->Add(FromDIP(5), 0, 0, 0);
line_sizer->Add(m_cb_fod_check, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
line_sizer->Add(text_fod_check, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
line_sizer = new wxBoxSizer(wxHORIZONTAL);
text_fod_check_caption = new Label(parent, _L("Checks for any objects on the build plate at the start of a print to avoid collisions."));
text_fod_check_caption->Wrap(FromDIP(400));
text_fod_check_caption->SetFont(Label::Body_12);
text_fod_check_caption->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
line_sizer->Add(FromDIP(38), 0, 0, 0);
line_sizer->Add(text_fod_check_caption, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
fod_check_bottom_space = sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
m_cb_fod_check->Hide();
text_fod_check->Hide();
text_fod_check_caption->Hide();
fod_check_bottom_space->Show(false);
// Printed Part Displacement Detection
line_sizer = new wxBoxSizer(wxHORIZONTAL);
m_cb_displacement_detection = new CheckBox(parent);
text_displacement_detection = new Label(parent, _L("Printed Part Displacement Detection"));
text_displacement_detection->SetFont(Label::Body_14);
line_sizer->Add(FromDIP(5), 0, 0, 0);
line_sizer->Add(m_cb_displacement_detection, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
line_sizer->Add(text_displacement_detection, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
line_sizer = new wxBoxSizer(wxHORIZONTAL);
text_displacement_detection_caption = new Label(parent, _L("Monitors the printed part during printing and alerts immediately if it shifts or collapses."));
text_displacement_detection_caption->Wrap(FromDIP(400));
text_displacement_detection_caption->SetFont(Label::Body_12);
text_displacement_detection_caption->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
line_sizer->Add(FromDIP(38), 0, 0, 0);
line_sizer->Add(text_displacement_detection_caption, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
displacement_bottom_space = sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
m_cb_displacement_detection->Hide();
text_displacement_detection->Hide();
text_displacement_detection_caption->Hide();
displacement_bottom_space->Show(false);
// Smart nozzle clumping detection (tri-mode: Auto / On / Off)
line_sizer = new wxBoxSizer(wxHORIZONTAL);
text_smart_nozzle_blob = new Label(parent, _L("Nozzle Clumping Detection"));
text_smart_nozzle_blob->SetFont(Label::Body_14);
line_sizer->Add(FromDIP(5), 0, 0, 0);
line_sizer->Add(text_smart_nozzle_blob, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
line_sizer = new wxBoxSizer(wxHORIZONTAL);
text_smart_nozzle_blob_mode_desc = new Label(parent, _L("Checks if the nozzle is clumping by filament or other foreign objects."));
text_smart_nozzle_blob_mode_desc->SetFont(Label::Body_12);
text_smart_nozzle_blob_mode_desc->Wrap(FromDIP(400));
text_smart_nozzle_blob_mode_desc->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
line_sizer->Add(FromDIP(5), 0, 0, 0);
line_sizer->Add(text_smart_nozzle_blob_mode_desc, 1, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(5));
sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
m_smart_nozzle_blob_mode_switch = new MultiSwitchButton(parent);
m_smart_nozzle_blob_mode_switch->SetOptions({_L("Auto"), _L("On"), _L("Off")});
m_smart_nozzle_blob_mode_switch->SetSelection(0);
sizer->Add(m_smart_nozzle_blob_mode_switch, 0, wxLEFT, FromDIP(30));
smart_nozzle_blob_bottom_space = sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
text_smart_nozzle_blob->Hide();
m_smart_nozzle_blob_mode_switch->Hide();
text_smart_nozzle_blob_mode_desc->Hide();
smart_nozzle_blob_bottom_space->Show(false);
//Open Door Detection
line_sizer = new wxBoxSizer(wxHORIZONTAL);
m_cb_open_door = new CheckBox(parent);
@@ -966,6 +1311,28 @@ wxBoxSizer* PrintOptionsDialog::create_settings_group(wxWindow* parent)
sizer->Add(open_door_switch_board, 0, wxLEFT, FromDIP(58));
sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
// Print status snapshot (enable path shows a confirm prompt; hidden until supported)
m_snapshot_sizer = new wxBoxSizer(wxVERTICAL);
line_sizer = new wxBoxSizer(wxHORIZONTAL);
m_cb_snapshot_enable = new CheckBox(parent);
Label* text_snapshot = new Label(parent, _L("Print Status Snapshot"));
text_snapshot->SetFont(Label::Body_14);
line_sizer->Add(FromDIP(5), 0, 0, 0);
line_sizer->Add(m_cb_snapshot_enable, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
line_sizer->Add(text_snapshot, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
m_snapshot_sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
line_sizer = new wxBoxSizer(wxHORIZONTAL);
Label* text_snapshot_caption = new Label(parent, _L("Automatically capture and upload print photos, showing defects during printing and the final result for remote viewing."));
text_snapshot_caption->Wrap(FromDIP(400));
text_snapshot_caption->SetFont(Label::Body_12);
text_snapshot_caption->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
line_sizer->Add(FromDIP(38), 0, 0, 0);
line_sizer->Add(text_snapshot_caption, 1, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0));
m_snapshot_sizer->Add(line_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(18));
sizer->Add(m_snapshot_sizer, 0, wxEXPAND | wxRIGHT, FromDIP(18));
m_cb_snapshot_enable->Hide();
m_snapshot_sizer->Show(false);
ai_monitoring_level_list->Connect(wxEVT_COMBOBOX, wxCommandEventHandler(PrintOptionsDialog::set_ai_monitor_sensitivity), NULL, this);
// refine printer function options

Some files were not shown because too many files have changed in this diff Show More