diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index ef80fc1a73..750918b5ad 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -683,8 +683,11 @@ std::string AppConfig::load() m_filament_presets = iter.value().get>(); } else if (iter.key() == "filament_colors") { m_filament_colors = iter.value().get>(); - } - else { + } else if(iter.key() == "filament_multi_colors") { + m_filament_multi_colors = iter.value().get>(); + } else if(iter.key() == "filament_color_types") { + m_filament_color_types = iter.value().get>(); + } else { if (iter.value().is_string()) m_storage[it.key()][iter.key()] = iter.value().get(); else { @@ -768,6 +771,13 @@ void AppConfig::save() for (const auto &filament_color : m_filament_colors) { j["app"]["filament_colors"].push_back(filament_color); } + for (const auto &filament_multi_color : m_filament_multi_colors) { + j["app"]["filament_multi_colors"].push_back(filament_multi_color); + } + + for (const auto &filament_color_type : m_filament_color_types) { + j["app"]["filament_color_types"].push_back(filament_color_type); + } for (const auto &cali_info : m_printer_cali_infos) { json cali_json; @@ -803,7 +813,7 @@ void AppConfig::save() } else if (category.first == "presets") { json j_filament_array; for(const auto& kvp : category.second) { - if (boost::starts_with(kvp.first, "filament") && kvp.first != "filament_colors") { + if (boost::starts_with(kvp.first, "filament") && kvp.first != "filament_colors" && kvp.first != "filament_multi_colors" && kvp.first != "filament_color_types") { j_filament_array.push_back(kvp.second); } else { j[category.first][kvp.first] = kvp.second; diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 6889dcdecd..9307877552 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -383,6 +383,8 @@ private: std::vector m_filament_presets; std::vector m_filament_colors; + std::vector m_filament_multi_colors; + std::vector m_filament_color_types; std::vector m_printer_cali_infos; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 39afb9386c..b4d56c2b94 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -39,6 +39,8 @@ static std::vector s_project_options { "flush_volumes_matrix", // BBS "filament_colour", + "filament_colour_type", + "filament_multi_colour", "wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle", @@ -1787,6 +1789,8 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p break; this->filament_presets.emplace_back(remove_ini_suffix(f_name)); } + + // Load data from AppConfig to ProjectConfig when Studio is initialized. std::vector filament_colors; auto f_colors = config.get_printer_setting(initial_printer_profile_name, "filament_colors"); if (!f_colors.empty()) { @@ -1795,6 +1799,20 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p filament_colors.resize(filament_presets.size(), "#26A69A"); project_config.option("filament_colour")->values = filament_colors; + std::vector multi_filament_colors; + if (config.has("presets", "filament_multi_colors")) { + boost::algorithm::split(multi_filament_colors, config.get("presets", "filament_multi_colors"), boost::algorithm::is_any_of(",")); + } + if (multi_filament_colors.size() == 0) project_config.option("filament_multi_colour")->values = filament_colors; + else project_config.option("filament_multi_colour")->values = multi_filament_colors; + + std::vector filament_color_types; + if (config.has("presets", "filament_color_types")) { + boost::algorithm::split(filament_color_types, config.get("presets", "filament_color_types"), boost::algorithm::is_any_of(",")); + } + filament_color_types.resize(filament_presets.size(), "1"); + project_config.option("filament_colour_type")->values = filament_color_types; + std::vector filament_maps(filament_colors.size(), 1); project_config.option("filament_map")->values = filament_maps; @@ -1884,7 +1902,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p } // Export selections (current print, current filaments, current printer) into config.ini -//BBS: change directories by design +// BBS: change directories by design void PresetBundle::export_selections(AppConfig &config) { assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1); @@ -1904,10 +1922,19 @@ void PresetBundle::export_selections(AppConfig &config) sprintf(name, "filament_%02u", i); config.set_printer_setting(printer_name, name, filament_presets[i]); } + // Load project config data into app config CNumericLocalesSetter locales_setter; std::string filament_colors = boost::algorithm::join(project_config.option("filament_colour")->values, ","); config.set_printer_setting(printer_name, "filament_colors", filament_colors); + // Load filament multi color data into app config + std::string filament_multi_colors = boost::algorithm::join(project_config.option("filament_multi_colour")->values, ","); + config.set("presets", "filament_multi_colors", filament_multi_colors); + + // Load filament color type data into app config + std::string filament_color_types = boost::algorithm::join(project_config.option("filament_colour_type")->values, ","); + config.set("presets", "filament_color_types", filament_color_types); + std::string flush_volumes_matrix = boost::algorithm::join(project_config.option("flush_volumes_matrix")->values | boost::adaptors::transformed(static_cast(std::to_string)), "|"); @@ -1960,10 +1987,13 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) else { filament_presets.resize(n); } - ConfigOptionStrings* filament_color = project_config.option("filament_colour"); + ConfigOptionStrings *filament_multi_color = project_config.option("filament_multi_colour"); + ConfigOptionStrings* filament_color_type = project_config.option("filament_colour_type"); ConfigOptionInts* filament_map = project_config.option("filament_map"); filament_color->resize(n); + filament_multi_color->resize(n); + filament_color_type->resize(n); filament_map->values.resize(n, 1); ams_multi_color_filment.resize(n); @@ -1972,6 +2002,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) if (!new_color.empty()) { for (int i = old_filament_count; i < n; i++) { filament_color->values[i] = new_color; + filament_multi_color->values[i] = new_color; + filament_color_type->values[i] = "1"; // default color type } } } @@ -2000,8 +2032,9 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id) } ConfigOptionStrings *filament_color = project_config.option("filament_colour"); + ConfigOptionStrings *filament_multi_color = project_config.option("filament_multi_colour"); + ConfigOptionStrings *filament_color_type = project_config.option("filament_colour_type"); ConfigOptionInts* filament_map = project_config.option("filament_map"); - if (filament_color->values.size() > to_del_flament_id) { filament_color->values.erase(filament_color->values.begin() + to_del_flament_id); if (filament_map->values.size() > to_del_flament_id) { @@ -2013,12 +2046,18 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id) filament_map->values.resize(to_del_flament_id, 1); } - if (ams_multi_color_filment.size() > to_del_flament_id){ - ams_multi_color_filment.erase(ams_multi_color_filment.begin() + to_del_flament_id); - } - else { - ams_multi_color_filment.resize(to_del_flament_id); - } + // lambda function to erase or resize the container + auto erase_or_resize = [to_del_flament_id](auto& container) { + if (container.size() > to_del_flament_id) { + container.erase(container.begin() + to_del_flament_id); + } else { + container.resize(to_del_flament_id); + } + }; + + erase_or_resize(filament_multi_color->values); + erase_or_resize(filament_color_type->values); + erase_or_resize(ams_multi_color_filment); update_multi_material_filament_presets(to_del_flament_id); } @@ -2033,7 +2072,7 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info) auto filament_color = ams.opt_string("filament_colour", 0u); auto ams_name = ams.opt_string("tray_name", 0u); auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed"); - auto filament_multi_color = ams.opt("filament_multi_colors")->values; + auto filament_multi_color = ams.opt("filament_multi_colour")->values; if (filament_id.empty()) { continue; } @@ -2081,6 +2120,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector ams_filament_presets; std::vector ams_filament_colors; + std::vector ams_filament_color_types; std::vector ams_array_maps; ams_multi_color_filment.clear(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_ams_list size: %1%") % filament_ams_list.size(); @@ -2089,6 +2129,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector mutli_filament_color; }; @@ -2099,8 +2140,9 @@ unsigned int PresetBundle::sync_ams_list(std::vector("filament_multi_colors")->values; + auto filament_multi_color = ams.opt("filament_multi_colour")->values; auto ams_id = ams.opt_string("ams_id", 0u); auto slot_id = ams.opt_string("slot_id", 0u); ams_infos.push_back({filament_id.empty() ? false : true,false, filament_color}); @@ -2117,6 +2159,7 @@ unsigned int PresetBundle::sync_ams_list(std::vectorfilament_presets.size() > ams_filament_presets.size()) { ams_filament_presets.push_back(this->filament_presets[ams_filament_presets.size()]); ams_filament_colors.push_back(filament_color); + ams_filament_color_types.push_back(filament_color_type); ams_multi_color_filment.push_back(filament_multi_color); continue; } @@ -2149,6 +2193,7 @@ unsigned int PresetBundle::sync_ams_list(std::vectorfilament_presets.size()) { ams_filament_presets.push_back(this->filament_presets[ams_filament_presets.size()]); ams_filament_colors.push_back(filament_color); + ams_filament_color_types.push_back(filament_color_type); ams_multi_color_filment.push_back(filament_multi_color); unknowns.emplace_back(&ams, has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") : L("The filament model is unknown. Still using the previous filament preset.")); @@ -2169,12 +2214,14 @@ unsigned int PresetBundle::sync_ams_list(std::vectorname); ams_filament_colors.push_back(filament_color); + ams_filament_color_types.push_back(filament_color_type); ams_multi_color_filment.push_back(filament_multi_color); } if (ams_filament_presets.empty()) return 0; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "get filament_colour and from config"; ConfigOptionStrings *filament_color = project_config.option("filament_colour"); + ConfigOptionStrings *filament_color_type = project_config.option("filament_colour_type"); ConfigOptionInts * filament_map = project_config.option("filament_map"); if (use_map) { auto check_has_merge_info = [](std::map &maps, MergeFilamentInfo &merge_info, int exist_colors_size) { @@ -2211,6 +2258,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector need_append_colors; auto exist_colors = filament_color->values; + auto exist_color_types = filament_color_type->values; auto exist_filament_presets = this->filament_presets; std::vector> exist_multi_color_filment; exist_multi_color_filment.resize(exist_colors.size()); @@ -2222,6 +2270,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector= 0 && valid_index < ams_filament_presets.size()) { exist_colors[i] = ams_filament_colors[valid_index]; + exist_color_types[i] = ams_filament_color_types[valid_index]; exist_filament_presets[i] = ams_filament_presets[valid_index]; exist_multi_color_filment[i] = ams_multi_color_filment[valid_index]; } else { @@ -2240,12 +2289,14 @@ unsigned int PresetBundle::sync_ams_list(std::vector(); } } else { ams_filament_colors[i] = ""; + ams_filament_color_types[i] = ""; ams_filament_presets[i] = ""; ams_multi_color_filment[i] = std::vector(); } @@ -2253,6 +2304,8 @@ unsigned int PresetBundle::sync_ams_list(std::vectorresize(exist_colors.size()); filament_color->values = exist_colors; + filament_color_type->resize(exist_colors.size()); + filament_color_type->values = exist_color_types; ams_multi_color_filment = exist_multi_color_filment; this->filament_presets = exist_filament_presets; filament_map->values.resize(exist_filament_presets.size(), 1); @@ -2290,15 +2346,39 @@ unsigned int PresetBundle::sync_ams_list(std::vectorresize(ams_filament_presets.size()); filament_color->values = ams_filament_colors; + filament_color_type->resize(ams_filament_presets.size()); + filament_color_type->values = ams_filament_color_types; this->filament_presets = ams_filament_presets; filament_map->values.resize(ams_filament_colors.size(), 1); } - + // Update ams_multi_color_filment + update_filament_multi_color(); update_multi_material_filament_presets(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "finish sync ams list"; return this->filament_presets.size(); } +void PresetBundle::update_filament_multi_color() +{ + std::vector exsit_multi_colors; + for (auto &fil_item : ams_multi_color_filment){ + if (fil_item.empty()) break; + if (fil_item.size() == 1) + exsit_multi_colors.push_back(fil_item[0]); + else { + std::string colors = ""; + for (auto &color : fil_item){ + colors += color + " "; + } + colors.erase(colors.size() - 1); // remove last space + exsit_multi_colors.push_back(colors); + } + } + ConfigOptionStrings *filament_multi_colour = project_config.option("filament_multi_colour"); + filament_multi_colour->resize(exsit_multi_colors.size()); + filament_multi_colour->values = exsit_multi_colors; +} + std::vector PresetBundle::get_used_tpu_filaments(const std::vector &used_filaments) { std::vector tpu_filaments; diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index d4c4854040..00651b4cd6 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -355,6 +355,8 @@ private: std::pair load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule); // Merge one vendor's presets with the other vendor's presets, report duplicates. std::vector merge_presets(PresetBundle &&other); + // Update the multicolor information for filaments. + void update_filament_multi_color(); // Update renamed_from and alias maps of system profiles. void update_system_maps(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 48677c1d12..6cc0f24ea5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -57,7 +57,7 @@ namespace Slic3r { const std::vector filament_extruder_override_keys = { // floats - "filament_retraction_length", + "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retract_lift_above", @@ -2164,6 +2164,14 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionStrings { "" }); + + def = this->add("filament_multi_colour", coStrings); + def->set_default_value(new ConfigOptionStrings{""}); + + // 0: gradient color, 1: default color(single or multi color) + def = this->add("filament_colour_type", coStrings); + def->set_default_value(new ConfigOptionStrings{"1"}); // Init as default color + //bbs def = this->add("required_nozzle_HRC", coInts); def->label = L("Required nozzle HRC"); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 158656a736..8732966143 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -241,6 +241,10 @@ set(SLIC3R_GUI_SOURCES GUI/ImageGrid.cpp GUI/ImageGrid.h GUI/ImGuiWrapper.cpp + GUI/FilamentPickerDialog.cpp + GUI/FilamentPickerDialog.hpp + GUI/FilamentBitmapUtils.cpp + GUI/FilamentBitmapUtils.hpp GUI/BaseTransparentDPIFrame.cpp GUI/BaseTransparentDPIFrame.hpp GUI/SyncAmsInfoDialog.cpp diff --git a/src/slic3r/GUI/EncodedFilament.hpp b/src/slic3r/GUI/EncodedFilament.hpp index 22a7907835..a554c09f60 100644 --- a/src/slic3r/GUI/EncodedFilament.hpp +++ b/src/slic3r/GUI/EncodedFilament.hpp @@ -161,7 +161,7 @@ private: /* loaded info*/ std::string m_fila_path; - std::unordered_map* m_fila_id2colors_map; // + std::unordered_map* m_fila_id2colors_map; // }; // EncodedFilaColorsInfo class holds a mapping of filament codes to specific filamet type @@ -201,7 +201,7 @@ public: public: wxString GetFilaCode() const { return m_owner->GetFilaCode(); } wxString GetFilaType() const { return m_owner->GetFilaType(); } - + wxString GetFilaColorCode() const { return m_fila_color_code; } // eg. Q01B00 FilamentColor GetFilaColor() const { return m_fila_color; } diff --git a/src/slic3r/GUI/FilamentBitmapUtils.cpp b/src/slic3r/GUI/FilamentBitmapUtils.cpp new file mode 100644 index 0000000000..4f4b9f8586 --- /dev/null +++ b/src/slic3r/GUI/FilamentBitmapUtils.cpp @@ -0,0 +1,184 @@ +#include +#include +#include +#include + +#include "EncodedFilament.hpp" + +namespace Slic3r { namespace GUI { + +// Helper struct to hold bitmap and DC +struct BitmapDC { + wxBitmap bitmap; + wxMemoryDC dc; + + BitmapDC(const wxSize& size) : bitmap(size), dc(bitmap) { + // Don't set white background - let the color patterns fill the entire area + dc.SetPen(*wxTRANSPARENT_PEN); + } +}; + +static BitmapDC init_bitmap_dc(const wxSize& size) { + return BitmapDC(size); +} + +// Sort colors by HSV values (primarily by hue, then saturation, then value) +static void sort_colors_by_hsv(std::vector& colors) { + if (colors.size() < 2) return; + std::sort(colors.begin(), colors.end(), + [](const wxColour& a, const wxColour& b) { + ColourHSV ha = wxColourToHSV(a); + ColourHSV hb = wxColourToHSV(b); + if (ha.h != hb.h) return ha.h < hb.h; + if (ha.s != hb.s) return ha.s < hb.s; + return ha.v < hb.v; + }); +} + +static wxBitmap create_single_filament_bitmap(const wxColour& color, const wxSize& size) +{ + BitmapDC bdc = init_bitmap_dc(size); + if (!bdc.dc.IsOk()) return wxNullBitmap; + + bdc.dc.SetBrush(wxBrush(color)); + bdc.dc.DrawRectangle(0, 0, size.GetWidth(), size.GetHeight()); + + // Add gray border for light colors (similar to wxExtensions.cpp logic) + if (color.Red() > 224 && color.Blue() > 224 && color.Green() > 224) { + bdc.dc.SetPen(*wxGREY_PEN); + bdc.dc.SetBrush(*wxTRANSPARENT_BRUSH); + bdc.dc.DrawRectangle(0, 0, size.GetWidth(), size.GetHeight()); + } + + bdc.dc.SelectObject(wxNullBitmap); + return bdc.bitmap; +} + +static wxBitmap create_dual_filament_bitmap(const wxColour& color1, const wxColour& color2, const wxSize& size) +{ + BitmapDC bdc = init_bitmap_dc(size); + + int half_width = size.GetWidth() / 2; + + bdc.dc.SetBrush(wxBrush(color1)); + bdc.dc.DrawRectangle(0, 0, half_width, size.GetHeight()); + + bdc.dc.SetBrush(wxBrush(color2)); + bdc.dc.DrawRectangle(half_width, 0, size.GetWidth() - half_width, size.GetHeight()); + + bdc.dc.SelectObject(wxNullBitmap); + return bdc.bitmap; +} + +static wxBitmap create_triple_filament_bitmap(const std::vector& colors, const wxSize& size) +{ + BitmapDC bdc = init_bitmap_dc(size); + + int third_width = size.GetWidth() / 3; + int remaining_width = size.GetWidth() - (third_width * 2); + + // Draw three vertical sections + bdc.dc.SetBrush(wxBrush(colors[0])); + bdc.dc.DrawRectangle(0, 0, third_width, size.GetHeight()); + + bdc.dc.SetBrush(wxBrush(colors[1])); + bdc.dc.DrawRectangle(third_width, 0, third_width, size.GetHeight()); + + bdc.dc.SetBrush(wxBrush(colors[2])); + bdc.dc.DrawRectangle(third_width * 2, 0, remaining_width, size.GetHeight()); + + bdc.dc.SelectObject(wxNullBitmap); + return bdc.bitmap; +} + +static wxBitmap create_quadruple_filament_bitmap(const std::vector& colors, const wxSize& size) +{ + BitmapDC bdc = init_bitmap_dc(size); + + int half_width = (size.GetWidth() + 1) / 2; + int half_height = (size.GetHeight() + 1) / 2; + + const int rects[4][4] = { + {0, 0, half_width, half_height}, // Top left + {half_width, 0, size.GetWidth() - half_width, half_height}, // Top right + {0, half_height, half_width, size.GetHeight() - half_height}, // Bottom left + {half_width, half_height, size.GetWidth() - half_width, size.GetHeight() - half_height} // Bottom right + }; + + for (int i = 0; i < 4; i++) { + bdc.dc.SetBrush(wxBrush(colors[i])); + bdc.dc.DrawRectangle(rects[i][0], rects[i][1], rects[i][2], rects[i][3]); + } + + bdc.dc.SelectObject(wxNullBitmap); + return bdc.bitmap; +} + +static wxBitmap create_gradient_filament_bitmap(const std::vector& colors, const wxSize& size) +{ + BitmapDC bdc = init_bitmap_dc(size); + + if (colors.size() == 1) { + return create_single_filament_bitmap(colors[0], size); + } + + // use segment gradient, make transition more natural + wxDC& dc = bdc.dc; + int total_width = size.GetWidth(); + int height = size.GetHeight(); + + // calculate segment count + int segment_count = colors.size() - 1; + double segment_width = (double)total_width / segment_count; + + int left = 0; + for (int i = 0; i < segment_count; i++) { + int current_width = (int)segment_width; + + // handle last segment, ensure fully filled + if (i == segment_count - 1) { + current_width = total_width - left; + } + + // avoid width exceed boundary + if (left + current_width > total_width) { + current_width = total_width - left; + } + + if (current_width > 0) { + auto rect = wxRect(left, 0, current_width, height); + dc.GradientFillLinear(rect, colors[i], colors[i + 1], wxEAST); + left += current_width; + } + } + + bdc.dc.SelectObject(wxNullBitmap); + return bdc.bitmap; +} + +wxBitmap create_filament_bitmap(const std::vector& colors, const wxSize& size, bool force_gradient) +{ + if (colors.empty()) return wxNullBitmap; + + // Make a copy to sort without modifying original + std::vector sorted_colors = colors; + + // Sort colors by HSV when there are 2 or more colors + if (sorted_colors.size() >= 2) { + sort_colors_by_hsv(sorted_colors); + } + + if (force_gradient && sorted_colors.size() >= 2) { + return create_gradient_filament_bitmap(sorted_colors, size); + } + + switch (sorted_colors.size()) { + case 1: return create_single_filament_bitmap(sorted_colors[0], size); + case 2: return create_dual_filament_bitmap(sorted_colors[0], sorted_colors[1], size); + case 3: return create_triple_filament_bitmap(sorted_colors, size); + case 4: return create_quadruple_filament_bitmap(sorted_colors, size); + default: return create_gradient_filament_bitmap(sorted_colors, size); + } +} + +}} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/FilamentBitmapUtils.hpp b/src/slic3r/GUI/FilamentBitmapUtils.hpp new file mode 100644 index 0000000000..b949522353 --- /dev/null +++ b/src/slic3r/GUI/FilamentBitmapUtils.hpp @@ -0,0 +1,26 @@ +#ifndef slic3r_GUI_FilamentBitmapUtils_hpp_ +#define slic3r_GUI_FilamentBitmapUtils_hpp_ + +#include +#include +#include + +namespace Slic3r { namespace GUI { + +enum class FilamentRenderMode { + Single, + Dual, + Triple, + Quadruple, + Gradient +}; + +// Create a colour swatch bitmap. The render mode is chosen automatically from the +// number of colours unless force_gradient is true. +wxBitmap create_filament_bitmap(const std::vector& colors, + const wxSize& size, + bool force_gradient = false); + +}} // namespace Slic3r::GUI + +#endif // slic3r_GUI_FilamentBitmapUtils_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/FilamentPickerDialog.cpp b/src/slic3r/GUI/FilamentPickerDialog.cpp new file mode 100644 index 0000000000..0d00b5aa19 --- /dev/null +++ b/src/slic3r/GUI/FilamentPickerDialog.cpp @@ -0,0 +1,618 @@ +#include "FilamentPickerDialog.hpp" +#include "GUI.hpp" +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "MainFrame.hpp" +#include "EncodedFilament.hpp" +#include "Widgets/Label.hpp" +#include +#include +#include +#include + +#define COLOR_DEMO_SIZE wxSize(FromDIP(50), FromDIP(50)) +#define COLOR_BTN_BITMAP_SIZE wxSize(FromDIP(24), FromDIP(24)) +#define COLOR_BTN_SIZE wxSize(FromDIP(30), FromDIP(30)) +#define GRID_GAP FromDIP(2) +#define COLS 9 // fixed column count +#define MAX_VISIBLE_ROWS 7 // max rows before scrollbar appears + +namespace Slic3r { namespace GUI { + +wxColour FilamentPickerDialog::GetSelectedColour() const +{ + if (!m_color_demo) return wxNullColour; + return m_color_demo->GetBackgroundColour(); +} + +void FilamentPickerDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + // Handle DPI change + CreateShapedBitmap(); + SetWindowShape(); + Refresh(); + Layout(); +} + +FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fila_id, const FilamentColor& fila_color, const std::string& fila_type) + : DPIDialog(parent ? parent : wxGetApp().mainframe, + wxID_ANY, + _L("Select Filament"), + wxDefaultPosition, + wxDefaultSize, + wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxFRAME_SHAPED) +{ + SetBackgroundColour(wxColour(255, 255, 255)); + + m_color_query = new FilamentColorCodeQuery(); + m_is_data_loaded = LoadFilamentData(fila_id); + m_current_filament_color = fila_color; + wxString color_name = m_color_query->GetFilaColorName(fila_id, fila_color); + m_cur_color_name = new wxString(color_name); + + wxBoxSizer *container_sizer = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL); + + // Preview panel (always present) + wxBoxSizer *preview_sizer = CreatePreviewPanel(fila_color, fila_type); + main_sizer->AddSpacer(FromDIP(4)); + main_sizer->Add(preview_sizer, 0, wxEXPAND, 0); + main_sizer->AddSpacer(FromDIP(12)); + + wxBoxSizer *line_sizer = CreateSeparatorLine(); + main_sizer->Add(line_sizer, 0, wxEXPAND, 0); + + // If caller passed an initial colour, reflect it in preview box. + if (m_is_data_loaded) { + // Colour grid with all filaments + wxScrolledWindow* color_grid = CreateColorGrid(); + main_sizer->Add(color_grid, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(8)); + } + + // "More colours" button (always present) + m_more_btn = new wxButton(this, wxID_ANY, "+ " + _L("More Colors"), + wxDefaultPosition, wxSize(-1, FromDIP(36)), 0); + m_more_btn->SetBackgroundColour(wxColour(248, 248, 248)); // Light gray background + main_sizer->Add(m_more_btn, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(8)); + main_sizer->AddSpacer(FromDIP(8)); + + // OK / Cancel buttons + wxBoxSizer* btn_sizer = CreateButtonPanel(); + main_sizer->Add(btn_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10)); + container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(16)); + + SetSizer(container_sizer); + Layout(); + container_sizer->Fit(this); + + // Position the dialog relative to the parent window + if (GetParent()) { + // Align the dialog with the sidebar + auto& sidebar = wxGetApp().sidebar(); + wxPoint sidebar_pos = sidebar.GetScreenPosition(); + wxSize sidebar_size = sidebar.GetSize(); + + wxPoint new_pos( + sidebar_pos.x + sidebar_size.GetWidth() + FromDIP(10), + sidebar_pos.y + FromDIP(80) + ); + SetPosition(new_pos); + } else { + Centre(wxBOTH); // If no parent window, center the dialog + } + + // Create shaped window after sizing + CreateShapedBitmap(); +#ifndef __WXGTK__ + // Windows and macOS can set shape immediately + SetWindowShape(); +#endif +#ifdef __WXGTK__ + // GTK platform needs to wait for window creation + Bind(wxEVT_CREATE, &FilamentPickerDialog::OnWindowCreate, this); +#endif + + Layout(); + // Set window transparency + SetTransparent(255); + BindEvents(); +} + +FilamentPickerDialog::~FilamentPickerDialog() +{ + delete m_color_query; + m_color_query = nullptr; + + delete m_cur_color_name; + m_cur_color_name = nullptr; +} + +void FilamentPickerDialog::CreateShapedBitmap() +{ + wxSize size = GetSize(); + if (size.GetWidth() <= 0 || size.GetHeight() <= 0) { + return; + } + + // Create a bitmap with alpha channel + m_shape_bmp.Create(size.GetWidth(), size.GetHeight(), 32); + + wxMemoryDC dc; + dc.SelectObject(m_shape_bmp); + + dc.SetBackground(wxBrush(wxColour(0, 0, 0))); + dc.Clear(); + + // Draw main white shape on top, positioned to let shadow show through + dc.SetBrush(wxBrush(wxColour(255, 255, 255, 255))); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.DrawRoundedRectangle(0, 0, + size.GetWidth(), + size.GetHeight(), + FromDIP(m_corner_radius)); + + dc.SelectObject(wxNullBitmap); +} + +void FilamentPickerDialog::SetWindowShape() +{ + if (!m_shape_bmp.IsOk()) { + return; + } + + // Create a region from the bitmap using magenta as transparent mask color + wxRegion region(m_shape_bmp, wxColour(0, 0, 0)); + + if (region.IsOk()) { + SetShape(region); + } +} + +bool FilamentPickerDialog::LoadFilamentData(const wxString& fila_id) +{ + m_current_color_codes = m_color_query->GetFilaInfoMap(fila_id); + + if (!m_current_color_codes) { + BOOST_LOG_TRIVIAL(warning) << "No color codes found for filament ID: " << fila_id.ToStdString(); + return false; + } + + FilamentColor2CodeMap* color_map = m_current_color_codes->GetFilamentColor2CodeMap(); + if (!color_map) { + BOOST_LOG_TRIVIAL(warning) << "No color map found for filament ID: " << fila_id.ToStdString(); + return false; + } + + BOOST_LOG_TRIVIAL(info) << "Successfully loaded " << color_map->size() << " color variants for filament " << fila_id.ToStdString(); + return !color_map->empty(); +} + +wxBoxSizer* FilamentPickerDialog::CreatePreviewPanel(const FilamentColor& fila_color, const std::string& fila_type) +{ + wxBoxSizer *preview_sizer = new wxBoxSizer(wxHORIZONTAL); + + // Bitmap preview box UI + m_color_demo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, + wxDefaultPosition, COLOR_DEMO_SIZE, 0); + + preview_sizer->Add(m_color_demo, 0, wxALIGN_CENTER_VERTICAL, 0); + preview_sizer->AddSpacer(FromDIP(12)); + + + // Basic info box UI + wxBoxSizer *label_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticBox *basic_info_box = new wxStaticBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition); + wxStaticBoxSizer *basic_info_sizer = new wxStaticBoxSizer(basic_info_box, wxHORIZONTAL); + basic_info_sizer->SetMinSize(wxSize(-1, -1)); // Set minimal margins + // Color name + m_label_preview_color = new wxStaticText(this, wxID_ANY, _L("Custom Color")); + wxFont cfont = m_label_preview_color->GetFont(); + cfont.SetWeight(wxFONTWEIGHT_BOLD); + cfont.SetPointSize(FromDIP(8)); + m_label_preview_color->SetFont(cfont); + // Color index + m_label_preview_idx = new wxStaticText(this, wxID_ANY, _L("")); + m_label_preview_idx->SetFont(cfont); + // Filament type + m_label_preview_type = new wxStaticText(this, wxID_ANY, _L("")); + m_label_preview_type->SetForegroundColour(wxColour(128, 128, 128)); + + // Add labels to sizer + basic_info_sizer->Add(m_label_preview_color, 0, wxALIGN_CENTER_VERTICAL, 0); + basic_info_sizer->AddSpacer(FromDIP(8)); + basic_info_sizer->Add(m_label_preview_idx, 0, wxALIGN_CENTER_VERTICAL, 0); + + label_sizer->Add(basic_info_sizer, 0, wxTOP, FromDIP(-6)); + label_sizer->AddSpacer(FromDIP(2)); + label_sizer->Add(m_label_preview_type, 0, wxEXPAND | wxLEFT, FromDIP(6)); + + preview_sizer->Add(label_sizer, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0); + + // Bitmap preview content + std::vector wx_colors(fila_color.m_colors.begin(), fila_color.m_colors.end()); + wxBitmap init_bmp = create_filament_bitmap(wx_colors, COLOR_DEMO_SIZE, + fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR); + m_color_demo->SetBitmap(init_bmp); + + // Basic info content + m_label_preview_type->SetLabel(fila_type); + if (m_cur_color_name && !m_cur_color_name->IsEmpty()) { + m_label_preview_color->SetLabel(*m_cur_color_name); + + // Try to get additional color code information + if (m_current_color_codes) { + FilamentColorCode* color_code = m_current_color_codes->GetColorCode(fila_color); + if (color_code) { + m_label_preview_idx->SetLabel(wxString::Format("(%s)", color_code->GetFilaColorCode())); + m_label_preview_type->SetLabel(color_code->GetFilaType()); + } + } + } + + return preview_sizer; +} + +wxBoxSizer* FilamentPickerDialog::CreateSeparatorLine() +{ + wxBoxSizer *line_sizer = new wxBoxSizer(wxHORIZONTAL); + wxPanel* separator_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(1))); + separator_line->SetBackgroundColour(wxColour(220, 220, 220)); + wxStaticText* line_text = new wxStaticText(this, wxID_ANY, _L("Official Filament"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); + line_text->SetForegroundColour(wxColour(128, 128, 128)); + line_sizer->Add(line_text, 0, wxEXPAND, 0); + line_sizer->AddSpacer(FromDIP(8)); + line_sizer->Add(separator_line, 1, wxALIGN_CENTER_VERTICAL, 0); + return line_sizer; +} + +wxScrolledWindow* FilamentPickerDialog::CreateColorGrid() +{ + if (!m_current_color_codes) return nullptr; + + FilamentColor2CodeMap* color_map = m_current_color_codes->GetFilamentColor2CodeMap(); + if (!color_map) return nullptr; + + // Calculate required row count + int total_colors = color_map->size(); + int needed_rows = (total_colors + COLS - 1) / COLS; // round-up division + bool need_scroll = needed_rows > MAX_VISIBLE_ROWS; + + // Create a vertical-only scrolled window + wxScrolledWindow* scroll_win = new wxScrolledWindow( + this, + wxID_ANY, + wxDefaultPosition, + wxDefaultSize, + wxVSCROLL | wxNO_BORDER + ); + + wxGridSizer* grid_sizer = new wxGridSizer(needed_rows, COLS, GRID_GAP, GRID_GAP); + + if (!color_map->empty()) { + for (const auto& color_pair : *color_map) { + const FilamentColor& fila_color = color_pair.first; // color info + FilamentColorCode* color_code = color_pair.second; // color code + + if (!color_code) continue; + std::vector wx_colors(fila_color.m_colors.begin(), fila_color.m_colors.end()); + wxBitmap btn_bmp = create_filament_bitmap( + wx_colors, + COLOR_BTN_BITMAP_SIZE, + fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR + ); + + if (!btn_bmp.IsOk()) { + BOOST_LOG_TRIVIAL(error) << "Failed to create bitmap for filament " << color_code->GetFilaColorCode().ToStdString(); + continue; + } + + wxBitmapButton* btn = new wxBitmapButton( + scroll_win, + wxID_ANY, + btn_bmp, + wxDefaultPosition, + COLOR_BTN_SIZE, + wxBU_EXACTFIT | wxNO_BORDER + ); + + if (btn) { + // Remove any default background and borders + btn->SetBackgroundColour(*wxWHITE); + + // Set tooltip with filament information + wxString tooltip = wxString::Format("%s", color_code->GetFilaColorName()); + btn->SetToolTip(tooltip); + + // Check if this color matches the current color name and set as selected + bool is_matching_color = (m_cur_color_name && + !m_cur_color_name->IsEmpty() && + *m_cur_color_name == color_code->GetFilaColorName()); + + if (is_matching_color) { + m_current_filament_color = color_code->GetFilaColor(); + m_currently_selected_btn = btn; + UpdatePreview(*color_code); + btn->Bind(wxEVT_PAINT, &FilamentPickerDialog::OnButtonPaint, this); + } + + // Bind click + btn->Bind(wxEVT_LEFT_DOWN, [this, btn, color_code](wxMouseEvent& evt) { + m_current_filament_color = color_code->GetFilaColor(); + UpdatePreview(*color_code); + UpdateButtonStates(btn); + evt.Skip(); + }); + + // Hover highlight + btn->Bind(wxEVT_ENTER_WINDOW, [btn](wxMouseEvent& evt) { + evt.Skip(); + }); + + btn->Bind(wxEVT_LEAVE_WINDOW, [btn](wxMouseEvent& evt) { + evt.Skip(); + }); + + grid_sizer->Add(btn, 0, wxALL | wxALIGN_CENTER, FromDIP(1)); + } + } + } + + scroll_win->SetSizer(grid_sizer); + + if (need_scroll) { + int row_height = COLOR_BTN_SIZE.GetHeight() + FromDIP(2); + int col_width = COLOR_BTN_SIZE.GetWidth() + FromDIP(4); + + // Reserve space for vertical scrollbar so it doesn't overlay content + int scrollbar_width = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X); + + // Set minimum visible area (including scrollbar width) + scroll_win->SetMinSize(wxSize(col_width * COLS + scrollbar_width, row_height * MAX_VISIBLE_ROWS)); + + // Let wxScrolledWindow calculate appropriate virtual size + scroll_win->FitInside(); + scroll_win->SetScrollRate(0, row_height); + } else { + scroll_win->FitInside(); + scroll_win->SetScrollRate(0, 0); + } + + return scroll_win; +} + +void FilamentPickerDialog::UpdatePreview(const FilamentColorCode& color_code) +{ + FilamentColor fila_color = color_code.GetFilaColor(); + + std::vector wx_colors(fila_color.m_colors.begin(), fila_color.m_colors.end()); + + // Update preview bitmap + wxBitmap bmp = create_filament_bitmap(wx_colors, COLOR_DEMO_SIZE, + fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR); + + if (bmp.IsOk()) { + BOOST_LOG_TRIVIAL(debug) << "Bitmap created successfully: " << bmp.GetWidth() << "x" << bmp.GetHeight(); + m_color_demo->SetBitmap(bmp); + if (!wx_colors.empty()) { + m_color_demo->SetBackgroundColour(wx_colors[0]); + } + m_color_demo->Refresh(); + } else { + BOOST_LOG_TRIVIAL(error) << "Failed to create bitmap"; + } + + // Update preview labels + m_label_preview_color->SetLabel(color_code.GetFilaColorName()); + m_label_preview_idx->SetLabel(wxString::Format("(%s)", color_code.GetFilaColorCode())); + m_label_preview_type->SetLabel(color_code.GetFilaType()); + Layout(); +} + +void FilamentPickerDialog::UpdateButtonStates(wxBitmapButton* selected_btn) +{ + // Reset selected button appearance + if (m_currently_selected_btn) { + m_currently_selected_btn->SetBackgroundColour(*wxWHITE); // Restore white background + m_currently_selected_btn->Unbind(wxEVT_PAINT, &FilamentPickerDialog::OnButtonPaint, this); + m_currently_selected_btn->Refresh(); + } + + if (selected_btn) { + // Bind paint event to draw custom green border + selected_btn->Bind(wxEVT_PAINT, &FilamentPickerDialog::OnButtonPaint, this); + selected_btn->Refresh(); + } + + m_currently_selected_btn = selected_btn; +} + +wxBoxSizer* FilamentPickerDialog::CreateButtonPanel() +{ + wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL); + + // Add spacer to push buttons to the right + btn_sizer->AddStretchSpacer(); + + // standard button color style + StateColor btn_bg_green( + std::pair(wxColour(27, 136, 68), StateColor::Pressed), + std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal) + ); + StateColor btn_bd_green( + std::pair(wxColour(0, 174, 66), StateColor::Normal) + ); + StateColor btn_text_green( + std::pair(wxColour(255, 255, 254), StateColor::Normal) + ); + + StateColor btn_bg_white( + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(255, 255, 255), StateColor::Normal) + ); + StateColor btn_bd_white( + std::pair(wxColour(38, 46, 48), StateColor::Normal) + ); + StateColor btn_text_white( + std::pair(wxColour(38, 46, 48), StateColor::Normal) + ); + + // Create Cancel button using project's Button class + m_cancel_btn = new Button(this, _L("Cancel"), "", 0, 0, wxID_CANCEL); + m_cancel_btn->SetMinSize(wxSize(FromDIP(72), FromDIP(24))); + m_cancel_btn->SetCornerRadius(FromDIP(12)); + m_cancel_btn->SetBackgroundColor(btn_bg_white); + m_cancel_btn->SetBorderColor(btn_bd_white); + m_cancel_btn->SetTextColor(btn_text_white); + btn_sizer->Add(m_cancel_btn, 0, wxEXPAND, 0); + btn_sizer->AddSpacer(FromDIP(10)); + + // Create OK button using project's Button class + m_ok_btn = new Button(this, _L("OK"), "", 0, 0, wxID_OK); + m_ok_btn->SetMinSize(wxSize(FromDIP(72), FromDIP(24))); + m_ok_btn->SetCornerRadius(FromDIP(12)); + m_ok_btn->SetBackgroundColor(btn_bg_green); + m_ok_btn->SetBorderColor(btn_bd_green); + m_ok_btn->SetTextColor(btn_text_green); + m_ok_btn->SetFocus(); + btn_sizer->Add(m_ok_btn, 0, wxEXPAND, 0); + + return btn_sizer; +} + +void FilamentPickerDialog::BindEvents() +{ + // Bind mouse events + Bind(wxEVT_LEFT_DOWN, &FilamentPickerDialog::OnMouseLeftDown, this); + Bind(wxEVT_MOTION, &FilamentPickerDialog::OnMouseMove, this); + Bind(wxEVT_LEFT_UP, &FilamentPickerDialog::OnMouseLeftUp, this); + + // Add safety event handlers to ensure mouse capture is released + Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& event) { + if (HasCapture()) { + ReleaseMouse(); + } + event.Skip(); + }); + + Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { + if (HasCapture()) { + ReleaseMouse(); + } + event.Skip(); + }); + + // Bind more colors button event + if (m_more_btn) { + m_more_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + auto parent = dynamic_cast(GetParent()); + if (parent) { + parent->show_default_color_picker(); + EndModal(wxID_CANCEL); + } + }); + } + + // Bind OK button event + if (m_ok_btn) { + m_ok_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + EndModal(wxID_OK); + }); + } + + // Bind Cancel button event + if (m_cancel_btn) { + m_cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + EndModal(wxID_CANCEL); + }); + } +} + +#ifdef __WXGTK__ +void FilamentPickerDialog::OnWindowCreate(wxWindowCreateEvent& event) +{ + // GTK platform needs to wait for window creation + SetWindowShape(); +} +#endif + +void FilamentPickerDialog::OnMouseLeftDown(wxMouseEvent& event) +{ + // Only allow dragging from empty areas (not from buttons or other controls) + wxWindow* hit_window = wxFindWindowAtPoint(ClientToScreen(event.GetPosition())); + if (hit_window && hit_window != this) { + // Click was on a child control, don't drag + event.Skip(); + return; + } + + // Release any existing capture first + if (HasCapture()) { + ReleaseMouse(); + } + + CaptureMouse(); + wxPoint pt = ClientToScreen(event.GetPosition()); + wxPoint origin = GetPosition(); + int dx = pt.x - origin.x; + int dy = pt.y - origin.y; + m_drag_delta = wxPoint(dx, dy); + + // Don't skip the event for dragging to work properly +} + +void FilamentPickerDialog::OnMouseMove(wxMouseEvent& event) +{ + wxPoint pt = event.GetPosition(); + + if (event.Dragging() && event.LeftIsDown() && HasCapture()) { + wxPoint pos = ClientToScreen(pt); + Move(wxPoint(pos.x - m_drag_delta.x, pos.y - m_drag_delta.y)); + } + + event.Skip(); +} + +void FilamentPickerDialog::OnMouseLeftUp(wxMouseEvent& event) +{ + if (HasCapture()) { + ReleaseMouse(); + } + + event.Skip(); +} + +void FilamentPickerDialog::OnButtonPaint(wxPaintEvent& event) +{ + wxWindow* button = dynamic_cast(event.GetEventObject()); + if (!button) { + event.Skip(); + return; + } + + // Create paint DC and let default painting happen first + wxPaintDC dc(button); + + //Clear the button with white background + dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH)); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.DrawRectangle(0, 0, COLOR_BTN_SIZE.GetWidth(), COLOR_BTN_SIZE.GetHeight()); + + // Draw the bitmap in the center + wxBitmapButton* bmpBtn = dynamic_cast(button); + if (bmpBtn && bmpBtn->GetBitmap().IsOk()) { + wxBitmap bmp = bmpBtn->GetBitmap(); + int x = (COLOR_BTN_SIZE.GetWidth() - COLOR_BTN_BITMAP_SIZE.GetWidth()) / 2; + int y = (COLOR_BTN_SIZE.GetHeight() - COLOR_BTN_BITMAP_SIZE.GetHeight()) / 2; + dc.DrawBitmap(bmp, x, y, true); + } + + // Draw the green border + dc.SetPen(wxPen(wxColour("#00AE42"), 2)); // Green pen, 2px thick + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.DrawRectangle(1, 1, COLOR_BTN_SIZE.GetWidth() - 1, COLOR_BTN_SIZE.GetHeight() - 1); +} + +}} // namespace Slic3r::GUI + diff --git a/src/slic3r/GUI/FilamentPickerDialog.hpp b/src/slic3r/GUI/FilamentPickerDialog.hpp new file mode 100644 index 0000000000..a6ba3a8c36 --- /dev/null +++ b/src/slic3r/GUI/FilamentPickerDialog.hpp @@ -0,0 +1,89 @@ +#ifndef slic3r_GUI_FilamentPickerDialog_hpp_ +#define slic3r_GUI_FilamentPickerDialog_hpp_ + +#include "GUI_App.hpp" +#include "GUI.hpp" +#include "GUI_Utils.hpp" +#include "FilamentBitmapUtils.hpp" +#include "Widgets/Button.hpp" +#include "EncodedFilament.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { namespace GUI { + +class FilamentPickerDialog : public DPIDialog +{ +public: + FilamentPickerDialog(wxWindow *parent, const wxString &fila_id, const FilamentColor &fila_color, const std::string &fila_type); + virtual ~FilamentPickerDialog(); + + // Public interface methods + bool IsDataLoaded() const { return m_is_data_loaded; } + wxColour GetSelectedColour() const; + const FilamentColor& GetSelectedFilamentColor() const { return m_current_filament_color; } + +protected: + void on_dpi_changed(const wxRect &suggested_rect) override; + + // Event handlers +#ifdef __WXGTK__ + void OnWindowCreate(wxWindowCreateEvent& event); +#endif + void OnMouseLeftDown(wxMouseEvent& event); + void OnMouseMove(wxMouseEvent& event); + void OnMouseLeftUp(wxMouseEvent& event); + void OnButtonPaint(wxPaintEvent& event); + +private: + // UI creation methods + wxBoxSizer* CreatePreviewPanel(const FilamentColor& fila_color, const std::string& fila_type); + wxScrolledWindow* CreateColorGrid(); + wxBoxSizer* CreateSeparatorLine(); + wxBoxSizer* CreateButtonPanel(); + void BindEvents(); + + // UI update methods + void UpdatePreview(const FilamentColorCode& filament); + void UpdateButtonStates(wxBitmapButton* selected_btn); + + // Shaped window methods + void SetWindowShape(); + void CreateShapedBitmap(); + + // Data loading + bool LoadFilamentData(const wxString& fila_id); + + // UI elements + wxStaticBitmap* m_color_demo{nullptr}; + wxStaticText* m_label_preview_color{nullptr}; + wxStaticText* m_label_preview_idx{nullptr}; + wxStaticText* m_label_preview_type{nullptr}; + wxButton* m_more_btn{nullptr}; + Button* m_ok_btn{nullptr}; + Button* m_cancel_btn{nullptr}; + wxString* m_cur_color_name{nullptr}; + + // Data members + FilamentColorCodeQuery* m_color_query{nullptr}; + FilamentColorCodes* m_current_color_codes{nullptr}; + bool m_is_data_loaded{false}; + wxBitmapButton* m_currently_selected_btn{nullptr}; + FilamentColor m_current_filament_color; + + // Shaped window members + wxBitmap m_shape_bmp; + int m_corner_radius{8}; + + // Mouse drag members + wxPoint m_drag_delta; +}; + +}} // namespace Slic3r::GUI + +#endif diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ba5644f659..e625be6905 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2923,14 +2923,15 @@ std::map Sidebar::build_filament_ams_list(MachineObject tray_config.set_key_value("tray_name", new ConfigOptionStrings{ name }); tray_config.set_key_value("filament_colour", new ConfigOptionStrings{into_u8(wxColour("#" + tray.color).GetAsString(wxC2S_HTML_SYNTAX))}); tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists}); - tray_config.set_key_value("filament_multi_colors", new ConfigOptionStrings{}); + tray_config.set_key_value("filament_multi_colour", new ConfigOptionStrings{}); + tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)}); std::optional info; if (wxGetApp().preset_bundle) { info = wxGetApp().preset_bundle->get_filament_by_filament_id(tray.setting_id); } tray_config.set_key_value("filament_is_support", new ConfigOptionBools{ info.has_value() ? info->is_support : false}); for (int i = 0; i < tray.cols.size(); ++i) { - tray_config.opt("filament_multi_colors")->values.push_back(into_u8(wxColour("#" + tray.cols[i]).GetAsString(wxC2S_HTML_SYNTAX))); + tray_config.opt("filament_multi_colour")->values.push_back(into_u8(wxColour("#" + tray.cols[i]).GetAsString(wxC2S_HTML_SYNTAX))); } return tray_config; }; @@ -5809,11 +5810,42 @@ std::vector Plater::priv::load_files(const std::vector& input_ *wipe_tower_y = *file_wipe_tower_y; ConfigOptionStrings* filament_color = proj_cfg.opt("filament_colour"); - ConfigOptionInts* filament_map = proj_cfg.opt("filament_map", true); - if (filament_color && filament_color->size() != filament_map->size()) { - filament_map->values.resize(filament_color->size(), 1); + if (filament_color) { + size_t filament_count = filament_color->size(); + + // Sync filament map + ConfigOptionInts* filament_map = proj_cfg.opt("filament_map", true); + if (filament_map->size() != filament_count) { + filament_map->values.resize(filament_count, 1); + } + + // Sync filament multi colour + ConfigOptionStrings* filament_multi_color = proj_cfg.opt("filament_multi_colour", true); + if (filament_multi_color->size() != filament_count) { + filament_multi_color->values.resize(filament_count); + } + // If there is no multi-color data or color is not match, use single color as default value + for (size_t i = 0; i < filament_count; i++) { + std::vector colors = Slic3r::split_string(filament_multi_color->values[i], ','); + if (i >= filament_multi_color->values.size() || colors.empty() || colors[0] != filament_color->values[i] ) { + filament_multi_color->values[i] = filament_color->values[i]; + } + } + // Sync filament colour type + ConfigOptionStrings* filament_color_type = proj_cfg.opt("filament_colour_type", true); + if (filament_color_type && filament_color_type->size() != filament_count) { + filament_color_type->values.resize(filament_count); + + for (size_t i = 0; i < filament_count; i++) { + if (i >= filament_color_type->values.size() || filament_color_type->values[i].empty()) { + filament_color_type->values[i] = "1"; + } + } + } } } + // Update filament combobox after loading config + wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT); } } if (!silence) wxGetApp().app_config->update_config_dir(path.parent_path().string()); @@ -15501,6 +15533,26 @@ std::vector Plater::get_extruder_colors_from_plater_config(const GC } } +std::vector Plater::get_filament_colors_render_info() const +{ + const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->project_config; + std::vector color_packs; + if (!config->has("filament_multi_colour")) return color_packs; + + color_packs = (config->option("filament_multi_colour"))->values; + return color_packs; +} + +std::vector Plater::get_filament_color_render_type() const +{ + const Slic3r::DynamicPrintConfig *config = &wxGetApp().preset_bundle->project_config; + std::vector ctype; + if (!config->has("filament_colour_type")) return ctype; + + ctype = (config->option("filament_colour_type"))->values; + return ctype; +} + /* Get vector of colors used for rendering of a Preview scene in "Color print" mode * It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z */ diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5174929def..ac856c600f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -557,6 +557,8 @@ public: // On activating the parent window. void on_activate(); std::vector get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; + std::vector get_filament_colors_render_info() const; + std::vector get_filament_color_render_type() const; std::vector get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const; void set_global_filament_map_mode(FilamentMapMode mode); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index d4f73e3ef2..0fdfb8d7cc 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,8 @@ #include "SavePresetDialog.hpp" #include "MsgDialog.hpp" #include "ParamsDialog.hpp" +#include "FilamentPickerDialog.hpp" +#include "wxExtensions.hpp" // A workaround for a set of issues related to text fitting into gtk widgets: #if defined(__WXGTK20__) || defined(__WXGTK3__) @@ -219,6 +222,8 @@ int PresetComboBox::update_ams_color() if (m_filament_idx < 0) return -1; int idx = selected_ams_filament(); std::string color; + std::string ctype; + std::vector colors; if (idx < 0) { auto name = Preset::remove_suffix_modified(GetValue().ToUTF8().data()); auto *preset = m_collection->find_preset(name); @@ -233,12 +238,30 @@ int PresetComboBox::update_ams_color() return -1; } color = iter->second.opt_string("filament_colour", 0u); + ctype = iter->second.opt_string("filament_colour_type", 0u); + colors = iter->second.opt("filament_multi_colour")->values; } DynamicPrintConfig *cfg = &wxGetApp().preset_bundle->project_config; - auto colors = static_cast(cfg->option("filament_colour")->clone()); - colors->values[m_filament_idx] = color; + auto color_head = static_cast(cfg->option("filament_colour")->clone()); // single color (the first color if multi-color filament) + auto color_pack = static_cast(cfg->option("filament_multi_colour")->clone()); // multi color (all colors in all kinds of filament) + auto color_type = static_cast(cfg->option("filament_colour_type")->clone()); // color type + + color_head->values[m_filament_idx] = color; + color_type->values[m_filament_idx] = ctype; + std::string color_str = ""; // Translate multi color info to config storage format + for (auto &c : colors) { + if (c.empty()) continue; + color_str += c + " "; + } + if (color_str.empty()) color_str = color; + else color_str.erase(color_str.size() - 1); + color_pack->values[m_filament_idx] = color_str; + + // Update color informations in config DynamicPrintConfig new_cfg; - new_cfg.set_key_value("filament_colour", colors); + new_cfg.set_key_value("filament_colour", color_head); + new_cfg.set_key_value("filament_colour_type", color_type); + new_cfg.set_key_value("filament_multi_colour", color_pack); cfg->apply(new_cfg); wxGetApp().plater()->on_config_change(new_cfg); //trigger the filament color changed @@ -507,6 +530,7 @@ bool PresetComboBox::add_ams_filaments(std::string selected, bool alias_name) } const_cast(*iter).is_visible = true; auto color = tray.opt_string("filament_colour", 0u); + auto multi_color = tray.opt("filament_multi_colour")->values; wxBitmap bmp(*get_extruder_color_icon(color, name, icon_width, 16)); auto text = get_preset_name(*iter); int item_id = Append(text, bmp.ConvertToImage(), &m_first_ams_filament + entry.first); @@ -809,39 +833,48 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset for (int i = 0; i < colors.size(); i++) { m_clrData.SetCustomColour(i, string_to_wxColor(colors[i])); } - wxColourDialog dialog(this, &m_clrData); - dialog.SetTitle(_L("Please choose the filament color")); - if ( dialog.ShowModal() == wxID_OK ) - { - m_clrData = dialog.GetColourData(); - if (colors.size() != CUSTOM_COLOR_COUNT) { - colors.resize(CUSTOM_COLOR_COUNT); + + // Check if it's an official filament + auto fila_type = Preset::remove_suffix_modified(GetValue().ToUTF8().data()); + bool is_official = boost::algorithm::starts_with(fila_type, "Bambu"); + if (is_official) { + // Get filament_id from filament_presets + const std::string& preset_name = m_preset_bundle->filament_presets[m_filament_idx]; + const Preset* selected_preset = m_collection->find_preset(preset_name); + wxString fila_id = selected_preset ? wxString::FromUTF8(selected_preset->filament_id) : "GFA00"; + FilamentColor fila_color = get_cur_color_info(); + + // Show filament picker dialog + FilamentPickerDialog dialog(this, fila_id, fila_color, fila_type); + + if (!dialog.IsDataLoaded()) { + // If FilamentPicker fails, fallback to default color picker + show_default_color_picker(); + } else if (dialog.ShowModal() == wxID_OK) { + // Get selected filament color data + FilamentColor fila_color = dialog.GetSelectedFilamentColor(); + + // Check if we have valid color data + if (!fila_color.m_colors.empty()) { + // Convert to storage format + std::vector colors; + for (const wxColour& color : fila_color.m_colors) { + colors.push_back(color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString()); + } + + bool is_gradient = (fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR); + this->sync_colour_config(colors, is_gradient); + } else { + // Fallback to basic color if no FilamentColor data + wxColour selected_color = dialog.GetSelectedColour(); + if (selected_color.IsOk()) { + std::vector color = {selected_color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString()}; + this->sync_colour_config(color, false); + } + } } - for (int i = 0; i < CUSTOM_COLOR_COUNT; i++) { - colors[i] = color_to_string(m_clrData.GetCustomColour(i)); - } - wxGetApp().app_config->save_custom_color_to_config(colors); - // get current color - DynamicPrintConfig* cfg = &wxGetApp().preset_bundle->project_config; - auto colors = static_cast(cfg->option("filament_colour")->clone()); - wxColour clr(colors->values[m_filament_idx]); - if (!clr.IsOk()) - clr = wxColour(0, 0, 0); // Don't set alfa to transparence - - colors->values[m_filament_idx] = m_clrData.GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); - DynamicPrintConfig cfg_new = *cfg; - cfg_new.set_key_value("filament_colour", colors); - - //wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); - cfg->apply(cfg_new); - wxGetApp().plater()->update_project_dirty_from_presets(); - wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); - update(); - wxGetApp().plater()->on_config_change(cfg_new); - - wxCommandEvent *evt = new wxCommandEvent(EVT_FILAMENT_COLOR_CHANGED); - evt->SetInt(m_filament_idx); - wxQueueEvent(wxGetApp().plater(), evt); + } else { + show_default_color_picker(); } }); } @@ -1066,13 +1099,8 @@ void PlaterPresetComboBox::update() invalidate_selection(); const Preset* selected_filament_preset = nullptr; - std::string filament_color; if (m_type == Preset::TYPE_FILAMENT) { - //unsigned char rgb[3]; - filament_color = m_preset_bundle->project_config.opt_string("filament_colour", (unsigned int) m_filament_idx); - wxColor clr(filament_color); - clr_picker->SetBackgroundColour(clr); std::vector bitmaps = get_extruder_color_icons(true); if (m_filament_idx < bitmaps.size()) { clr_picker->SetBitmap(*bitmaps[m_filament_idx]); @@ -1160,7 +1188,6 @@ void PlaterPresetComboBox::update() preset_filament_types[name] = preset.config.option("filament_type")->values.at(0); } } - wxBitmap* bmp = get_bmp(preset); assert(bmp); @@ -1332,7 +1359,7 @@ void PlaterPresetComboBox::update() update_selection(); if (m_type == Preset::TYPE_FILAMENT) { - if (wxGetApp().plater()->is_same_printer_for_connected_and_selected(false)) { + if (wxGetApp().plater()->is_same_printer_for_connected_and_selected(false)) { update_badge_according_flag(); } } @@ -1370,6 +1397,80 @@ void PlaterPresetComboBox::msw_rescale() } +FilamentColor PlaterPresetComboBox::get_cur_color_info() +{ + std::vector filaments_multi_color = Slic3r::GUI::wxGetApp().plater()->get_filament_colors_render_info(); + std::vector filament_color_type = Slic3r::GUI::wxGetApp().plater()->get_filament_color_render_type(); + std::string filament_color_info = filaments_multi_color[m_filament_idx]; + std::vector colors; + boost::split(colors, filament_color_info, boost::is_any_of(" ")); + + FilamentColor fila_color; + for (const std::string& color_str : colors) { + if (!color_str.empty()) { + wxColour color(color_str); + if (color.IsOk()) { + fila_color.m_colors.insert(color); + } + } + } + + fila_color.EndSet(filament_color_type[m_filament_idx] == "0" ? 0 : 1); + return fila_color; +} + +void PlaterPresetComboBox::show_default_color_picker() +{ + wxColourDialog dialog(this, &m_clrData); + dialog.SetTitle(_L("Please choose the filament colour")); + if (dialog.ShowModal() == wxID_OK) + { + m_clrData = dialog.GetColourData(); + std::vector color = {m_clrData.GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString()}; + this->sync_colour_config(color, false); + } +} + +void PlaterPresetComboBox::sync_colour_config(const std::vector &clrs, bool is_gradient) +{ + DynamicPrintConfig *cfg = &wxGetApp().preset_bundle->project_config; + + // Clone the string vector and patch the value at current extruder index. + auto multi_colour_opt = static_cast(cfg->option("filament_multi_colour")->clone()); + auto colour_type_opt = static_cast(cfg->option("filament_colour_type")->clone()); + auto colour_opt = static_cast(cfg->option("filament_colour")->clone()); + + if (m_filament_idx >= multi_colour_opt->values.size()) multi_colour_opt->values.resize(m_filament_idx + 1); + if (m_filament_idx >= colour_type_opt->values.size()) colour_type_opt->values.resize(m_filament_idx + 1); + if (m_filament_idx >= colour_opt->values.size()) colour_opt->values.resize(m_filament_idx + 1); + + std::string clr_str = ""; + for(auto &clr : clrs) { + clr_str += clr + " "; + } + clr_str.pop_back(); + + multi_colour_opt->values[m_filament_idx] = clr_str; + colour_opt->values[m_filament_idx] = clrs[0]; + colour_type_opt->values[m_filament_idx] = is_gradient ? "0" : "1"; + DynamicPrintConfig cfg_new = *cfg; + cfg_new.set_key_value("filament_multi_colour", multi_colour_opt); + cfg_new.set_key_value("filament_colour", colour_opt); + cfg_new.set_key_value("filament_colour_type", colour_type_opt); + cfg->apply(cfg_new); + + wxGetApp().plater()->update_project_dirty_from_presets(); + + wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); + update(); // refresh the preset combobox with new config + + wxGetApp().plater()->on_config_change(cfg_new); + + wxCommandEvent *evt = new wxCommandEvent(EVT_CALI_TRAY_CHANGED); + evt->SetInt(m_filament_idx); + wxQueueEvent(wxGetApp().plater(), evt); +} + // --------------------------------- // *** TabPresetComboBox *** // --------------------------------- diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 2e02631c36..c3b4fca51f 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -11,6 +11,7 @@ #include "BitmapComboBox.hpp" #include "Widgets/ComboBox.hpp" #include "GUI_Utils.hpp" +#include "EncodedFilament.hpp" class wxString; class wxTextCtrl; @@ -205,6 +206,10 @@ public: void OnSelect(wxCommandEvent& evt) override; void update_badge_according_flag(); + FilamentColor get_cur_color_info(); + void show_default_color_picker(); + void sync_colour_config(const std::vector &clrs, bool is_gradient); + private: // BBS wxColor m_color; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index d4d1a76a91..59793bb430 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -18,6 +18,7 @@ #include "Widgets/StaticBox.hpp" #include "Widgets/Label.hpp" #include "../Utils/WxFontUtils.hpp" +#include "FilamentBitmapUtils.hpp" #ifndef __linux__ // msw_menuitem_bitmaps is used for MSW and OSX static std::map msw_menuitem_bitmaps; @@ -529,27 +530,128 @@ std::vector get_extruder_color_icons(bool thin_icon/* = false*/) { // Create the bitmap with color bars. std::vector bmps; - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + std::vector filaments_color_info = Slic3r::GUI::wxGetApp().plater()->get_filament_colors_render_info(); + std::vector ctype = Slic3r::GUI::wxGetApp().plater()->get_filament_color_render_type(); - if (colors.empty()) - return bmps; + if (!filaments_color_info.empty() && !ctype.empty() && ctype.size() == filaments_color_info.size()) { + std::vector> readable_color_info = read_color_pack(filaments_color_info); + /* It's supposed that standard size of an icon is 36px*16px for 100% scaled display. + * So set sizes for solid_colored icons used for filament preset + * and scale them in respect to em_unit value + */ + const double em = Slic3r::GUI::wxGetApp().em_unit(); + const int icon_width = lround((thin_icon ? 2 : 4.4) * em); + const int icon_height = lround(2 * em); - /* It's supposed that standard size of an icon is 36px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const double em = Slic3r::GUI::wxGetApp().em_unit(); - const int icon_width = lround((thin_icon ? 2 : 4.4) * em); - const int icon_height = lround(2 * em); + int index = 0; + for (const auto &colors : readable_color_info) { + auto label = std::to_string(++index); + bool is_gradient = ctype[index-1] == "0"; + if (colors.size() == 1) { + bmps.push_back(get_extruder_color_icon(colors[0], label, icon_width, icon_height)); + } else { + bmps.push_back(get_extruder_color_icon(colors, is_gradient, label, icon_width, icon_height)); + } + } + } else { + std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + if (colors.empty()) return bmps; - int index = 0; - for (const std::string &color : colors) - { - auto label = std::to_string(++index); - bmps.push_back(get_extruder_color_icon(color, label, icon_width, icon_height)); + const double em = Slic3r::GUI::wxGetApp().em_unit(); + const int icon_width = lround((thin_icon ? 2 : 4.4) * em); + const int icon_height = lround(2 * em); + int index = 0; + for (const auto &color : colors) { + auto label = std::to_string(++index); + bmps.push_back(get_extruder_color_icon(color, label, icon_width, icon_height)); + } } - return bmps; + + +} + +std::vector> read_color_pack(std::vector color_pack) { + std::vector> color_info; + for (const std::string &color : color_pack) { + std::vector colors; + colors = Slic3r::split_string(color, ' '); + color_info.push_back(colors); + } + return color_info; +} + +wxBitmap *get_extruder_color_icon(std::vector colors, bool is_gradient, std::string label, int icon_width, int icon_height){ + + static Slic3r::GUI::BitmapCache bmp_cache; + + // build cache key, include all color info + std::string bitmap_key = ""; + for (const auto& color : colors) { + bitmap_key += color + "_"; + } + bitmap_key += "h" + std::to_string(icon_height) + "-w" + std::to_string(icon_width) + "-i" + label; + + wxBitmap *bitmap = bmp_cache.find(bitmap_key); + if (bitmap == nullptr) { + + std::vector wx_colors; + for (const auto& color_str : colors) { + wx_colors.push_back(wxColour(color_str)); + } + + // create filament bitmap in multi color + wxBitmap base_bitmap = Slic3r::GUI::create_filament_bitmap(wx_colors, wxSize(icon_width, icon_height), is_gradient); + + if (!base_bitmap.IsOk()) { + // if create failed, return nullptr + return nullptr; + } + + // add text label directly on base_bitmap + if (!label.empty()) { +#ifndef __WXMSW__ + wxMemoryDC dc(base_bitmap); +#else + wxClientDC cdc((wxWindow *) Slic3r::GUI::wxGetApp().mainframe); + wxMemoryDC dc(&cdc); + dc.SelectObject(base_bitmap); +#endif + + // Ensure no background contamination + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetPen(*wxTRANSPARENT_PEN); + + dc.SetFont(::Label::Body_12); + Slic3r::GUI::WxFontUtils::get_suitable_font_size(icon_height - 2, dc); + + auto size = dc.GetTextExtent(wxString(label)); + + // Set transparent background mode for text rendering + dc.SetBackgroundMode(wxTRANSPARENT); + + // draw text with black border effect + int text_x = (icon_width - size.x) / 2; + int text_y = (icon_height - size.y) / 2; + + // Draw very thin border with lighter color and fewer directions + dc.SetTextForeground(wxColor("262E30")); // Semi-transparent dark gray + dc.DrawText(label, text_x - 1, text_y); // Left + dc.DrawText(label, text_x + 1, text_y); // Right + dc.DrawText(label, text_x, text_y - 1); // Up + dc.DrawText(label, text_x, text_y + 1); // Down + + // Draw main white text on top + dc.SetTextForeground(*wxWHITE); + dc.DrawText(label, text_x, text_y); + + dc.SelectObject(wxNullBitmap); + } + + // cache result + bitmap = bmp_cache.insert(bitmap_key, base_bitmap); + } + return bitmap; } wxBitmap *get_extruder_color_icon(std::string color, std::string label, int icon_width, int icon_height) @@ -601,7 +703,6 @@ wxBitmap *get_extruder_color_icon(std::string color, std::string label, int icon } return bitmap; } - void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, wxWindow* parent, const std::string& first_item/* = ""*/, diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 30f69dbf8f..d3971822c0 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -75,6 +75,9 @@ wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullp wxBitmap* get_default_extruder_color_icon(bool thin_icon = false); std::vector get_extruder_color_icons(bool thin_icon = false); wxBitmap * get_extruder_color_icon(std::string color, std::string label, int icon_width, int icon_height); +wxBitmap * get_extruder_color_icon(std::vector colors, bool is_gradient, std::string label, int icon_width, int icon_height); +std::vector> read_color_pack(std::vector color_pack); + namespace Slic3r { namespace GUI { class BitmapComboBox;