From d4fabbf7c8020e5e633a3bd386d8cdaafd3e191a Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Thu, 15 Oct 2026 19:43:07 +0800 Subject: [PATCH] Fix AMS gradient rendering, color presets and printer writeback (#16252) * Fix external spool gradient and multicolor rendering * Display AMS gradients and multicolors in color popup * Preserve AMS gradient selection through color popup * Match popup selections by full filament color data * Align AMS Lite gradient geometry with BambuStudio * Align AMS color palette with BambuStudio presets * Match Bambu circular color rendering without background masks * Preserve AMS gradient colors when applying filament settings * Match AMS preview gradient swatches to the solid color shape --- src/slic3r/GUI/AMSMaterialsSetting.cpp | 332 ++++++++++++++---- src/slic3r/GUI/AMSMaterialsSetting.hpp | 23 +- src/slic3r/GUI/DeviceManager.cpp | 8 +- src/slic3r/GUI/DeviceManager.hpp | 3 +- src/slic3r/GUI/EncodedFilament.cpp | 2 +- src/slic3r/GUI/EncodedFilament.hpp | 11 + src/slic3r/GUI/Widgets/AMSItem.cpp | 91 +++-- .../test_device_manager_integration.cpp | 76 ++++ 8 files changed, 434 insertions(+), 112 deletions(-) diff --git a/src/slic3r/GUI/AMSMaterialsSetting.cpp b/src/slic3r/GUI/AMSMaterialsSetting.cpp index 87b00c940d..f723ef13d3 100644 --- a/src/slic3r/GUI/AMSMaterialsSetting.cpp +++ b/src/slic3r/GUI/AMSMaterialsSetting.cpp @@ -85,6 +85,96 @@ static std::string float_to_string_with_precision(float value, int precision = 3 return stream.str(); } +static std::string colour_to_ams_string(const wxColour& color) +{ + char col_buf[10]; + sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha()); + return col_buf; +} + +static wxColour mix_colour(const wxColour& left, const wxColour& right, double ratio) +{ + ratio = std::max(0.0, std::min(1.0, ratio)); + auto mix_channel = [ratio](unsigned char l, unsigned char r) { + return static_cast(std::round(l + (r - l) * ratio)); + }; + return wxColour(mix_channel(left.Red(), right.Red()), + mix_channel(left.Green(), right.Green()), + mix_channel(left.Blue(), right.Blue()), + mix_channel(left.Alpha(), right.Alpha())); +} + +static std::vector collect_ams_color_items(DevFilaSystem* fila_system) +{ + std::vector items; + if (!fila_system) return items; + + for (const auto& ams_pair : fila_system->GetAmsList()) { + DevAms* ams = ams_pair.second; + if (!ams) continue; + + for (const auto& tray_pair : ams->GetTrays()) { + DevAmsTray* tray = tray_pair.second; + if (!tray || !tray->is_tray_info_ready()) continue; + + ColorPickerPopup::ColorItem item; + for (const std::string& col : tray->cols) { + item.colors.emplace_back(DevAmsTray::decode_color(col)); + } + if (item.colors.empty() && !tray->color.empty()) { + item.colors.emplace_back(DevAmsTray::decode_color(tray->color)); + } + if (item.colors.empty()) continue; + + item.ctype = item.colors.size() > 1 ? tray->ctype : 2; + items.emplace_back(std::move(item)); + } + } + + return items; +} + +static std::vector collect_preset_color_items(const std::string& filament_id) +{ + std::vector items; + if (filament_id.empty()) return items; + + auto* clr_query = GUI::wxGetApp().get_filament_color_code_query(); + if (!clr_query) return items; + + auto* agent = wxGetApp().getAgent(); + const std::string catalog_id = agent ? agent->from_orca_filament_id(filament_id) : filament_id; + FilamentColorCodes* color_codes = clr_query->GetFilaInfoMap(wxString::FromUTF8(catalog_id)); + if (!color_codes || !color_codes->GetFilamentColor2CodeMap()) return items; + + std::set seen; + for (const auto& color_pair : *color_codes->GetFilamentColor2CodeMap()) { + const FilamentColor& fila_color = color_pair.first; + FilamentColorCode* color_code = color_pair.second; + if (!color_code || fila_color.GetColors().empty()) continue; + + ColorPickerPopup::ColorItem item; + item.ctype = fila_color.ColorCount() < 2 ? 2 : + (fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR ? 0 : 1); + item.name = color_code->GetFilaColorName(); + + std::string key = std::to_string(item.ctype); + for (const wxColour& color : fila_color.GetColors()) { + item.colors.emplace_back(color); + char rgba[9]; + std::snprintf(rgba, sizeof(rgba), "%02X%02X%02X%02X", + static_cast(color.Red()), static_cast(color.Green()), + static_cast(color.Blue()), static_cast(color.Alpha())); + key += "|" + std::string(rgba); + } + + if (item.colors.empty() || !seen.insert(key).second) continue; + items.emplace_back(std::move(item)); + } + + return items; +} + AMSMaterialsSetting::AMSMaterialsSetting(wxWindow *parent, wxWindowID id) : DPIDialog(parent, id, _L("AMS Materials Setting"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) , m_color_picker_popup(ColorPickerPopup(this)) @@ -740,8 +830,19 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event) nozzle_temp_min.ToLong(&nozzle_temp_min_int); nozzle_temp_max.ToLong(&nozzle_temp_max_int); wxColour color = m_clr_picker->m_colour; - char col_buf[10]; - sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha()); + std::string tray_color = colour_to_ams_string(color); + std::vector tray_colors; + int tray_ctype = 2; + // Only firmware advertising fun2[23] receives the manual multi-color fields. + if (obj->is_support_filament_manual_multi_color) { + for (const wxColour& selected_color : m_clr_picker->m_cols) { + tray_colors.emplace_back(colour_to_ams_string(selected_color)); + } + if (tray_colors.empty()) { + tray_colors.emplace_back(tray_color); + } + tray_ctype = tray_colors.size() > 1 ? m_clr_picker->ctype : 2; + } if (ams_filament_id.empty() || nozzle_temp_min.empty() || nozzle_temp_max.empty() || m_filament_type.empty()) { BOOST_LOG_TRIVIAL(trace) << "Invalid Setting id"; @@ -760,13 +861,33 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event) << ", tray_info_idx (filament_id) = " << ams_filament_id << ", setting_id = " << ams_setting_id << ", tray_type = " << m_filament_type - << ", tray_color = " << col_buf + << ", tray_color = " << tray_color << ", nozzle_temp_min = " << nozzle_temp_min_int << ", nozzle_temp_max = " << nozzle_temp_max_int; // set filament if (m_is_third) { - obj->command_ams_filament_settings(ams_id, slot_id, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int); + obj->command_ams_filament_settings(ams_id, slot_id, ams_filament_id, ams_setting_id, tray_color, m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int, + tray_colors, tray_ctype); + + // Refresh the edited tray immediately while its MQTT acknowledgement is pending. + DevAmsTray* tray = nullptr; + if (is_virtual_tray()) { + for (auto& virtual_tray : obj->vt_slot) { + if (virtual_tray.id == std::to_string(ams_id)) { + tray = &virtual_tray; + break; + } + } + } else { + tray = obj->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id)); + } + if (tray) { + tray->color = tray_color; + tray->cols = tray_colors; + tray->ctype = tray_ctype; + tray->set_hold_count(); + } } //reset param @@ -923,8 +1044,15 @@ void AMSMaterialsSetting::set_ctype(int ctype) void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event) { - unsigned int color_num = event.GetInt(); - set_color(wxColour(color_num>>24&0xFF, color_num>>16&0xFF, color_num>>8&0xFF, color_num&0xFF)); + std::vector colors = m_color_picker_popup.get_selected_colours(); + if (colors.empty()) { + unsigned int color_num = event.GetInt(); + colors.emplace_back(color_num >> 24 & 0xFF, color_num >> 16 & 0xFF, color_num >> 8 & 0xFF, color_num & 0xFF); + } + + set_ctype(m_color_picker_popup.get_selected_ctype()); + set_color(colors.front()); + set_colors(colors); } void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event) @@ -939,14 +1067,12 @@ void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event) } } - std::vector ams_colors; - obj->GetFilaSystem()->CollectAmsColors(ams_colors); - + m_color_picker_popup.set_ams_colours(collect_ams_color_items(obj->GetFilaSystem().get())); + m_color_picker_popup.set_preset_colours(collect_preset_color_items(ams_filament_id)); + m_color_picker_popup.set_def_colour(m_clr_picker->m_colour, m_clr_picker->m_cols, m_clr_picker->ctype); wxPoint img_pos = m_clr_picker->ClientToScreen(wxPoint(0, 0)); wxPoint popup_pos(img_pos.x - m_color_picker_popup.GetSize().x - FromDIP(95), img_pos.y - FromDIP(65)); m_color_picker_popup.Position(popup_pos, wxSize(0, 0)); - m_color_picker_popup.set_ams_colours(ams_colors); - m_color_picker_popup.set_def_colour(m_clr_picker->m_colour); m_color_picker_popup.Popup(); } @@ -1735,7 +1861,46 @@ void ColorPicker::doRender(wxDC& dc) auto radius = m_show_full ? size.x / 2 - FromDIP(1) : size.x / 2; if (m_selected) radius -= FromDIP(1); - if (alpha == 0) { + if (m_show_full && m_cols.size() > 1) { + if (ctype == 0) { + const double center_x = size.x / 2.0; + const double center_y = size.y / 2.0; + const double draw_radius = radius; + dc.SetPen(*wxTRANSPARENT_PEN); + for (int x = 0; x < size.x; ++x) { + const double dx = x + 0.5 - center_x; + if (std::abs(dx) > draw_radius) continue; + + const double half_height = std::sqrt(std::max(0.0, draw_radius * draw_radius - dx * dx)); + const int top = std::max(0, static_cast(std::ceil(center_y - half_height))); + const int bottom = std::min(size.y - 1, static_cast(std::floor(center_y + half_height))); + const double pos = size.x > 1 ? static_cast(x) / (size.x - 1) : 0.0; + const double scaled = pos * (m_cols.size() - 1); + const size_t idx = std::min(static_cast(scaled), m_cols.size() - 2); + const double ratio = scaled - idx; + + if (top <= bottom) { + dc.SetBrush(wxBrush(mix_colour(m_cols[idx], m_cols[idx + 1], ratio))); + dc.DrawRectangle(x, top, 1, bottom - top + 1); + } + } + } + else { + float ev_angle = 360.0 / m_cols.size(); + const float overlap = 2.0f; + wxPoint center(size.x / 2, size.y / 2); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(wxBrush(m_cols.front())); + dc.DrawCircle(center.x, center.y, radius); + for (int i = 0; i < m_cols.size(); i++) { + dc.SetBrush(m_cols[i]); + float startAngle = 270.0f + i * ev_angle; + float endAngle = startAngle + ev_angle; + dc.DrawEllipticArc(center.x - radius, center.y - radius, 2 * radius, 2 * radius, startAngle - overlap, endAngle + overlap); + } + } + } + else if (alpha == 0) { wxSize bmp_size = m_bitmap_transparent_def.GetBmpSize(); int center_x = (size.x - bmp_size.x) / 2; int center_y = (size.y - bmp_size.y) / 2; @@ -1777,52 +1942,6 @@ void ColorPicker::doRender(wxDC& dc) dc.SetPen(wxPen(wxColour("#6B6B6B"))); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawCircle(size.x / 2, size.y / 2, radius); - - if (m_cols.size() > 1) { - if (ctype == 0) { - int left = FromDIP(0); - float total_width = size.x; - int gwidth = std::round(total_width / (m_cols.size() - 1)); - - for (int i = 0; i < m_cols.size() - 1; i++) { - - if ((left + gwidth) > (size.x)) { - gwidth = size.x - left; - } - - auto rect = wxRect(left, 0, gwidth, size.y); - dc.GradientFillLinear(rect, m_cols[i], m_cols[i + 1], wxEAST); - left += gwidth; - } - if (wxGetApp().dark_mode()) { - dc.DrawBitmap(m_bitmap_border_dark, wxPoint(0, 0)); - } - else { - dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0)); - } - } - else { - float ev_angle = 360.0 / m_cols.size(); - float startAngle = 270.0; - float endAngle = 270.0; - dc.SetPen(*wxTRANSPARENT_PEN); - for (int i = 0; i < m_cols.size(); i++) { - dc.SetBrush(m_cols[i]); - endAngle += ev_angle; - endAngle = endAngle > 360.0 ? endAngle - 360.0 : endAngle; - wxPoint center(size.x / 2, size.y / 2); - dc.DrawEllipticArc(center.x - radius, center.y - radius, 2 * radius, 2 * radius, startAngle, endAngle); - startAngle += ev_angle; - startAngle = startAngle > 360.0 ? startAngle - 360.0 : startAngle; - } - if (wxGetApp().dark_mode()) { - dc.DrawBitmap(m_bitmap_border_dark, wxPoint(0, 0)); - } - else { - dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0)); - } - } - } } if (m_is_empty) { @@ -1891,19 +2010,21 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent) m_ams_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); //other - wxFlexGridSizer* fg_sizer; - fg_sizer = new wxFlexGridSizer(0, 8, 0, 0); - fg_sizer->SetFlexibleDirection(wxBOTH); - fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + m_other_fg_sizer = new wxFlexGridSizer(0, 8, 0, 0); + m_other_fg_sizer->SetFlexibleDirection(wxBOTH); + m_other_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); for (wxColour col : m_def_colors) { auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize); cp->set_color(col); + cp->set_colors({col}); + cp->ctype = 2; cp->set_selected(false); cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238))); m_color_pickers.push_back(cp); - fg_sizer->Add(cp, 0, wxALL, FromDIP(3)); + m_default_color_pickers.push_back(cp); + m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3)); cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) { set_def_colour(cp->m_colour); @@ -1970,7 +2091,7 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent) m_sizer_box->Add(m_sizer_ams, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10)); m_sizer_box->Add(m_ams_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10)); m_sizer_box->Add(m_sizer_other, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10)); - m_sizer_box->Add(fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10)); + m_sizer_box->Add(m_other_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10)); m_sizer_box->Add(m_sizer_custom, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10)); m_sizer_box->Add(m_custom_cp, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(16)); m_sizer_box->Add(0, 0, 0, wxTOP, FromDIP(10)); @@ -1981,7 +2102,7 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent) m_def_color_box->Fit(); m_sizer_main->Add(m_def_color_box, 0, wxALL | wxEXPAND, 10); - SetSizer(m_sizer_main); + SetSizerAndFit(m_sizer_main); Layout(); Fit(); @@ -2019,13 +2140,15 @@ void ColorPickerPopup::on_custom_clr_picker(wxMouseEvent& /*event*/) wxPostEvent(GetParent(), evt); } -void ColorPickerPopup::set_ams_colours(std::vector ams) +void ColorPickerPopup::set_ams_colours(const std::vector& ams) { + m_ams_fg_sizer->Clear(false); if (m_ams_color_pickers.size() > 0) { for (ColorPicker* col_pick:m_ams_color_pickers) { std::vector::iterator iter = find(m_color_pickers.begin(), m_color_pickers.end(), col_pick); if (iter != m_color_pickers.end()) { + col_pick->Hide(); col_pick->Destroy(); m_color_pickers.erase(iter); } @@ -2035,17 +2158,22 @@ void ColorPickerPopup::set_ams_colours(std::vector ams) } - m_ams_colors = ams; - for (wxColour col : m_ams_colors) { + m_ams_color_items = ams; + for (const ColorItem& item : m_ams_color_items) { + if (item.colors.empty()) continue; + auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize); - cp->set_color(col); + cp->set_color(item.colors.front()); + cp->set_colors(item.colors); + cp->ctype = item.colors.size() > 1 ? item.ctype : 2; + cp->set_show_full(true); cp->set_selected(false); cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238))); m_color_pickers.push_back(cp); m_ams_color_pickers.push_back(cp); m_ams_fg_sizer->Add(cp, 0, wxALL, FromDIP(3)); cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) { - set_def_colour(cp->m_colour); + set_def_colour(cp->m_colour, cp->m_cols, cp->ctype); wxCommandEvent evt(EVT_SELECTED_COLOR); unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 24) + ((cp->m_colour.Green() & 0xff) << 16) + ((cp->m_colour.Blue() & 0xff) << 8) + (cp->m_colour.Alpha() & 0xff); @@ -2058,9 +2186,68 @@ void ColorPickerPopup::set_ams_colours(std::vector ams) Fit(); } -void ColorPickerPopup::set_def_colour(wxColour col) +void ColorPickerPopup::set_preset_colours(const std::vector& preset_colors) +{ + m_other_fg_sizer->Clear(false); + if (!m_preset_color_pickers.empty()) { + for (ColorPicker* col_pick : m_preset_color_pickers) { + auto iter = std::find(m_color_pickers.begin(), m_color_pickers.end(), col_pick); + if (iter != m_color_pickers.end()) { + col_pick->Hide(); + col_pick->Destroy(); + m_color_pickers.erase(iter); + } + } + m_preset_color_pickers.clear(); + } + + for (const ColorItem& item : preset_colors) { + if (item.colors.empty()) continue; + + auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize); + cp->set_color(item.colors.front()); + cp->set_colors(item.colors); + cp->ctype = item.colors.size() > 1 ? item.ctype : 2; + cp->set_show_full(true); + cp->set_selected(false); + cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238))); + if (!item.name.empty()) { + cp->SetToolTip(item.name); + } + + m_color_pickers.push_back(cp); + m_preset_color_pickers.push_back(cp); + cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) { + set_def_colour(cp->m_colour, cp->m_cols, cp->ctype); + + wxCommandEvent evt(EVT_SELECTED_COLOR); + unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 24) + ((cp->m_colour.Green() & 0xff) << 16) + ((cp->m_colour.Blue() & 0xff) << 8) + (cp->m_colour.Alpha() & 0xff); + evt.SetInt(g_col); + wxPostEvent(GetParent(), evt); + }); + } + + const bool show_default_colours = m_preset_color_pickers.empty(); + for (ColorPicker* cp : m_default_color_pickers) { + cp->Show(show_default_colours); + } + for (ColorPicker* cp : (show_default_colours ? m_default_color_pickers : m_preset_color_pickers)) { + m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3)); + } + + m_other_fg_sizer->Layout(); + Layout(); + Fit(); +} + +void ColorPickerPopup::set_def_colour(wxColour col, std::vector cols, int ctype) { m_def_col = col; + if (cols.empty()) { + cols.push_back(col); + } + m_def_cols = cols; + m_def_ctype = m_def_cols.size() > 1 ? ctype : 2; for (ColorPicker* cp : m_color_pickers) { if (cp->m_selected) { @@ -2069,7 +2256,8 @@ void ColorPickerPopup::set_def_colour(wxColour col) } for (ColorPicker* cp : m_color_pickers) { - if (cp->m_colour == m_def_col) { + if (!cp->IsShown()) continue; + if (cp->ctype == m_def_ctype && cp->m_cols == m_def_cols) { cp->set_selected(true); break; } diff --git a/src/slic3r/GUI/AMSMaterialsSetting.hpp b/src/slic3r/GUI/AMSMaterialsSetting.hpp index d895feb551..799b5d800b 100644 --- a/src/slic3r/GUI/AMSMaterialsSetting.hpp +++ b/src/slic3r/GUI/AMSMaterialsSetting.hpp @@ -82,14 +82,22 @@ public: class ColorPickerPopup : public PopupWindow { public: + struct ColorItem + { + std::vector colors; + int ctype = 2; + wxString name; + }; + ScalableBitmap m_ts_bitmap_custom; wxStaticBitmap* m_ts_stbitmap_custom; StaticBox* m_custom_cp; StaticBox* m_def_color_box; wxFlexGridSizer* m_ams_fg_sizer; + wxFlexGridSizer* m_other_fg_sizer; wxColour m_def_col; std::vector m_def_colors; - std::vector m_ams_colors; + std::vector m_ams_color_items; std::vector m_color_pickers; std::vector m_ams_color_pickers; @@ -97,13 +105,20 @@ public: ColorPickerPopup(wxWindow* parent); ~ColorPickerPopup() {}; void on_custom_clr_picker(wxMouseEvent& event); - void set_ams_colours(std::vector ams); - void set_def_colour(wxColour col); + void set_ams_colours(const std::vector& ams); + void set_preset_colours(const std::vector& preset_colors); + void set_def_colour(wxColour col, std::vector cols = {}, int ctype = 2); + const std::vector& get_selected_colours() const { return m_def_cols; } + int get_selected_ctype() const { return m_def_ctype; } void paintEvent(wxPaintEvent& evt); virtual void OnDismiss() wxOVERRIDE; virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE; -public: +private: + std::vector m_default_color_pickers; + std::vector m_preset_color_pickers; + std::vector m_def_cols; + int m_def_ctype = 2; }; diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 7c75ee105a..e5d8395e44 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -1798,7 +1798,8 @@ int MachineObject::command_ams_calibrate(int ams_id) return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer())); } -int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max) +int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, + int nozzle_temp_min, int nozzle_temp_max, const std::vector& tray_colors, int tray_ctype) { int tag_tray_id = 0; int tag_ams_id = ams_id; @@ -1827,6 +1828,10 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s j["print"]["nozzle_temp_min"] = nozzle_temp_min; j["print"]["nozzle_temp_max"] = nozzle_temp_max; j["print"]["tray_type"] = tray_type; + if (!tray_colors.empty()) { + j["print"]["cols"] = tray_colors; + j["print"]["ctype"] = tray_ctype; + } return this->publish_json(j); } @@ -5478,6 +5483,7 @@ void MachineObject::parse_new_info(json print) 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; + is_support_filament_manual_multi_color = (get_flag_bits_no_border(fun2, 23) == 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; diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 75bc545f64..46c0b711dd 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -674,6 +674,7 @@ public: bool is_support_remote_dry = false; bool is_support_check_track_switch_match_slice_printer{false}; bool is_support_pa_mode{false}; + bool is_support_filament_manual_multi_color{false}; std::optional m_firmware_support_print_tpu_left; bool installed_upgrade_kit{false}; @@ -804,7 +805,7 @@ public: int command_ams_switch_filament(bool switch_filament); int command_ams_air_print_detect(bool air_print_detect); int command_ams_calibrate(int ams_id); - int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max); + int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max, const std::vector& tray_colors = {}, int tray_ctype = 2); int command_ams_select_tray(std::string tray_id); int command_ams_refresh_rfid(int ams_id, int slot_id); int command_ams_control(std::string action); diff --git a/src/slic3r/GUI/EncodedFilament.cpp b/src/slic3r/GUI/EncodedFilament.cpp index cc9b2a3ae3..413c0877c9 100644 --- a/src/slic3r/GUI/EncodedFilament.cpp +++ b/src/slic3r/GUI/EncodedFilament.cpp @@ -96,7 +96,7 @@ void FilamentColorCodeQuery::LoadFromLocal() const auto& fila_color_strs = json_data_item["fila_color"].get>(); for (const auto& color_str : fila_color_strs) { if (color_str.size() > 3) /* Skip the value like "#0"*/{ - fila_color.m_colors.emplace(wxColour(color_str)); + fila_color.AddColor(wxColour(color_str)); } } } diff --git a/src/slic3r/GUI/EncodedFilament.hpp b/src/slic3r/GUI/EncodedFilament.hpp index 8e3542b1e4..3f1660464b 100644 --- a/src/slic3r/GUI/EncodedFilament.hpp +++ b/src/slic3r/GUI/EncodedFilament.hpp @@ -87,6 +87,17 @@ struct FilamentColor public: size_t ColorCount() const noexcept { return m_colors.size(); } + // Preserve catalog endpoint order for display while retaining set-based lookup identity. + const std::vector& GetColors() const noexcept { return m_color_list; } + void AddColor(const wxColour& color) + { + if (m_colors.insert(color).second) m_color_list.push_back(color); + } + +private: + std::vector m_color_list; + +public: void EndSet(int ctype) { diff --git a/src/slic3r/GUI/Widgets/AMSItem.cpp b/src/slic3r/GUI/Widgets/AMSItem.cpp index 95418f79a2..c1938d06dc 100644 --- a/src/slic3r/GUI/Widgets/AMSItem.cpp +++ b/src/slic3r/GUI/Widgets/AMSItem.cpp @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include #include @@ -1409,22 +1411,17 @@ void AMSLib::render_lite_lib(wxDC& dc) dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); if (m_info.material_cols.size() > 1) { - int left = FromDIP(10); - int gwidth = std::round(libsize.x / (m_info.material_cols.size() - 1)); - //gradient + wxRect color_rect(FromDIP(10), FromDIP(10), libsize.x - FromDIP(18), libsize.y - FromDIP(18)); if (m_info.ctype == 0) { - for (int i = 0; i < m_info.material_cols.size() - 1; i++) { - auto rect = wxRect(left, FromDIP(10), libsize.x - FromDIP(18), libsize.y - FromDIP(18)); - fill_gradient_rect_east(dc, rect, m_info.material_cols[i], m_info.material_cols[i + 1]); - left += gwidth; - } + fill_gradient_rect_east(dc, color_rect, m_info.material_cols.front(), m_info.material_cols.back()); } else { int cols_size = m_info.material_cols.size(); for (int i = 0; i < cols_size; i++) { dc.SetBrush(wxBrush(m_info.material_cols[i])); - float x = FromDIP(10) + ((float)libsize.x - FromDIP(18)) * i / cols_size; - dc.DrawRoundedRectangle(x, FromDIP(10), ((float)libsize.x - FromDIP(17)) / cols_size, libsize.y - FromDIP(20), 0); + int x = color_rect.x + color_rect.width * i / cols_size; + int next_x = color_rect.x + color_rect.width * (i + 1) / cols_size; + dc.DrawRectangle(x, color_rect.y, next_x - x, color_rect.height); } dc.SetBrush(wxBrush(tmp_lib_colour)); } @@ -1516,7 +1513,27 @@ void AMSLib::render_generic_lib(wxDC &dc) int top = height - curr_height; if (m_ams_model == EXT_AMS){ - dc.DrawRoundedRectangle(FromDIP(1), FromDIP(1), size.x - FromDIP(2), size.y - FromDIP(1), m_radius - 1); + wxRect color_rect(FromDIP(1), FromDIP(1), size.x - FromDIP(2), size.y - FromDIP(1)); + if ((m_info.ctype == 0 || m_info.ctype == 1) && m_info.material_cols.size() > 1 && alpha != 0) { + if (m_info.ctype == 0) { + fill_gradient_rect_east(dc, color_rect, m_info.material_cols.front(), m_info.material_cols.back()); + } + else { + int cols_size = static_cast(m_info.material_cols.size()); + for (int i = 0; i < cols_size; i++) { + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.SetBrush(wxBrush(m_info.material_cols[i])); + int x = color_rect.x + color_rect.width * i / cols_size; + int next_x = color_rect.x + color_rect.width * (i + 1) / cols_size; + dc.DrawRectangle(x, color_rect.y, next_x - x, color_rect.height); + } + } + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.SetBrush(wxBrush(tmp_lib_colour)); + } + else { + dc.DrawRoundedRectangle(color_rect.x, color_rect.y, color_rect.width, color_rect.height, m_radius - 1); + } if (alpha == 0) { dc.DrawBitmap(m_bitmap_transparent_def.bmp(), FromDIP(2), FromDIP(2)); } @@ -2844,30 +2861,38 @@ void AMSPreview::doRender(wxDC &dc) //dc.DrawRoundedRectangle((size.x - rec_size.x) / 2, (size.y - rec_size.y) / 2, rec_size.x, rec_size.y, FromDIP(2)); if (iter.material_cols.size() > 1) { - int fleft = (size.x - AMS_ITEM_CUBE_SIZE.x) / 2; - - float total_width = AMS_ITEM_CUBE_SIZE.x; - int gwidth = (total_width / (iter.material_cols.size())); - if (iter.ctype == 0) { - for (int i = 0; i < iter.material_cols.size() - 1; i++) { - - if ((fleft + gwidth) > (AMS_ITEM_CUBE_SIZE.x)) { - gwidth = (fleft + AMS_ITEM_CUBE_SIZE.x) - fleft; - } - - auto rect = wxRect(fleft, (size.y - AMS_ITEM_CUBE_SIZE.y) / 2, gwidth, AMS_ITEM_CUBE_SIZE.y); - fill_gradient_rect_east(dc, rect, iter.material_cols[i], iter.material_cols[i + 1]); - fleft += gwidth; - } + const wxSize swatch_size = m_ams_item_type == AMSModel::N3S_AMS ? AMS_ITEM_CUBE_SIZE : AMS_ITEM_CUBE_SIZE2; + const wxRect rect((size.x - swatch_size.x) / 2, (size.y - swatch_size.y) / 2, swatch_size.x, swatch_size.y); + const int radius = m_ams_item_type == AMSModel::N3S_AMS ? 0 : FromDIP(3); + std::unique_ptr owned_gc; + wxGraphicsContext* gc = dc.GetGraphicsContext(); + if (!gc) { + owned_gc.reset(wxGraphicsContext::CreateFromUnknownDC(dc)); + gc = owned_gc.get(); } - else { - int cols_size = iter.material_cols.size(); - for (int i = 0; i < cols_size; i++) { - dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); - dc.SetBrush(wxBrush(iter.material_cols[i])); - float x = (size.x - AMS_ITEM_CUBE_SIZE.x) / 2 + total_width * i / cols_size; - dc.DrawRectangle(x, (size.y - AMS_ITEM_CUBE_SIZE.y) / 2, total_width / cols_size, AMS_ITEM_CUBE_SIZE.y); + if (gc) { + wxGraphicsGradientStops stops(iter.material_cols.front(), iter.material_cols.back()); + const size_t count = iter.material_cols.size(); + if (iter.ctype == 0) { + for (size_t i = 1; i + 1 < count; ++i) + stops.Add(iter.material_cols[i], static_cast(i) / (count - 1)); + } else { + // Two stops at each boundary keep multicolour bands distinct. + for (size_t i = 1; i < count; ++i) { + const float position = static_cast(i) / count; + stops.Add(iter.material_cols[i - 1], position); + stops.Add(iter.material_cols[i], position); + } } + gc->PushState(); + gc->SetPen(*wxTRANSPARENT_PEN); + gc->SetBrush(gc->CreateLinearGradientBrush(rect.x, rect.y, rect.x + rect.width, rect.y, stops)); + gc->DrawRoundedRectangle(rect.x, rect.y, rect.width, rect.height, radius); + gc->PopState(); + } else { + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(iter.material_colour); + dc.DrawRoundedRectangle(rect, radius); } } else { diff --git a/tests/slic3rutils/test_device_manager_integration.cpp b/tests/slic3rutils/test_device_manager_integration.cpp index 7e58dc7696..dc3c492803 100644 --- a/tests/slic3rutils/test_device_manager_integration.cpp +++ b/tests/slic3rutils/test_device_manager_integration.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include using namespace Slic3r; using json = nlohmann::json; @@ -49,6 +51,21 @@ public: AgentInfo get_agent_info() override { return m_info; } + int send_message(std::string dev_id, std::string json_str, int, int) override + { + published_device = std::move(dev_id); + published_message = json::parse(json_str); + return 0; + } + + int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override + { + return send_message(std::move(dev_id), std::move(json_str), qos, flag); + } + + std::string published_device; + json published_message; + private: AgentInfo m_info; }; @@ -160,3 +177,62 @@ TEST_CASE("Device manager filters and rehomes devices by printer-agent ownership CHECK(manager.get_my_machine_list("integration-agent-a").empty()); CHECK(manager.get_my_machine_list("integration-agent-b").count(machine.dev_id) == 1); } + +TEST_CASE("AMS filament settings keep legacy single-color commands compatible", "[DeviceManager][AMSMaterialsSetting][integration]") +{ + ScopedAppConfig app_config; + auto agent = std::make_shared("integration-agent"); + NetworkAgent network(nullptr, agent); + DeviceManager manager(&network, false, &app_config.config); + MachineObject object(&manager, &network, "Test printer", "integration-device", "192.0.2.10"); + object.dev_connection_type = GENERATE("lan", "cloud"); + + REQUIRE(object.command_ams_filament_settings(1, 2, "filament-id", "setting-id", "FF000080", "PLA", 190, 220) == 0); + + const json& payload = agent->published_message.at("print"); + CHECK(agent->published_device == object.get_dev_id()); + CHECK(payload.at("command") == "ams_filament_setting"); + CHECK(payload.at("ams_id") == 1); + CHECK(payload.at("slot_id") == 2); + CHECK(payload.at("tray_color") == "FF000080"); + CHECK_FALSE(payload.contains("cols")); + CHECK_FALSE(payload.contains("ctype")); +} + +TEST_CASE("AMS filament settings preserve ordered gradient and multicolor RGBA components", "[DeviceManager][AMSMaterialsSetting][integration]") +{ + ScopedAppConfig app_config; + auto agent = std::make_shared("integration-agent"); + NetworkAgent network(nullptr, agent); + DeviceManager manager(&network, false, &app_config.config); + MachineObject object(&manager, &network, "Test printer", "integration-device", "192.0.2.10"); + object.dev_connection_type = GENERATE("lan", "cloud"); + const int color_type = GENERATE(0, 1); + const std::vector colors{"FF000080", "0000FF40", "00FF00FF"}; + + REQUIRE(object.command_ams_filament_settings(1, 2, "filament-id", "setting-id", colors.front(), "PLA", 190, 220, + colors, color_type) == 0); + + const json& payload = agent->published_message.at("print"); + CHECK(payload.at("tray_color") == colors.front()); + CHECK(payload.at("cols").get>() == colors); + CHECK(payload.at("ctype") == color_type); +} + +TEST_CASE("AMS filament settings default a supplied single-color list to type two", "[DeviceManager][AMSMaterialsSetting][integration]") +{ + ScopedAppConfig app_config; + auto agent = std::make_shared("integration-agent"); + NetworkAgent network(nullptr, agent); + DeviceManager manager(&network, false, &app_config.config); + MachineObject object(&manager, &network, "Test printer", "integration-device", "192.0.2.10"); + object.dev_connection_type = GENERATE("lan", "cloud"); + const std::vector colors{"FF000080"}; + + REQUIRE(object.command_ams_filament_settings(1, 2, "filament-id", "setting-id", colors.front(), "PLA", 190, 220, + colors) == 0); + + const json& payload = agent->published_message.at("print"); + CHECK(payload.at("cols").get>() == colors); + CHECK(payload.at("ctype") == 2); +}