diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 3f2df591db..a39d27ab4c 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -4154,20 +4154,22 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio //BBS: add logic for settings check between different system presets out.erase("different_settings_to_system"); - static const char* keys[] = {"support_filament", "support_interface_filament", "wipe_tower_filament"}; - for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++ i) { - std::string key = std::string(keys[i]); + const size_t num_total_filaments = this->mixed_filaments.total_filaments(num_filaments); + + static const char* support_keys[] = {"support_filament", "support_interface_filament"}; + for (size_t i = 0; i < sizeof(support_keys) / sizeof(support_keys[0]); ++ i) { + std::string key = std::string(support_keys[i]); auto *opt = dynamic_cast(out.option(key, false)); assert(opt != nullptr); opt->value = boost::algorithm::clamp(opt->value, 0, int(num_filaments)); } - static const char* keys_with_default[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament"}; - for (size_t i = 0; i < sizeof(keys_with_default) / sizeof(keys_with_default[0]); ++ i) { - std::string key = std::string(keys_with_default[i]); + static const char* feature_keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament"}; + for (size_t i = 0; i < sizeof(feature_keys) / sizeof(feature_keys[0]); ++ i) { + std::string key = std::string(feature_keys[i]); auto *opt = dynamic_cast(out.option(key, false)); assert(opt != nullptr); - if(opt->value < 0 || opt->value > int(num_filaments)) + if (opt->value < 0 || opt->value > int(num_total_filaments)) opt->value = 0; } out.option("print_settings_id", true)->value = this->prints.get_selected_preset_name(); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 16c29bbaf0..cd72e467d5 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -479,27 +479,46 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con } } - // BBS: Rule 1 — reject out-of-range AND mixed filament IDs in support/wall/infill slots. - // Mixed filaments (virtual IDs > num_phys) cannot be used in these roles; reset to 0. + // Rule 1: + // - support slots stay physical-only + // - feature slots (wall/infill/wipe tower) may use mixed virtual IDs + // Any out-of-range value is reset to 0. { - static const char* filament_slot_keys[] = { - "support_filament", "support_interface_filament", - "wall_filament", "sparse_infill_filament", "solid_infill_filament" + static const char* support_slot_keys[] = { + "support_filament", "support_interface_filament" }; - size_t total = wxGetApp().preset_bundle->total_filament_count(); - size_t num_phys = wxGetApp().preset_bundle->filament_presets.size(); - for (auto key : filament_slot_keys) { + static const char* feature_slot_keys[] = { + "wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament" + }; + + const size_t total = wxGetApp().preset_bundle->total_filament_count(); + const size_t num_phys = wxGetApp().preset_bundle->filament_presets.size(); + + for (auto key : support_slot_keys) { auto* opt = dynamic_cast(config->option(key, false)); - if (!opt) continue; - int val = opt->getInt(); - bool out_of_range = val > (int)total; - bool is_mixed = (val > (int)num_phys && val <= (int)total); + if (!opt) + continue; + const int val = opt->getInt(); + const bool out_of_range = val < 0 || val > int(total); + const bool is_mixed = val > int(num_phys) && val <= int(total); if (out_of_range || is_mixed) { DynamicPrintConfig new_conf = *config; new_conf.set_key_value(key, new ConfigOptionInt(0)); apply(config, &new_conf); } } + + for (auto key : feature_slot_keys) { + auto* opt = dynamic_cast(config->option(key, false)); + if (!opt) + continue; + const int val = opt->getInt(); + if (val < 0 || val > int(total)) { + DynamicPrintConfig new_conf = *config; + new_conf.set_key_value(key, new ConfigOptionInt(0)); + apply(config, &new_conf); + } + } } if (config->opt_enum("seam_slope_type") != SeamScarfType::None && diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 03cb796799..b48b7064bd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -932,21 +932,118 @@ std::vector get_min_flush_volumes(const DynamicPrintConfig &full_config, si struct DynamicFilamentList : DynamicList { + explicit DynamicFilamentList(bool include_mixed = false) + : m_include_mixed(include_mixed) + {} + std::vector> items; + std::vector filament_ids; + bool m_include_mixed = false; + + static wxString physical_filament_label(unsigned int filament_id_1based) + { + if (filament_id_1based == 0) + return _L("Default"); + + if (wxGetApp().preset_bundle == nullptr) + return wxString::Format(_L("Filament %d"), int(filament_id_1based)); + + const auto &filament_presets = wxGetApp().preset_bundle->filament_presets; + const size_t preset_idx = size_t(filament_id_1based - 1); + if (preset_idx < filament_presets.size()) { + auto *preset = wxGetApp().preset_bundle->filaments.find_preset(filament_presets[preset_idx]); + if (preset != nullptr) { + std::string type; + preset->get_filament_type(type); + if (!type.empty()) + return from_u8(type); + } + } + + return wxString::Format(_L("Filament %d"), int(filament_id_1based)); + } + + static wxString mixed_filament_label() + { + return _L("Mixed Filament"); + } + + static wxBitmap *fallback_icon_from_color(unsigned int filament_id_1based, + unsigned int display_filament_id_1based, + const std::vector &all_colors) + { + if (filament_id_1based == 0 || size_t(filament_id_1based) > all_colors.size()) + return nullptr; + + const std::string &color = all_colors[size_t(filament_id_1based - 1)]; + if (color.empty()) + return nullptr; + + const double em = wxGetApp().em_unit(); + const int icon_width = lround(2.0 * em); + const int icon_height = lround(2.0 * em); + return get_extruder_color_icon(color, std::to_string(display_filament_id_1based), icon_width, icon_height); + } + + void append_filament_item(unsigned int filament_id_1based, + size_t num_physical, + wxBitmap *icon) + { + if (filament_id_1based == 0) + return; + + wxString label = filament_id_1based <= num_physical + ? physical_filament_label(filament_id_1based) + : mixed_filament_label(); + + items.push_back({label, icon}); + filament_ids.push_back(int(filament_id_1based)); + } + + std::vector ordered_filament_ids() const + { + std::vector ids; + if (wxGetApp().preset_bundle == nullptr) + return ids; + + const size_t num_physical = wxGetApp().preset_bundle->filament_presets.size(); + ids.reserve(num_physical); + + if (m_include_mixed && wxGetApp().plater() != nullptr) { + ids = wxGetApp().plater()->sidebar().get_ui_ordered_filament_ids(); + return ids; + } + + for (size_t idx = 0; idx < num_physical; ++idx) + ids.emplace_back(unsigned(idx + 1)); + return ids; + } void apply_on(Choice *c) override { if (items.empty()) update(true); auto cb = dynamic_cast(c->window); + const int old_index = cb->GetSelection(); + long old_filament_id = 0; + if (old_index >= 0) { + wxString old_value = get_value(old_index); + old_value.ToLong(&old_filament_id); + } + wxString old_selection = cb->GetStringSelection(); - int old_index = cb->GetSelection(); cb->Clear(); cb->Append(_L("Default")); for (auto i : items) { cb->Append(i.first, i.second ? *i.second : wxNullBitmap); } + const int mapped_index = old_index >= 0 ? index_of(wxString::Format("%ld", old_filament_id)) : -1; + if (mapped_index >= 0 && (unsigned int)mapped_index < cb->GetCount()) { + cb->SetSelection(mapped_index); + return; + } + if (old_index >= 0 && (unsigned int) old_index < cb->GetCount()) { cb->SetSelection(old_index); return; @@ -963,29 +1060,67 @@ struct DynamicFilamentList : DynamicList } wxString get_value(int index) override { - wxString str; - str << index; - return str; + if (index <= 0) + return wxString("0"); + if (size_t(index) <= filament_ids.size()) + return wxString::Format("%d", filament_ids[size_t(index - 1)]); + return wxString("0"); } + int index_of(wxString value) override { long n = 0; - return (value.ToLong(&n) && n <= items.size()) ? int(n) : -1; + if (!value.ToLong(&n)) + return -1; + if (n == 0) + return 0; + + auto it = std::find(filament_ids.begin(), filament_ids.end(), int(n)); + if (it == filament_ids.end()) + return -1; + return int(std::distance(filament_ids.begin(), it)) + 1; } + void update(bool force = false) { items.clear(); + filament_ids.clear(); if (!force && m_choices.empty()) return; - auto icons = get_extruder_color_icons(true); - auto presets = wxGetApp().preset_bundle->filament_presets; - for (int i = 0; i < presets.size(); ++i) { - wxString str; - std::string type; - wxGetApp().preset_bundle->filaments.find_preset(presets[i])->get_filament_type(type); - str << type; - items.push_back({str, i < icons.size() ? icons[i] : nullptr}); + + if (wxGetApp().preset_bundle == nullptr) { + DynamicList::update(); + return; } + + const std::vector ids = ordered_filament_ids(); + const size_t num_physical = wxGetApp().preset_bundle->filament_presets.size(); + const auto icons = get_extruder_color_icons(true); + std::vector all_colors; + if (wxGetApp().plater() != nullptr) + all_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, true); + + for (size_t display_idx = 0; display_idx < ids.size(); ++display_idx) { + const unsigned int filament_id = ids[display_idx]; + wxBitmap *icon = nullptr; + if (filament_id >= 1 && size_t(filament_id) <= icons.size()) + icon = icons[size_t(filament_id - 1)]; + if (icon == nullptr) + icon = fallback_icon_from_color(filament_id, unsigned(display_idx + 1), all_colors); + + append_filament_item(filament_id, num_physical, icon); + } + + if (items.empty()) { + for (size_t idx = 0; idx < num_physical; ++idx) { + const unsigned int filament_id = unsigned(idx + 1); + wxBitmap *icon = (filament_id >= 1 && size_t(filament_id) <= icons.size()) + ? icons[size_t(filament_id - 1)] + : fallback_icon_from_color(filament_id, filament_id, all_colors); + append_filament_item(filament_id, num_physical, icon); + } + } + DynamicList::update(); } }; @@ -1005,7 +1140,8 @@ static bool has_junction_deviation(const DynamicPrintConfig* printer_config) junction_dev->values.front() > 0.0; } -static DynamicFilamentList dynamic_filament_list; +static DynamicFilamentList dynamic_feature_filament_list(true); +static DynamicFilamentList dynamic_support_filament_list(false); class AMSCountPopupWindow : public PopupWindow { @@ -1710,12 +1846,13 @@ void Sidebar::update_sync_ams_btn_enable(wxUpdateUIEvent &e) Sidebar::Sidebar(Plater *parent) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(39 * wxGetApp().em_unit(), -1)), p(new priv(parent)) { - Choice::register_dynamic_list("support_filament", &dynamic_filament_list); - Choice::register_dynamic_list("support_interface_filament", &dynamic_filament_list); - Choice::register_dynamic_list("wall_filament", &dynamic_filament_list); - Choice::register_dynamic_list("sparse_infill_filament", &dynamic_filament_list); - Choice::register_dynamic_list("solid_infill_filament", &dynamic_filament_list); - Choice::register_dynamic_list("wipe_tower_filament", &dynamic_filament_list); + Choice::register_dynamic_list("support_filament", &dynamic_support_filament_list); + Choice::register_dynamic_list("support_interface_filament", &dynamic_support_filament_list); + Choice::register_dynamic_list("wall_filament", &dynamic_feature_filament_list); + Choice::register_dynamic_list("sparse_infill_filament", &dynamic_feature_filament_list); + Choice::register_dynamic_list("solid_infill_filament", &dynamic_feature_filament_list); + Choice::register_dynamic_list("wipe_tower_filament", &dynamic_feature_filament_list); + Choice::register_dynamic_list("extruder", &dynamic_feature_filament_list); p->scrolled = new wxPanel(this); // p->scrolled->SetScrollbars(0, 100, 1, 2); // ys_DELETE_after_testing. pixelsPerUnitY = 100 @@ -3377,7 +3514,7 @@ void Sidebar::on_filaments_delete(size_t filament_id) Layout(); p->m_panel_filament_title->Refresh(); update_ui_from_settings(); - dynamic_filament_list.update(); + update_dynamic_filament_list(); update_mixed_filament_panel(); } @@ -3754,7 +3891,7 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn) if (m_sync_dlg->is_dirty_filament()) { wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0], false, "", false, true); wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); - dynamic_filament_list.update(); + update_dynamic_filament_list(); } m_sync_dlg->set_check_dirty_fialment(false); dlg_res = m_sync_dlg->ShowModal(); @@ -3980,7 +4117,8 @@ void Sidebar::show_SEMM_buttons() void Sidebar::update_dynamic_filament_list() { - dynamic_filament_list.update(); + dynamic_feature_filament_list.update(); + dynamic_support_filament_list.update(); } // ---------------------------------------------------------------------------