Files
OrcaSlicer/src/slic3r/GUI/AMSDryControl.cpp
Noisyfox 62e5cfed36 feat: add AMSDryControl dialog for AMS filament drying
Three-page wizard: main status/control page, guide page with
filament tray status, and progress page. Supports N3F and N3S AMS types.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:16:04 +08:00

1865 lines
70 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "AMSDryControl.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/Widgets/AnimaController.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/ComboBox.hpp"
#include "slic3r/GUI/Widgets/ProgressBar.hpp"
#include "slic3r/GUI/DeviceCore/DevUtilBackend.h"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Preset.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace GUI {
const int AMS_DRY_STATUS_IMAGE_SIZE = 96;
static std::string get_humidity_level_img_path(int humidity_percent)
{
const int num_levels = 5;
int hum_level;
if (humidity_percent <= 100 / num_levels) {
hum_level = 5;
} else if (humidity_percent <= 100 / num_levels * 2) {
hum_level = 4;
} else if (humidity_percent <= 100 / num_levels * 3) {
hum_level = 3;
} else if (humidity_percent <= 100 / num_levels * 4) {
hum_level = 2;
} else {
hum_level = 1;
}
if (wxGetApp().dark_mode()) {
return "hum_level" + std::to_string(hum_level) + "_no_num_light";
} else {
return "hum_level" + std::to_string(hum_level) + "_no_num_light";
}
}
static std::string get_dry_status_img_path(DevAmsType type, DevAms::DryStatus status, DevAms::DrySubStatus sub_status)
{
std::string img_name = "dev_ams_dry_ctr_";
switch (type) {
case DevAmsType::N3S:
img_name += "n3s";
break;
case DevAmsType::N3F:
img_name += "n3f";
break;
default:
return "";
}
img_name += "_";
switch (status) {
case DevAms::DryStatus::Error:
img_name += "error";
return img_name;
}
switch (sub_status) {
case DevAms::DrySubStatus::Heating:
img_name += "heating";
return img_name;
case DevAms::DrySubStatus::Dehumidify:
img_name += "dehumidifying";
return img_name;
}
return "";
}
FilamentItemPanel::FilamentItemPanel(wxWindow* parent, const wxString& text, const std::string& icon_name, wxWindowID id)
: wxPanel(parent, id)
, m_icon_name(icon_name)
{
SetBackgroundColour(wxColour("#F7F7F7")); // Light gray background
SetMinSize(wxSize(FromDIP(64), FromDIP(106))); // Width: 64, Height: 106
SetSize(wxSize(FromDIP(64), FromDIP(106))); // Fixed size
// Create sizer for vertical layout
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
// Top section with text - moved to be closer to center
wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL);
top_sizer->AddStretchSpacer(5);
m_text_label = new Label(this, text);
m_text_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour(*wxBLACK)));
m_text_label->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F7F7F7")));
m_text_label->SetFont(Label::Body_12);
m_text_label->Wrap(FromDIP(40));
top_sizer->Add(m_text_label, 0, wxALIGN_CENTER_HORIZONTAL);
top_sizer->AddStretchSpacer(1); // Increased bottom spacer to push text down
sizer->Add(top_sizer, 1, wxEXPAND);
// Bottom section with icon - vertically centered in bottom half
wxBoxSizer* bottom_sizer = new wxBoxSizer(wxVERTICAL);
bottom_sizer->AddStretchSpacer(1);
m_icon_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
m_icon_bitmap->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F7F7F7")));
m_icon_bitmap->SetMinSize(wxSize(FromDIP(24), FromDIP(24)));
m_icon_bitmap->SetMaxSize(wxSize(FromDIP(24), FromDIP(24)));
bottom_sizer->Add(m_icon_bitmap, 0, wxALIGN_CENTER_HORIZONTAL);
bottom_sizer->AddStretchSpacer(1);
sizer->Add(bottom_sizer, 1, wxEXPAND);
SetSizer(sizer);
// Bind events
Bind(wxEVT_PAINT, &FilamentItemPanel::OnPaint, this);
Bind(wxEVT_SIZE, &FilamentItemPanel::OnSize, this);
SetIcon(icon_name);
wxGetApp().UpdateDarkUI(this);
}
void FilamentItemPanel::SetText(const wxString& text)
{
m_text_label->SetLabel(text);
Layout();
}
void FilamentItemPanel::SetIcon(const std::string& icon_name)
{
m_icon_name = icon_name;
if (!icon_name.empty()) {
try {
m_icon = ScalableBitmap(this, icon_name, 20);
m_icon.msw_rescale();
m_icon_bitmap->SetBitmap(m_icon.bmp());
} catch (Exception&) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": failed to load filament icon";
m_icon_bitmap->SetBitmap(wxNullBitmap);
}
} else {
m_icon_bitmap->SetBitmap(wxNullBitmap);
}
m_icon_bitmap->Refresh();
}
void FilamentItemPanel::msw_rescale()
{
if (m_icon_bitmap && !m_icon_name.empty()) {
m_icon.msw_rescale();
m_icon_bitmap->SetBitmap(m_icon.bmp());
m_icon_bitmap->Refresh();
}
Layout();
}
void FilamentItemPanel::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
wxSize size = GetSize();
// bool is_dark_mode = wxGetApp().dark_mode();
wxColour backgroundColor = StateColor::darkModeColorFor(wxColour("#F7F7F7"));
wxColour borderColor = StateColor::darkModeColorFor(wxColour("#DBDBDB"));
// Draw white background rectangle with rounded corners inside the thick vertical lines
dc.SetBrush(wxBrush(backgroundColor));
dc.SetPen(wxPen(backgroundColor));
dc.DrawRectangle(FromDIP(6), FromDIP(12), size.GetWidth() - FromDIP(12), size.GetHeight() - 2 * FromDIP(12));
// Draw much thicker vertical rounded rectangles on left and right (3 times thicker)
dc.SetBrush(wxBrush(wxColour(borderColor)));
dc.SetPen(wxPen(wxColour(borderColor)));
dc.DrawRoundedRectangle(FromDIP(0), FromDIP(0), FromDIP(6), size.GetHeight(), FromDIP(6)); // Left rounded rectangle
dc.DrawRoundedRectangle(size.GetWidth() - FromDIP(6), FromDIP(0), FromDIP(6), size.GetHeight(), FromDIP(6)); // Right rounded rectangle
// Draw thin horizontal rounded rectangles on top and bottom (moved closer to center by half the distance)
dc.SetBrush(wxBrush(wxColour(borderColor)));
dc.SetPen(wxPen(wxColour(borderColor)));
dc.DrawRoundedRectangle(FromDIP(6), FromDIP(12), size.GetWidth() - FromDIP(12), FromDIP(3), FromDIP(1)); // Top rounded rectangle
dc.DrawRoundedRectangle(FromDIP(6), size.GetHeight() - FromDIP(13), size.GetWidth() - FromDIP(12), FromDIP(3), FromDIP(1)); // Bottom rounded rectangle
}
void FilamentItemPanel::OnSize(wxSizeEvent& event)
{
Refresh();
event.Skip();
}
// class AMSFilamentPanel
AMSFilamentPanel::AMSFilamentPanel(wxWindow* parent, const wxString& ams_name, wxWindowID id)
: wxPanel(parent, id)
{
SetBackgroundColour(wxColour("#DBDBDB"));
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
// Filament items section
m_filament_container = new wxPanel(this);
m_filament_container->SetBackgroundColour(wxColour("#F7F7F7"));
m_filament_sizer = new wxBoxSizer(wxHORIZONTAL);
m_filament_container->SetSizer(m_filament_sizer);
// AMS name section
m_ams_name_label = new Label(this, ams_name);
m_ams_name_label->SetForegroundColour(wxColour("#858585"));
m_ams_name_label->SetFont(Label::Body_14);
m_ams_name_label->SetBackgroundColour(wxColour("#DBDBDB"));
main_sizer->Add(m_filament_container, 1, wxEXPAND | wxALL, 0);
main_sizer->Add(m_ams_name_label, 0, wxALIGN_LEFT | wxALL, FromDIP(5));
SetSizer(main_sizer);
wxGetApp().UpdateDarkUI(this);
}
void AMSFilamentPanel::AddFilamentItem(FilamentItemPanel* panel)
{
if (panel) {
panel->Reparent(m_filament_container);
m_filament_sizer->Add(panel, 0, wxALL, FromDIP(5));
m_filament_items.push_back(panel);
Layout();
}
}
void AMSFilamentPanel::AddFilamentItem(const wxString& text, const std::string& icon_name)
{
FilamentItemPanel* item = new FilamentItemPanel(m_filament_container, text, icon_name);
m_filament_items.push_back(item);
m_filament_sizer->Add(item, 0, wxALL, FromDIP(5));
Layout();
}
void AMSFilamentPanel::SetAmsName(const wxString& ams_name)
{
m_ams_name_label->SetLabel(ams_name);
Layout();
}
void AMSFilamentPanel::Clear()
{
m_filament_sizer->Clear(true);
m_filament_items.clear();
}
void AMSFilamentPanel::msw_rescale()
{
for (auto item : m_filament_items) {
item->msw_rescale();
}
Layout();
}
AMSDryCtrWin::AMSDryCtrWin(wxWindow *parent)
:DPIDialog(parent, wxID_ANY, _L("AMS Dryness Control"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
create();
}
AMSDryCtrWin::~AMSDryCtrWin()
{
if (m_progress_timer) {
delete m_progress_timer;
}
}
wxScrolledWindow* AMSDryCtrWin::create_preview_scrolled_window(wxWindow* parent)
{
wxScrolledWindow* panel = new wxScrolledWindow(parent, wxID_ANY);
panel->SetScrollRate(10, 0);
panel->SetSize(AMS_ITEMS_PANEL_SIZE);
panel->SetMinSize(AMS_ITEMS_PANEL_SIZE);
panel->SetBackgroundColour(AMS_CONTROL_DEF_BLOCK_BK_COLOUR);
return panel;
}
wxBoxSizer* AMSDryCtrWin::create_humidity_status_section(wxPanel* parent)
{
wxBoxSizer* image_sizer = new wxBoxSizer(wxVERTICAL);
m_humidity_img = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
image_sizer->Add(m_humidity_img, 1, wxALIGN_CENTER_HORIZONTAL, 0);
m_humidity_img->SetBitmap(wxNullBitmap);
// Create a horizontal sizer for description and icon
wxBoxSizer* desc_sizer = new wxBoxSizer(wxHORIZONTAL);
m_image_description_icon = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
m_image_description_icon->SetBackgroundColour(*wxWHITE);
m_image_description_icon->SetMinSize(wxSize(FromDIP(20), FromDIP(20)));
m_image_description_icon->SetMaxSize(wxSize(FromDIP(20), FromDIP(20)));
m_image_description_icon->Show(false);
desc_sizer->Add(m_image_description_icon, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(5));
m_image_description = new Label(parent, _L("Idle"));
m_image_description->SetFont(Label::Head_14);
m_image_description->SetForegroundColour(*wxBLACK);
m_image_description->SetBackgroundColour(*wxWHITE);
desc_sizer->Add(m_image_description, 0, wxALIGN_CENTER_VERTICAL);
image_sizer->Add(desc_sizer, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
return image_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_description_item(wxPanel* parent, const wxString& title, Label*& dataLabel)
{
wxBoxSizer* item_sizer = new wxBoxSizer(wxVERTICAL);
Label* titleLabel = new Label(parent, title);
titleLabel->SetForegroundColour(*wxBLACK);
titleLabel->SetFont(Label::Body_16);
dataLabel = new Label(parent, wxT("--"));
dataLabel->SetForegroundColour(*wxBLACK);
dataLabel->SetFont(Label::Body_14);
item_sizer->AddStretchSpacer();
item_sizer->Add(titleLabel, 0, wxALIGN_CENTER | wxALL, FromDIP(2));
item_sizer->Add(dataLabel, 0, wxALIGN_CENTER | wxALL, FromDIP(2));
item_sizer->AddStretchSpacer();
return item_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_status_descriptions_section(wxPanel* parent)
{
wxBoxSizer* desc_sizer = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer* hum_desc_sizer = create_description_item(parent, _L("Humidity"), m_humidity_data_label);
desc_sizer->Add(hum_desc_sizer, 1, wxEXPAND, FromDIP(1));
wxStaticLine* vert_separator = new wxStaticLine(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL);
desc_sizer->Add(vert_separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(5));
wxBoxSizer* temp_desc_sizer = create_description_item(parent, _L("Temperature"), m_temperature_data_label);
desc_sizer->Add(temp_desc_sizer, 1, wxEXPAND, FromDIP(1));
m_time_descrition_container = new wxBoxSizer(wxHORIZONTAL);
wxStaticLine* vert_separator2 = new wxStaticLine(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL);
m_time_descrition_container->Add(vert_separator2, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(5));
wxBoxSizer* time_desc_sizer = create_description_item(parent, _L("Left Time"), m_time_data_label);
m_time_descrition_container->Add(time_desc_sizer, 1, wxEXPAND, FromDIP(1));
desc_sizer->Add(m_time_descrition_container, 1, wxEXPAND, 0);
// m_time_descrition_container->Show(false);
return desc_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_left_panel(wxPanel* parent)
{
wxBoxSizer* left_sizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer* image_section = create_humidity_status_section(parent);
left_sizer->Add(image_section, 1, wxEXPAND | wxALL, FromDIP(5));
wxBoxSizer* descriptions_section = create_status_descriptions_section(parent);
left_sizer->Add(descriptions_section, 0, wxEXPAND | wxALL, FromDIP(20));
return left_sizer;
}
Button* AMSDryCtrWin::create_button(wxPanel* parent, const wxString& title,
const wxColour& background_color, const wxColour& border_color, const wxColour& text_color)
{
Button* button = new Button(parent, title);
// Create state colors for background
StateColor bg_color(
std::pair<wxColour, int>(AMS_CONTROL_DISABLE_COLOUR, StateColor::Disabled),
std::pair<wxColour, int>(background_color.ChangeLightness(80), StateColor::Pressed),
std::pair<wxColour, int>(background_color.ChangeLightness(120), StateColor::Hovered),
std::pair<wxColour, int>(background_color, StateColor::Normal)
);
// Create state colors for border
StateColor bd_color(
std::pair<wxColour, int>(AMS_CONTROL_WHITE_COLOUR, StateColor::Disabled),
std::pair<wxColour, int>(border_color, StateColor::Enabled)
);
button->SetBackgroundColor(bg_color);
button->SetBorderColor(bd_color);
button->SetTextColor(text_color);
button->SetFont(Label::Body_14);
// Auto-size button based on text content with padding
wxSize best_size = button->GetBestSize();
int padding_width = FromDIP(4);
int padding_height = FromDIP(2);
wxSize final_size(best_size.GetWidth() + padding_width, best_size.GetHeight() + padding_height);
button->SetMinSize(final_size);
return button;
}
wxBoxSizer* AMSDryCtrWin::create_normal_state_panel(wxPanel* parent)
{
wxBoxSizer* normal_state_sizer = new wxBoxSizer(wxVERTICAL);
Label* description_label = new Label(parent, _L("Filament Drying Settings"));
description_label->SetForegroundColour(*wxBLACK);
description_label->SetFont(Label::Head_14);
normal_state_sizer->Add(description_label, 0, wxALL, FromDIP(5));
// Part 2: ComboBox for material selection
wxBoxSizer* combo_sizer = new wxBoxSizer(wxHORIZONTAL);
m_trays_combo = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(-1, FromDIP(26)), 0, nullptr, wxCB_READONLY);
m_trays_combo->SetFont(Label::Body_14);
combo_sizer->Add(m_trays_combo, 1, wxEXPAND);
normal_state_sizer->Add(combo_sizer, 0, wxEXPAND | wxALL, FromDIP(5));
m_trays_combo->Bind(wxEVT_COMBOBOX, &AMSDryCtrWin::OnFilamentSelectionChanged, this);
// part 3 time and temperature
// Temperature part
wxBoxSizer* temp_sizer = new wxBoxSizer(wxHORIZONTAL);
m_temperature_input = new wxTextCtrl(parent, wxID_ANY, "", wxDefaultPosition, wxSize(FromDIP(80), -1));
m_temperature_input->SetMaxLength(3); // Limit to 3 digits
m_temperature_input->Bind(wxEVT_CHAR, [this](wxKeyEvent& event) {
int keycode = event.GetKeyCode();
if (keycode >= '0' && keycode <= '9') {
event.Skip();
} else if (keycode == WXK_BACK || keycode == WXK_DELETE || keycode == WXK_LEFT || keycode == WXK_RIGHT) {
event.Skip();
}
});
m_temperature_input->Bind(wxEVT_TEXT, [this](wxCommandEvent&) {
m_next_button->Disable();
m_start_button->Disable();
});
m_temperature_input->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
m_temperature_input->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
Label* temp_unit_label = new Label(parent, wxString::FromUTF8(""));
temp_unit_label->SetForegroundColour(*wxBLACK);
temp_sizer->Add(m_temperature_input, 1, wxRIGHT, FromDIP(1));
temp_sizer->Add(temp_unit_label, 0, wxALIGN_CENTER_VERTICAL);
// Time part
wxBoxSizer* time_sizer = new wxBoxSizer(wxHORIZONTAL);
m_time_input = new wxTextCtrl(parent, wxID_ANY, "", wxDefaultPosition, wxSize(FromDIP(100), -1));
m_time_input->SetMaxLength(3); // Limit to 3 digits
m_time_input->Bind(wxEVT_CHAR, [this](wxKeyEvent& event) {
int keycode = event.GetKeyCode();
if (keycode >= '0' && keycode <= '9') {
event.Skip();
} else if (keycode == WXK_BACK || keycode == WXK_DELETE || keycode == WXK_LEFT || keycode == WXK_RIGHT) {
event.Skip();
}
});
m_time_input->Bind(wxEVT_TEXT, [this](wxCommandEvent&) {
m_next_button->Disable();
m_start_button->Disable();
});
m_time_input->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
m_time_input->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
Label* time_unit_label = new Label(parent, "H");
time_unit_label->SetForegroundColour(*wxBLACK);
time_sizer->Add(m_time_input, 1, wxRIGHT, FromDIP(1));
time_sizer->Add(time_unit_label, 0, wxALIGN_CENTER_VERTICAL);
wxBoxSizer* input_sizer = new wxBoxSizer(wxHORIZONTAL);
input_sizer->Add(temp_sizer, 1, wxRIGHT, FromDIP(10));
input_sizer->Add(time_sizer, 1, 0);
normal_state_sizer->Add(input_sizer, 0, wxEXPAND | wxALL, FromDIP(5));
// Part 4: Abnormal description/message area
m_normal_description = new Label(parent, "");
m_normal_description->SetForegroundColour(wxColour("#F09A17"));
m_normal_description->SetFont(Label::Body_12);
normal_state_sizer->Add(m_normal_description, 0, wxALL, FromDIP(5));
// Part 5: Start button
m_next_button = create_button(
parent,
_L("Start"),
AMS_CONTROL_BRAND_COLOUR, // Background color - green
AMS_CONTROL_BRAND_COLOUR, // Border color - green
wxColour("#FFFFFE") // Text color - white
);
m_next_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) {
m_main_simplebook->SetSelection(1);
});
normal_state_sizer->Add(m_next_button, 0, wxALL, FromDIP(5));
m_next_button->Disable();
m_stop_button = create_button(
parent,
_L("Stop"),
wxColour(255, 0, 0), // Background color - red
wxColour(255, 0, 0), // Border color - red
wxColour("#FFFFFE") // Text color - white
);
m_stop_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) {
auto fila_system = get_fila_system();
if (!fila_system) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::start_sending_drying_command: Invalid FilaSystem Pointer";
return;
}
fila_system->CtrlAmsStopDrying(std::stoi(m_ams_info.m_ams_id));
// Temporarily show stopping state, then restore after 2 seconds
if (m_stop_button) {
m_stop_button->SetLabel(_L("Stopping"));
update_button_size(m_stop_button); // Adjust button size for longer text
m_stop_button->Disable();
m_stop_button->Layout();
m_stop_button->GetParent()->Layout(); // Relayout parent container
m_stop_button->Refresh();
m_stop_button_restore_deadline.reset();
m_stop_button_restore_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
}
});
m_stop_button->Show(false);
normal_state_sizer->Add(m_stop_button, 0, wxALL, FromDIP(5));
return normal_state_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_cannot_dry_panel(wxPanel* parent)
{
wxBoxSizer* abnormal_sizer = new wxBoxSizer(wxVERTICAL);
Label* description_label = new Label(parent, _L("Unable to dry temporarily due to ..."));
description_label->SetForegroundColour(*wxBLACK);
description_label->SetFont(Label::Head_16);
description_label->Wrap(FromDIP(300));
abnormal_sizer->Add(description_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(10));
// Add a description label for the abnormal state
m_cannot_dry_description_label = new Label(parent, (""));
m_cannot_dry_description_label->SetForegroundColour(*wxBLACK);
m_cannot_dry_description_label->SetFont(Label::Body_14);
m_cannot_dry_description_label->Wrap(FromDIP(250)); // Wrap text to fit within panel
abnormal_sizer->Add(m_cannot_dry_description_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(10));
// Unload button shown only when ConsumableAtAmsOutlet reason is active
m_unload_button = create_button(
parent,
_L("Unload"),
AMS_CONTROL_BRAND_COLOUR, // Background color - light gray
AMS_CONTROL_BRAND_COLOUR, // Border color - gray
*wxWHITE // Text color - white
);
m_unload_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) {
auto fila_system = get_fila_system();
if (!fila_system) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::unload_button: Invalid FilaSystem Pointer";
return;
}
MachineObject* obj = fila_system->GetOwner();
if (!obj) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::unload_button: Invalid MachineObject Pointer";
return;
}
obj->command_ams_change_filament(false, m_ams_info.m_ams_id, "255");
m_unload_button->Disable();
m_unload_button_restore_deadline.reset();
m_unload_button_restore_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
});
m_unload_button->Show(false);
abnormal_sizer->Add(m_unload_button, 0, wxALL, FromDIP(5));
return abnormal_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_drying_error_panel(wxPanel* parent)
{
wxBoxSizer* err_sizer = new wxBoxSizer(wxVERTICAL);
Label* description_label = new Label(parent, _L("Drying Error"));
description_label->SetForegroundColour(*wxRED);
description_label->SetFont(Label::Body_14); // Wrap text to fit within panel
err_sizer->Add(description_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(10));
Label* additional_description_label = new Label(parent, _L("Please check the Assistant for troubleshooting"));
additional_description_label->SetForegroundColour(*wxBLACK);
additional_description_label->SetFont(Label::Body_14); // Wrap text to fit within panel
err_sizer->Add(additional_description_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(10));
return err_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_right_panel(wxPanel* parent)
{
wxBoxSizer* right_sizer = new wxBoxSizer(wxHORIZONTAL);
m_normal_state_sizer = create_normal_state_panel(parent);
m_cannot_dry_sizer = create_cannot_dry_panel(parent);
m_dry_error_sizer = create_drying_error_panel(parent);
right_sizer->Add(m_normal_state_sizer, 1, wxEXPAND);
right_sizer->Add(m_cannot_dry_sizer, 1, wxEXPAND);
m_cannot_dry_sizer->Show(false);
m_dry_error_sizer->Show(false);
return right_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_main_content_section(wxPanel* parent)
{
wxBoxSizer* content_sizer = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer* left_panel = create_left_panel(parent);
content_sizer->Add(left_panel, 2, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
left_panel->SetMinSize(wxSize(FromDIP(250), -1));
wxBoxSizer* right_panel = create_right_panel(parent);
content_sizer->Add(right_panel, 1, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
right_panel->SetMinSize(wxSize(FromDIP(250), -1));
return content_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_main_page_sizer(wxPanel* parent)
{
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer* content_section = create_main_content_section(parent);
main_sizer->Add(content_section, 1, wxEXPAND | wxALL, FromDIP(5));
return main_sizer;
}
wxBoxSizer* AMSDryCtrWin::create_guide_info_filament(wxPanel* parent)
{
wxBoxSizer* filament_section = new wxBoxSizer(wxHORIZONTAL);
m_ams_filament_panel = new AMSFilamentPanel(parent, "");
filament_section->Add(m_ams_filament_panel, 0, wxEXPAND | wxALL | wxALIGN_CENTER_HORIZONTAL, FromDIP(5));
return filament_section;
}
wxBoxSizer* AMSDryCtrWin::create_guide_info_section(wxPanel* parent)
{
wxBoxSizer* info_section = new wxBoxSizer(wxVERTICAL);
// Part 1: Title
m_guide_title_label = new Label(parent, _L("Please remove and store the filament (as shown)."));
m_guide_title_label->SetForegroundColour(*wxBLACK);
m_guide_title_label->SetFont(Label::Head_18);
info_section->Add(m_guide_title_label, 0, wxEXPAND | wxALL, FromDIP(5));
// Part 2: Description
m_guide_description_label = new Label(parent, _L("The AMS can rotate the filament which is properly stored, providing better drying results."));
m_guide_description_label->SetForegroundColour(*wxBLACK);
m_guide_description_label->SetFont(Label::Body_14);
m_guide_description_label->Wrap(FromDIP(300)); // Wrap text to fit within panel
info_section->Add(m_guide_description_label, 0, wxEXPAND | wxALL, FromDIP(5));
// Part 3: filament panel
wxBoxSizer* fila_section = create_guide_info_filament(parent);
info_section->Add(fila_section, 0, wxEXPAND | wxALL, FromDIP(5));
// Part 4: Circular toggle with description
wxBoxSizer* toggle_section = new wxBoxSizer(wxHORIZONTAL);
m_rotate_spool_toggle = new wxCheckBox(parent, wxID_ANY, "");
m_rotate_spool_toggle->SetValue(false);
m_rotate_spool_toggle->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) {
bool is_checked = event.IsChecked();
// Add toggle behavior logic here
});
toggle_section->Add(m_rotate_spool_toggle, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(10));
// Toggle description
Label* toggle_description = new Label(parent, _L("Rotate spool when drying"), LB_AUTO_WRAP);
toggle_description->SetForegroundColour(*wxBLACK);
toggle_description->SetFont(Label::Body_12);
toggle_section->Add(toggle_description, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, 0);
info_section->Add(toggle_section, 0, wxEXPAND | wxALL, FromDIP(5));
return info_section;
}
wxBoxSizer* AMSDryCtrWin::create_guide_right_section(wxPanel* parent)
{
wxBoxSizer* right_section = new wxBoxSizer(wxVERTICAL);
// Upper part: Image section
m_image_placeholder = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
m_guide_image = ScalableBitmap(parent, "dev_ams_dry_ctr_filament_in_chamber", 256);
m_image_placeholder->SetBitmap(m_guide_image.bmp());
wxBoxSizer* image_container = new wxBoxSizer(wxHORIZONTAL);
image_container->AddStretchSpacer(1);
image_container->Add(m_image_placeholder, 0, wxALL, FromDIP(5));
right_section->Add(image_container, 0, wxEXPAND | wxALL, FromDIP(5));
wxBoxSizer* buttons_container = new wxBoxSizer(wxHORIZONTAL);
buttons_container->AddStretchSpacer(1);
m_back_button = create_button(
parent,
wxString::FromUTF8(_CTX_utf8(L_CONTEXT("Back", "amsdrying"), "amsdrying")),
wxColour("#F8F8F8"), // Background color - light gray
wxColour("#D0D0D0"), // Border color - gray
*wxBLACK // Text color - black
);
m_back_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) {
m_main_simplebook->SetSelection(0);
});
buttons_container->Add(m_back_button, 0, wxALL, FromDIP(5));
m_start_button = create_button(
parent,
_L("Start"),
AMS_CONTROL_BRAND_COLOUR, // Background color - green
AMS_CONTROL_BRAND_COLOUR, // Border color - green
wxColour("#FFFFFE") // Text color - white
);
m_start_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) {
m_main_simplebook->SetSelection(2);
m_progress_gauge->SetValue(0);
m_progress_timer->Start(70);
m_progress_value = 0;
start_sending_drying_command();
});
buttons_container->Add(m_start_button, 0, wxALL, FromDIP(5));
right_section->Add(buttons_container, 0, wxEXPAND | wxALL, FromDIP(5));
return right_section;
}
wxBoxSizer* AMSDryCtrWin::create_guide_page_sizer(wxPanel* parent)
{
wxBoxSizer* guide_sizer = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer* info_section = create_guide_info_section(parent);
guide_sizer->Add(info_section, 1, wxEXPAND | wxALL, FromDIP(10));
wxBoxSizer* right_section = create_guide_right_section(parent);
guide_sizer->Add(right_section, 0, wxEXPAND | wxALL, FromDIP(10));
return guide_sizer;
}
void AMSDryCtrWin::start_sending_drying_command()
{
auto fila_system = get_fila_system();
if (!fila_system) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::start_sending_drying_command: Invalid FilaSystem Pointer";
return;
}
if (m_temperature_input->GetValue().IsEmpty() || m_time_input->GetValue().IsEmpty()) {
// Show error message to user
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::start_sending_drying_command: Time or temperature is empty";
return;
}
long temperature, time;
if (!m_temperature_input->GetValue().ToLong(&temperature) ||
!m_time_input->GetValue().ToLong(&time)) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::start_sending_drying_command: Failed to convert temperature or time";
return;
}
int tray_index;
tray_index = m_trays_combo->GetSelection();
if (m_tray_ids.empty() || tray_index < 0 || tray_index >= m_tray_ids.size()) {
BOOST_LOG_TRIVIAL(warning) << "AMSDryCtrWin::start_sending_drying_command: Invalid tray_index " << tray_index
<< ", m_tray_ids.size=" << m_tray_ids.size();
return;
}
int cooling_temp = 50;
std::optional<DevFilamentDryingPreset> preset = DevUtilBackend::GetFilamentDryingPreset(m_tray_ids[tray_index].filament_id);
if (preset.has_value()) {
cooling_temp = static_cast<int>(preset.value().filament_dev_drying_softening_temperature);
}
fila_system->CtrlAmsStartDryingHour(std::stoi(m_ams_info.m_ams_id), m_tray_ids[tray_index].filament_type,
temperature, time, m_rotate_spool_toggle->GetValue(), cooling_temp, false);
m_dry_setting.m_filament_names[m_ams_info.m_ams_id] = m_tray_ids[tray_index].filament_name;
m_dry_setting.m_filament_type[m_ams_info.m_ams_id] = m_tray_ids[tray_index].filament_type;
m_dry_setting.m_dry_temp[m_ams_info.m_ams_id] = temperature;
m_dry_setting.m_dry_time[m_ams_info.m_ams_id] = time;
}
void AMSDryCtrWin::OnProgressTimer(wxTimerEvent& event)
{
m_progress_value += 1;
if (m_progress_value <= 100)
m_progress_gauge->SetValue(m_progress_value);
if (m_progress_value == 1) { // First tick, reset message index
m_progress_message_index = 0;
if (!m_progress_text.empty()) {
m_progress_title->SetLabel(m_progress_text[0]);
}
}
else if (m_progress_value % 15 == 0) { // Approximately every second
m_progress_message_index++;
if (m_progress_message_index < m_progress_text.size()) {
m_progress_title->SetLabel(m_progress_text[m_progress_message_index]);
m_progress_title->Refresh();
}
}
if (m_progress_value >= 100 && !is_dry_ctr_idle()) {
m_progress_value = 0;
m_progress_timer->Stop();
m_main_simplebook->SetSelection(0);
}
}
void AMSDryCtrWin::restore_stop_button_if_deadline_passed()
{
if (m_stop_button_restore_deadline.has_value()) {
if (std::chrono::steady_clock::now() >= m_stop_button_restore_deadline.value()) {
if (m_stop_button) {
m_stop_button->SetLabel(_L("Stop"));
update_button_size(m_stop_button); // Adjust button size back to original
m_stop_button->Enable();
m_stop_button->Layout();
m_stop_button->GetParent()->Layout();
m_stop_button->Refresh();
}
m_stop_button_restore_deadline.reset();
}
}
}
void AMSDryCtrWin::restore_unload_button_if_deadline_passed()
{
if (m_unload_button_restore_deadline.has_value()) {
if (std::chrono::steady_clock::now() >= m_unload_button_restore_deadline.value()) {
if (m_unload_button) {
m_unload_button->Enable();
}
m_unload_button_restore_deadline.reset();
}
}
}
void AMSDryCtrWin::update_button_size(Button* button)
{
if (!button) return;
// Reset MinSize first to avoid accumulation of size
button->SetMinSize(wxSize(-1, -1));
// Recalculate button size based on current text and DPI settings
wxSize best_size = button->GetBestSize();
int padding_width = FromDIP(4);
int padding_height = FromDIP(2);
wxSize final_size(best_size.GetWidth() + padding_width, best_size.GetHeight() + padding_height);
button->SetMinSize(final_size);
}
void AMSDryCtrWin::OnShow(wxShowEvent& event)
{
if (event.IsShown()) {
wxGetApp().UpdateDlgDarkUI(this);
msw_rescale();
}
event.Skip();
}
void AMSDryCtrWin::OnClose(wxCloseEvent& event)
{
if (m_progress_timer && m_progress_timer->IsRunning()) {
m_progress_timer->Stop();
}
if (m_progress_gauge) {
m_progress_gauge->SetValue(0);
}
m_progress_value = 0;
m_progress_message_index = 0;
if (m_main_simplebook) {
m_main_simplebook->SetSelection(0);
}
if (m_progress_title && !m_progress_text.empty()) {
m_progress_title->SetLabel(m_progress_text[0]);
}
// Clean up and restore stop button state
if (m_stop_button) {
m_stop_button->SetLabel(_L("Stop"));
update_button_size(m_stop_button); // Adjust button size back to original
m_stop_button->Enable();
m_stop_button->Layout();
m_stop_button->GetParent()->Layout();
m_stop_button->Refresh();
}
m_stop_button_restore_deadline.reset();
// Clean up unload button state
if (m_unload_button) {
m_unload_button->Enable();
}
m_unload_button_restore_deadline.reset();
event.Skip();
}
wxBoxSizer* AMSDryCtrWin::create_progress_page_sizer(wxPanel* parent)
{
wxBoxSizer* progress_sizer = new wxBoxSizer(wxVERTICAL);
m_progress_title = new Label(parent, m_progress_text[0]);
m_progress_title->SetForegroundColour(*wxBLACK);
m_progress_title->SetFont(Label::Body_16);
progress_sizer->Add(0, 0, 1, wxEXPAND, 0); // Spacer
progress_sizer->Add(m_progress_title, 0, wxALIGN_CENTER | wxALL, FromDIP(30));
m_progress_gauge = new ProgressBar(parent, wxID_ANY, 100, wxDefaultPosition, wxSize(FromDIP(300), FromDIP(8)));
m_progress_gauge->SetValue(0);
m_progress_gauge->SetHeight(FromDIP(8));
progress_sizer->Add(m_progress_gauge, 0, wxALIGN_CENTER | wxALL, FromDIP(10));
progress_sizer->Add(0, 0, 1, wxEXPAND, 0);
return progress_sizer;
}
void AMSDryCtrWin::create()
{
// set title icon
SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
this->SetDoubleBuffered(true);
std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetSize(wxSize(FromDIP(700), FromDIP(500)));
SetMinSize(wxSize(FromDIP(700), FromDIP(500)));
SetMaxSize(wxSize(FromDIP(700), FromDIP(500)));
m_main_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
// Create main page
m_original_page = new wxPanel(m_main_simplebook, wxID_ANY);
m_original_page->SetBackgroundColour(*wxWHITE);
wxBoxSizer* main_sizer = create_main_page_sizer(m_original_page);
m_original_page->SetSizer(main_sizer);
m_main_simplebook->AddPage(m_original_page, "Main Page");
m_guide_page = new wxPanel(m_main_simplebook, wxID_ANY);
m_guide_page->SetBackgroundColour(*wxWHITE);
wxBoxSizer* guide_sizer = create_guide_page_sizer(m_guide_page);
m_guide_page->SetSizer(guide_sizer);
m_main_simplebook->AddPage(m_guide_page, "Guide Page");
// Create progress page
m_progress_page = new wxPanel(m_main_simplebook, wxID_ANY);
m_progress_page->SetBackgroundColour(*wxWHITE);
wxBoxSizer* progress_sizer = create_progress_page_sizer(m_progress_page);
m_progress_page->SetSizer(progress_sizer);
m_main_simplebook->AddPage(m_progress_page, "Progress Page");
m_progress_timer = new wxTimer(this, wxID_ANY);
Bind(wxEVT_TIMER, &AMSDryCtrWin::OnProgressTimer, this, m_progress_timer->GetId());
Bind(wxEVT_CLOSE_WINDOW, &AMSDryCtrWin::OnClose, this);
wxBoxSizer* top_level_sizer = new wxBoxSizer(wxVERTICAL);
top_level_sizer->Add(m_main_simplebook, 1, wxEXPAND | wxALL, FromDIP(10));
Bind(wxEVT_SHOW, &AMSDryCtrWin::OnShow, this);
SetSizer(top_level_sizer);
Layout();
Refresh();
wxGetApp().UpdateDlgDarkUI(this);
}
void AMSDryCtrWin::on_dpi_changed(const wxRect &suggested_rect)
{
msw_rescale();
}
void AMSDryCtrWin::msw_rescale()
{
if (!IsShown()) {
return;
}
if (m_ams_filament_panel) {m_ams_filament_panel->msw_rescale();}
if (m_trays_combo) {m_trays_combo->Rescale(); m_trays_combo->Layout(); m_trays_combo->SetFont(Label::Body_14), m_trays_combo->Refresh();}
if (m_humidity_img && m_humidity_image.bmp().IsOk()) {m_humidity_image.msw_rescale(); m_humidity_img->SetBitmap(m_humidity_image.bmp());}
if (m_image_placeholder && m_guide_image.bmp().IsOk()) {m_guide_image.msw_rescale();m_image_placeholder->SetBitmap(m_guide_image.bmp());}
if (m_image_description_icon && m_description_icon_bitmap.bmp().IsOk()) {m_description_icon_bitmap.msw_rescale(); m_image_description_icon->SetBitmap(m_description_icon_bitmap.bmp());}
// Rescale and update button sizes based on text content
if (m_next_button) {
m_next_button->Rescale();
update_button_size(m_next_button);
m_next_button->Layout();
m_next_button->Refresh();
}
if (m_stop_button) {
m_stop_button->Rescale();
update_button_size(m_stop_button);
m_stop_button->Layout();
m_stop_button->Refresh();
}
if (m_start_button) {
m_start_button->Rescale();
update_button_size(m_start_button);
m_start_button->Layout();
m_start_button->Refresh();
}
if (m_back_button) {
m_back_button->Rescale();
update_button_size(m_back_button);
m_back_button->Layout();
m_back_button->Refresh();
}
if (m_unload_button) {
m_unload_button->Rescale();
update_button_size(m_unload_button);
m_unload_button->Layout();
m_unload_button->Refresh();
}
if (m_guide_page) {m_guide_page->Layout();}
if (m_original_page) {m_original_page->Layout();}
Fit();
Layout();
Refresh();
}
void AMSDryCtrWin::set_ams_id(const std::string& ams_id)
{
m_ams_info.m_ams_id = ams_id;
m_is_ams_changed = true;
}
void AMSDryCtrWin::update_img_description(DevAms::DryStatus status, DevAms::DrySubStatus sub_status)
{
if (status == DevAms::DryStatus::Off || status == DevAms::DryStatus::Cooling) {
m_image_description->SetLabel(_L("Idle"));
m_image_description_icon->Show(false);
return;
}
// Determine label text for non-idle states
wxString label_text;
if (status == DevAms::DryStatus::Error) {
label_text = _L("Drying");
} else if (sub_status == DevAms::DrySubStatus::Heating) {
label_text = _L("Drying-Heating");
} else if (sub_status == DevAms::DrySubStatus::Dehumidify) {
label_text = _L("Drying-Dehumidifying");
} else {
m_image_description_icon->Show(false);
return;
}
m_image_description->SetLabel(label_text);
try {
m_description_icon_bitmap = ScalableBitmap(this, "dev_ams_dry_ctr_heating_icon", 20);
m_description_icon_bitmap.msw_rescale();
m_image_description_icon->SetBitmap(m_description_icon_bitmap.bmp());
m_image_description_icon->Show(true);
} catch (Exception&) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Error loading drying icon";
m_image_description_icon->Show(false);
}
}
int AMSDryCtrWin::update_image(DevAmsType model, DevAms::DryStatus status, DevAms::DrySubStatus sub_status, int humidity_percent)
{
if (model == m_ams_info.m_model && status == m_ams_info.m_dry_status
&& sub_status == m_ams_info.m_dry_sub_status && humidity_percent == m_ams_info.m_humidity_percent) {
return 0;
}
std::string img_path;
if (status == DevAms::DryStatus::Off && sub_status == DevAms::DrySubStatus::Off) {
img_path = get_humidity_level_img_path(humidity_percent);
} else {
img_path = get_dry_status_img_path(model, status, sub_status);
}
if (img_path.empty()) {
return 0;
}
m_humidity_image = ScalableBitmap(this, img_path, AMS_DRY_STATUS_IMAGE_SIZE);
m_humidity_img->SetBitmap(m_humidity_image.bmp());
update_img_description(status, sub_status);
return 1;
}
bool AMSDryCtrWin::check_values_changed(DevAms* dev_ams)
{
bool changed = false;
if (m_ams_info.m_model != dev_ams->GetAmsType()) {
m_ams_info.m_model = dev_ams->GetAmsType();
changed = true;
}
if (dev_ams->GetDryStatus().has_value() && m_ams_info.m_dry_status != dev_ams->GetDryStatus()) {
m_ams_info.m_dry_status = dev_ams->GetDryStatus().value();
changed = true;
}
if (dev_ams->GetDrySubStatus().has_value() && m_ams_info.m_dry_sub_status != dev_ams->GetDrySubStatus()) {
m_ams_info.m_dry_sub_status = dev_ams->GetDrySubStatus().value();
changed = true;
}
if (m_ams_info.m_humidity_percent != dev_ams->GetHumidityPercent()) {
m_ams_info.m_humidity_percent = dev_ams->GetHumidityPercent();
changed = true;
}
return changed;
}
void AMSDryCtrWin::update_normal_description(DevAms* dev_ams)
{
if (m_tray_ids.empty() || m_trays_combo->GetSelection() >= m_tray_ids.size()) { return; }
FilamentBaseInfo info = m_tray_ids[m_trays_combo->GetSelection()];
wxString warning_text;
bool can_enable_button = true;
long temp_val = 0, time_val = 0;
m_temperature_input->GetValue().ToLong(&temp_val);
m_time_input->GetValue().ToLong(&time_val);
std::optional<Slic3r::DevFilamentDryingPreset> preset = DevUtilBackend::GetFilamentDryingPreset(info.filament_id);
auto total_dry = preset.has_value() ? preset.value().ams_limitations : std::unordered_set<DevAmsType>();
struct AmsTempLimit { DevAmsType type; int min_temp; int max_temp; const char* name; };
static const AmsTempLimit ams_limits[] = {
{ DevAmsType::N3F, 45, 65, "AMS2" },
{ DevAmsType::N3S, 45, 85, "AMS-S" }
};
for (const auto& lim : ams_limits) {
if (dev_ams->GetAmsType() == lim.type) {
if (temp_val > lim.max_temp) {
wxString msg = wxString(lim.name) + _L(" maximum drying temperature is ") + wxString::Format(wxT("%d"), lim.max_temp) + wxString::FromUTF8("°C.");
warning_text += msg + "\n";
can_enable_button = false;
} else if (temp_val < lim.min_temp) {
wxString msg = wxString(lim.name) + _L(" minimum drying temperature is ") + wxString::Format(wxT("%d"), lim.min_temp) + wxString::FromUTF8("°C.");
warning_text += msg + "\n";
can_enable_button = false;
}
if (total_dry.find(lim.type) == total_dry.end()) {
warning_text += _L("This filament may not be completely dried.") + "\n\n";
}
break;
}
}
if (m_printer_status.m_is_printing && temp_val > m_ams_info.m_recommand_dry_temp) {
warning_text += _L("This AMS is currently printing. To ensure print quality, the drying temperature cannot exceed the recommended drying temperature.") + "\n";
can_enable_button = false;
} else if (preset.has_value()) {
// Only check heat distortion temperature when AMS has filament inserted;
// empty AMS only needs to respect the device hardware temperature limit.
bool has_filament_inserted = false;
for (const auto& tray_pair : dev_ams->GetTrays()) {
if (tray_pair.second && tray_pair.second->is_exists) {
has_filament_inserted = true;
break;
}
}
if (has_filament_inserted) {
auto limit_temperature = preset.value().filament_dev_ams_drying_heat_distortion_temperature;
if (temp_val > limit_temperature) {
warning_text += _L("The temperature shall not exceed the filament's heat distortion temperature") + "(" +
wxString::Format(wxT("%d"), static_cast<int>(limit_temperature)) + wxString::FromUTF8("°C)\n");
can_enable_button = false;
}
}
}
if (time_val < 1) {
warning_text += _L("Minimum time value cannot be less than 1.") + "\n";
can_enable_button = false;
} else if (time_val > 24) {
warning_text += _L("Maximum time value cannot be greater than 24.") + "\n";
can_enable_button = false;
}
m_next_button->Enable(can_enable_button);
m_normal_description->SetLabel(warning_text);
m_normal_description->Wrap(FromDIP(250));
m_normal_description->GetParent()->Layout();
}
void AMSDryCtrWin::update_normal_state(DevAms* dev_ams)
{
if (is_dry_ctr_idle(dev_ams)) {
m_next_button->Show(true);
m_normal_description->Show(true);
m_trays_combo->Enable();
m_temperature_input->SetEditable(true);
m_time_input->SetEditable(true);
m_stop_button->Hide();
m_dry_error_sizer->Show(false);
} else if (is_dry_ctr_err(dev_ams)) {
m_dry_error_sizer->Show(true);
m_next_button->Hide();
m_normal_description->Hide();
m_trays_combo->Hide();
m_temperature_input->Hide();
m_time_input->Hide();
m_stop_button->Show(true);
} else {
m_next_button->Hide();
m_normal_description->Hide();
m_trays_combo->Disable();
m_temperature_input->SetEditable(false);
m_time_input->SetEditable(false);
m_stop_button->Show(true);
m_dry_error_sizer->Show(false);
}
if (m_temperature_input->GetValue().IsEmpty() || m_time_input->GetValue().IsEmpty()) {
m_next_button->Disable();
m_start_button->Disable();
} else {
m_next_button->Enable();
}
update_normal_description(dev_ams);
}
void AMSDryCtrWin::OnFilamentSelectionChanged(wxCommandEvent& event)
{
int selectionIndex = event.GetSelection();
wxString selectedFilament = event.GetString();
auto fila_system = get_fila_system();
if (!fila_system) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::OnFilamentSelectionChanged: Invalid FilaSystem Pointer";
return;
}
DevAms* dev_ams = fila_system->GetAmsById(m_ams_info.m_ams_id);
if (!dev_ams) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::OnFilamentSelectionChanged: Invalid AMS id";
return;
}
std::optional<Slic3r::DevFilamentDryingPreset> preset = DevUtilBackend::GetFilamentDryingPreset(m_tray_ids[selectionIndex].filament_id);
if (preset.has_value()) {
DevFilamentDryingPreset info = preset.value();
if (m_printer_status.m_is_printing) {
m_temperature_input->SetValue(std::to_string(static_cast<int>(info.filament_dev_ams_drying_temperature_on_print[dev_ams->GetAmsType()])));
m_time_input->SetValue(std::to_string(static_cast<int>(info.filament_dev_ams_drying_time_on_print[dev_ams->GetAmsType()])));
} else {
m_temperature_input->SetValue(std::to_string(static_cast<int>(info.filament_dev_ams_drying_temperature_on_idle[dev_ams->GetAmsType()])));
m_time_input->SetValue(std::to_string(static_cast<int>(info.filament_dev_ams_drying_time_on_idle[dev_ams->GetAmsType()])));
}
}
update_filament_guide_info(dev_ams);
}
wxString get_cannot_reason_text(DevAms::CannotDryReason reason)
{
wxString cannot_reason_text;
switch (reason)
{
case DevAms::CannotDryReason::InsufficientPower:
cannot_reason_text = "*" + _L("Insufficient power") + "\n";
cannot_reason_text += _L(" Too many AMS drying simultaneously. Please plug in the power or stop other drying processes before starting.") + "\n";
break;
case DevAms::CannotDryReason::AmsBusy:
cannot_reason_text = "*" + _L("AMS is busy") + "\n";
cannot_reason_text += _L(" AMS is calibrating | reading RFID | loading/unloading material, please wait.") + "\n";
break;
case DevAms::CannotDryReason::ConsumableAtAmsOutlet:
cannot_reason_text = "*" + _L("Filament in AMS outlet") + "\n";
cannot_reason_text += _L(" The high drying temperature may cause AMS blockage, please unload first.");
break;
case DevAms::CannotDryReason::InitiatingAmsDrying:
cannot_reason_text = "*" + _L("Initiating AMS drying") + "\n";
break;
case DevAms::CannotDryReason::NotSupportedIn2dMode:
cannot_reason_text = "*" + _L("Not supported in 2D mode") + "\n";
break;
case DevAms::CannotDryReason::DryingInProgress:
cannot_reason_text = "*" + _L("Task in progress") + "\n";
cannot_reason_text += _L(" The AMS might be in use during Task.") + "\n";
break;
case DevAms::CannotDryReason::Upgrading:
cannot_reason_text = "*" + _L("Upgrading") + "\n";
cannot_reason_text += _L(" Firmware update in progress, please wait...") + "\n";
break;
case DevAms::CannotDryReason::InsufficientPowerNeedPluginPower:
cannot_reason_text = "*" + _L("Insufficient power") + "\n";
cannot_reason_text += _L(" Please plug in the power and then use the drying function.") + "\n";
break;
case DevAms::CannotDryReason::FilamentAtAmsOutletManualUnload:
cannot_reason_text = "*" + _L("Filament in AMS outlet") + "\n";
cannot_reason_text += _L(" The high drying temperature may cause AMS blockage. Please unload the filament manually before proceeding.") + "\n";
break;
default:
cannot_reason_text = "*" + _L("System is busy") + "\n";
cannot_reason_text += _L(" Initiating other drying processes, please wait a few seconds...") + "\n";
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": unknown cannot dry reason";
break;
}
return cannot_reason_text;
}
wxString organize_cannot_reasons_text(std::vector<DevAms::CannotDryReason>& reasons)
{
wxString cannot_reasons_text;
if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::DryingInProgress) != reasons.end()) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::DryingInProgress);
} else if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::AmsBusy) != reasons.end()) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::AmsBusy);
} else if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::ConsumableAtAmsOutlet) != reasons.end()) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::ConsumableAtAmsOutlet);
} else if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::FilamentAtAmsOutletManualUnload) != reasons.end()) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::FilamentAtAmsOutletManualUnload);
}
if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::InsufficientPower) != reasons.end()) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::InsufficientPower);
}
for (auto reason : reasons) {
if (reason == DevAms::CannotDryReason::InsufficientPowerNeedPluginPower) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::InsufficientPowerNeedPluginPower);
} else if (reason == DevAms::CannotDryReason::NotSupportedIn2dMode) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::NotSupportedIn2dMode);
} else if (reason == DevAms::CannotDryReason::InitiatingAmsDrying) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::InitiatingAmsDrying);
} else if (reason == DevAms::CannotDryReason::Upgrading) {
cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::Upgrading);
}
}
if (cannot_reasons_text.empty()) {
cannot_reasons_text += "*" + _L("System is busy") + "\n";
cannot_reasons_text += _L(" Initiating other drying processes, please wait a few seconds...") + "\n";
}
return cannot_reasons_text;
}
int AMSDryCtrWin::update_state(DevAms* dev_ams)
{
std::vector<DevAms::CannotDryReason> cannot_reasons = dev_ams->GetCannotDryReason().has_value()?
dev_ams->GetCannotDryReason().value(): std::vector<DevAms::CannotDryReason>();
if (cannot_reasons.size() == 0 ||
(cannot_reasons.size() == 1 && cannot_reasons[0] == DevAms::CannotDryReason::DryingInProgress)) {
m_normal_state_sizer->Show(true);
m_cannot_dry_sizer->Show(false);
m_unload_button->Show(false);
update_normal_state(dev_ams);
} else if (cannot_reasons.size() > 0) {
m_normal_state_sizer->Show(false);
m_cannot_dry_sizer->Show(true);
m_cannot_dry_description_label->SetLabel(organize_cannot_reasons_text(cannot_reasons));
m_cannot_dry_description_label->Wrap(FromDIP(300));
// Show unload button when ConsumableAtAmsOutlet reason is present
bool has_consumable_at_outlet = std::find(cannot_reasons.begin(), cannot_reasons.end(),
DevAms::CannotDryReason::ConsumableAtAmsOutlet) != cannot_reasons.end();
m_unload_button->Show(has_consumable_at_outlet);
}
restore_stop_button_if_deadline_passed();
restore_unload_button_if_deadline_passed();
return 1;
}
int AMSDryCtrWin::update_ams_change(DevAms* dev_ams)
{
if (!is_ams_changed(dev_ams)) {
return 0;
}
m_ams_info.m_ams_id = dev_ams->GetAmsId();
if (dev_ams->GetAmsType() == DevAmsType::N3F) {
m_temperature_input->SetHint("45-65" + wxString::FromUTF8("°C"));
} else if (dev_ams->GetAmsType() == DevAmsType::N3S) {
m_temperature_input->SetHint("45-85" + wxString::FromUTF8("°C"));
}
m_time_input->SetHint("1-24 h");
return 0;
}
int AMSDryCtrWin::update_dryness_status(DevAms* dev_ams)
{
int updated = 0;
if (m_ams_info.m_humidity_percent != dev_ams->GetHumidityPercent()) {
updated += 1;
m_ams_info.m_humidity_percent = dev_ams->GetHumidityPercent();
m_humidity_data_label->SetLabel(std::to_string(m_ams_info.m_humidity_percent) + "%");
}
if (m_ams_info.m_temperature != dev_ams->GetCurrentTemperature()) {
updated += 1;
m_ams_info.m_temperature = dev_ams->GetCurrentTemperature();
m_temperature_data_label->SetLabel(std::to_string(m_ams_info.m_temperature) + wxString::FromUTF8("°C"));
}
if (is_dry_ctr_idle(dev_ams)) {
m_time_descrition_container->Show(false);
m_original_page->Layout();
} else {
if (is_dry_status_changed(dev_ams)) {
m_time_descrition_container->Show(true);
m_original_page->Layout();
}
if (m_ams_info.m_left_dry_time != dev_ams->GetLeftDryTime()) {
updated += 1;
m_ams_info.m_left_dry_time = dev_ams->GetLeftDryTime();
m_time_data_label->SetLabel(wxString::Format("%02d : %02d",
m_ams_info.m_left_dry_time / 60, m_ams_info.m_left_dry_time % 60));
}
}
return updated;
}
bool AMSDryCtrWin::is_tray_changed(DevAms* dev_ams)
{
return false;
}
void AMSDryCtrWin::update_filament_guide_info(DevAms* dev_ams)
{
if (!IsShown()) {
return;
}
m_guide_page->Freeze();
m_ams_filament_panel->Clear();
m_ams_filament_panel->SetAmsName(dev_ams->GetDisplayName());
// Get the temperature input value
long input_temp = 0;
bool valid_temp = !m_temperature_input->GetValue().IsEmpty() &&
m_temperature_input->GetValue().ToLong(&input_temp);
bool can_start = true;
int slot_count = 0, empty_count = 0;
for (auto& tray_pair : dev_ams->GetTrays()) {
if (!tray_pair.second) {
continue;
}
++slot_count;
if (!tray_pair.second->is_exists) {
m_ams_filament_panel->AddFilamentItem("/", "dev_ams_dry_ctr_enable");
++empty_count;
continue;
}
auto preset_opt = tray_pair.second->get_ams_drying_preset();
wxString filament_type = tray_pair.second->get_display_filament_type();
DevFilamentDryingPreset preset;
if (filament_type.IsEmpty()) {
auto fallback_preset = DevUtilBackend::GetFilamentDryingPreset("GFA00");
preset = fallback_preset.value();
filament_type = "?";
} else if (preset_opt.has_value()) {
preset = preset_opt.value();
} else {
auto fallback_preset = DevUtilBackend::GetFilamentDryingPreset("GFA00");
preset = fallback_preset.value();
}
std::string icon_path = "dev_ams_dry_ctr_enable";
int distortion_temp = static_cast<int>(preset.filament_dev_ams_drying_heat_distortion_temperature);
if (valid_temp && distortion_temp < input_temp) {
icon_path = "dev_ams_dry_ctr_disable";
can_start = false;
}
m_ams_filament_panel->AddFilamentItem(filament_type, icon_path);
}
if (slot_count == 0) {
can_start = false;
}
if (can_start) {
m_guide_title_label->SetLabel(_L("For better drying results, remove the filament and allow it to rotate."));
m_guide_description_label->SetLabel(_L("The AMS will automatically rotate the stored filament slots to enhance the drying performance.") +
"\n" + _L("Alternatively, you can dry the filament without removing it.") +
"\n" + "*" + _L("Unknown filaments will be treated as PLA."));
m_guide_description_label->Wrap(FromDIP(300));
} else {
m_guide_title_label->SetLabel(_L("Please store the filament marked with an exclamation mark."));
m_guide_description_label->SetLabel(_L("Filament left in the feeder during drying may soften because the drying temperature exceeds the softening point of materials like PLA and TPU.") +
"\n" + "*" + _L("Unknown filaments will be treated as PLA."));
m_guide_description_label->Wrap(FromDIP(300));
}
m_start_button->Enable(can_start);
m_guide_page->Layout();
m_guide_page->Thaw();
}
int AMSDryCtrWin::update_filament_list(DevAms* dev_ams, MachineObject* obj)
{
const bool ams_changed = is_ams_changed(dev_ams);
bool rebuilt_filament_list = false;
// Build `m_tray_ids` and the combobox items from the preset bundle.
// This list is also used as a lookup table in non-idle state (type -> name),
// so it must be available even when the combobox is disabled.
auto rebuild_filament_list = [&]() -> bool {
m_trays_combo->Clear();
m_tray_ids.clear();
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
if (!preset_bundle || !obj) {
return false;
}
std::set<std::string> filament_id_set;
auto & filaments = preset_bundle->filaments;
// Get nozzle diameter using the same method as AMSMaterialsSetting::Popup
std::ostringstream stream;
int extruder_id = obj->GetFilaSystem()->GetExtruderIdByAmsId(m_ams_info.m_ams_id);
if (!obj->GetExtderSystem()->GetExtderById(extruder_id)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " get extruder id failed";
extruder_id = 0;
}
stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(extruder_id);
std::string nozzle_diameter_str = stream.str();
std::set<std::string> printer_names = preset_bundle->get_printer_names_by_printer_type_and_nozzle(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str);
for (auto filament_it = filaments.begin(); filament_it != filaments.end(); ++filament_it) {
Preset& preset = *filament_it;
// Filter by system preset: root preset and (system preset or user preset is supported)
if (filaments.get_preset_base(*filament_it) != &preset || (!filament_it->is_system && !obj->is_support_user_preset)) {
continue;
}
ConfigOption * printer_opt = filament_it->config.option("compatible_printers");
ConfigOptionStrings *printer_strs = dynamic_cast<ConfigOptionStrings *>(printer_opt);
if (!printer_strs) continue;
for (auto printer_str : printer_strs->values) {
if (printer_names.find(printer_str) != printer_names.end()) {
if (filament_id_set.find(filament_it->filament_id) != filament_id_set.end()) {
continue;
}
filament_id_set.insert(filament_it->filament_id);
auto filament_alias = filament_it->alias;
if (!filament_alias.empty()) {
auto opt_info = preset_bundle->get_filament_by_filament_id(filament_it->filament_id);
if (opt_info.has_value()) {
auto real_info = opt_info.value();
real_info.filament_name = filament_alias;
m_tray_ids.push_back(std::move(real_info));
m_trays_combo->Append(wxString::FromUTF8(filament_alias));
}
}
}
}
}
if (m_tray_ids.empty()) {
BOOST_LOG_TRIVIAL(warning) << "AMSDryCtrWin::update_filament_list: No valid filaments found";
return false;
}
return true;
};
if (ams_changed || m_tray_ids.empty() || m_trays_combo->GetCount() == 0) {
// Disable start button during rebuild
if (m_start_button) {
m_start_button->Disable();
}
rebuilt_filament_list = rebuild_filament_list();
}
if (!is_dry_ctr_idle(dev_ams)) {
const std::string& ams_id = dev_ams->GetAmsId();
const auto settings_opt = dev_ams->GetDrySettings();
// Set the combobox display label by the filament "type" string.
// Priority: last saved selection for this AMS (if the type matches) -> fallback to current `m_tray_ids`.
auto set_filament_label_by_type = [&](const std::string& filament_type) {
if (filament_type.empty()) {
return;
}
auto it_name = m_dry_setting.m_filament_names.find(ams_id);
auto it_type = m_dry_setting.m_filament_type.find(ams_id);
if (it_name != m_dry_setting.m_filament_names.end() && it_type != m_dry_setting.m_filament_type.end()
&& it_type->second == filament_type) {
m_trays_combo->SetLabel(wxString::FromUTF8(it_name->second));
return;
}
const auto it = std::find_if(m_tray_ids.begin(), m_tray_ids.end(), [&](const FilamentBaseInfo& tray) {
return tray.filament_type == filament_type;
});
if (it != m_tray_ids.end()) {
m_trays_combo->SetLabel(wxString::FromUTF8(it->filament_name));
}
};
if (settings_opt.has_value()) {
const auto& settings = settings_opt.value();
m_temperature_input->SetValue(std::to_string(settings.dry_temp));
m_time_input->SetValue(std::to_string(settings.dry_hour));
set_filament_label_by_type(settings.dry_filament);
} else {
auto it_name = m_dry_setting.m_filament_names.find(ams_id);
if (it_name != m_dry_setting.m_filament_names.end()) {
m_trays_combo->SetLabel(wxString::FromUTF8(it_name->second));
m_temperature_input->SetValue(std::to_string(m_dry_setting.m_dry_temp[ams_id]));
m_time_input->SetValue(std::to_string(m_dry_setting.m_dry_time[ams_id]));
}
}
return 0;
}
// Idle state: avoid re-selecting / firing selection change unless the list was rebuilt.
if (!rebuilt_filament_list && !ams_changed && is_dry_ctr_idle()) {
return 0;
}
if (m_tray_ids.empty()) {
return 0;
}
// Select recommended drying temperature and default filament
float min_dry_temp = std::numeric_limits<float>::max();
std::string default_filament_id = "GFA00";
bool has_ready = false;
const auto fallback_preset = DevUtilBackend::GetFilamentDryingPreset("GFA00");
for (const auto& tray_pair : dev_ams->GetTrays()) {
if (!tray_pair.second || !tray_pair.second->is_tray_info_ready()) continue;
has_ready = true;
wxString filament_type = tray_pair.second->get_display_filament_type();
Slic3r::DevFilamentDryingPreset preset;
if (filament_type.IsEmpty()) {
// no filament type, is PLA
if (!fallback_preset) continue;
preset = fallback_preset.value();
} else {
auto preset_opt = tray_pair.second->get_ams_drying_preset();
if (preset_opt.has_value()) {
preset = preset_opt.value();
} else {
// no presetis PLA
if (!fallback_preset) continue;
preset = fallback_preset.value();
}
}
float cur_temp = m_printer_status.m_is_printing
? std::min({preset.filament_dev_ams_drying_temperature_on_print[dev_ams->GetAmsType()],
preset.filament_dev_drying_softening_temperature, preset.filament_dev_ams_drying_heat_distortion_temperature})
: preset.filament_dev_ams_drying_temperature_on_idle[dev_ams->GetAmsType()];
if (cur_temp < min_dry_temp) {
min_dry_temp = cur_temp;
default_filament_id = preset.filament_id;
}
}
// if no tray is ready, use PLA as default
if (!has_ready) {
if (fallback_preset.has_value()) {
Slic3r::DevFilamentDryingPreset preset = fallback_preset.value();
float cur_temp = m_printer_status.m_is_printing
? std::min({preset.filament_dev_ams_drying_temperature_on_print[dev_ams->GetAmsType()],
preset.filament_dev_drying_softening_temperature, preset.filament_dev_ams_drying_heat_distortion_temperature})
: preset.filament_dev_ams_drying_temperature_on_idle[dev_ams->GetAmsType()];
min_dry_temp = cur_temp;
default_filament_id = preset.filament_id;
}
}
m_ams_info.m_recommand_dry_temp = min_dry_temp;
// Set default selection
unsigned int default_index = 0;
for (unsigned int i = 0; i < m_tray_ids.size(); i++) {
if (m_tray_ids[i].filament_id == default_filament_id) {
default_index = i;
break;
}
}
m_trays_combo->SetSelection(default_index);
wxCommandEvent evt(wxEVT_COMBOBOX, m_trays_combo->GetId());
evt.SetInt(default_index);
OnFilamentSelectionChanged(evt);
return 1;
}
std::shared_ptr<DevFilaSystem> AMSDryCtrWin::get_fila_system() const
{
std::shared_ptr<DevFilaSystem> fila_system = m_fila_system.lock();
if (!fila_system) {
const_cast<AMSDryCtrWin*>(this)->Close();
}
return fila_system;
}
void AMSDryCtrWin::update_printer_state(MachineObject* obj)
{
m_printer_status.m_is_printing = obj->is_in_printing()
&& obj->GetExtderSystem()->GetCurrentAmsId() == m_ams_info.m_ams_id;
}
void AMSDryCtrWin::update(std::shared_ptr<DevFilaSystem> fila_system, MachineObject* obj)
{
if (!fila_system || !obj) {
return;
}
update_printer_state(obj);
m_fila_system = fila_system;
DevAms* dev_ams = fila_system->GetAmsById(m_ams_info.m_ams_id);
if (!dev_ams) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::update: Invalid AMS id";
m_ams_info.m_ams_id = "";
Close();
return;
}
if (!dev_ams->IsSupportRemoteDry(fila_system->GetOwner())) {
BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::update: Selected AMS does not support remote drying";
Close();
return;
}
update_ams_change(dev_ams);
update_image(dev_ams->GetAmsType(),
dev_ams->GetDryStatus().has_value()? dev_ams->GetDryStatus().value(): DevAms::DryStatus::Off,
dev_ams->GetDrySubStatus().has_value()? dev_ams->GetDrySubStatus().value(): DevAms::DrySubStatus::Off,
dev_ams->GetHumidityPercent());
update_state(dev_ams);
update_dryness_status(dev_ams);
update_filament_list(dev_ams, obj);
update_filament_guide_info(dev_ams);
m_is_ams_changed = false;
check_values_changed(dev_ams);
Layout();
Refresh();
}
bool AMSDryCtrWin::is_ams_changed(DevAms* dev_ams)
{
return m_ams_info.m_ams_id != dev_ams->GetAmsId() || m_is_ams_changed;
}
bool AMSDryCtrWin::is_dry_status_changed(DevAms* dev_ams)
{
if (!dev_ams->GetDryStatus().has_value() || !dev_ams->GetDrySubStatus().has_value()) {
return true;
}
return dev_ams->GetDryStatus().value() != m_ams_info.m_dry_status
|| dev_ams->GetDrySubStatus().value() != m_ams_info.m_dry_sub_status;
}
bool AMSDryCtrWin::is_dry_ctr_idle(DevAms* dev_ams)
{
if (!dev_ams->GetDryStatus().has_value() || !dev_ams->GetDrySubStatus().has_value()) {
return true;
}
return dev_ams->GetDryStatus().value() == DevAms::DryStatus::Off
|| dev_ams->GetDryStatus().value() == DevAms::DryStatus::Cooling;
}
bool AMSDryCtrWin::is_dry_ctr_idle()
{
return m_ams_info.m_dry_status == DevAms::DryStatus::Off || m_ams_info.m_dry_status == DevAms::DryStatus::Cooling;
}
bool AMSDryCtrWin::is_dry_ctr_err(DevAms* dev_ams)
{
if (!dev_ams->GetDryStatus().has_value() || !dev_ams->GetDrySubStatus().has_value()) {
return false;
}
return dev_ams->GetDryStatus().value() == DevAms::DryStatus::Error;
}
} // GUI
} // Slic3r