diff --git a/src/libslic3r/MixedFilament.cpp b/src/libslic3r/MixedFilament.cpp index 3f1921772b..0bba9a4d7e 100644 --- a/src/libslic3r/MixedFilament.cpp +++ b/src/libslic3r/MixedFilament.cpp @@ -1479,6 +1479,60 @@ std::string compute_mixed_filament_display_color(const MixedFilament &entry, con mix_b); } +std::string mixed_filament_standardized_name(const MixedFilament &entry, size_t num_physical) +{ + const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(entry.manual_pattern); + if (!normalized_pattern.empty()) { + const std::string flattened = flatten_manual_pattern_groups(normalized_pattern); + return std::string("Pattern ") + (flattened.empty() ? normalized_pattern : flattened); + } + + std::vector> parts; + parts.reserve(4); + auto append_part = [&parts](unsigned int component_id, int percent) { + if (component_id == 0 || percent <= 0) + return; + for (auto &part : parts) { + if (part.first == component_id) { + part.second += percent; + return; + } + } + parts.emplace_back(component_id, percent); + }; + + const std::vector gradient_ids = decode_gradient_component_ids(entry.gradient_component_ids, num_physical); + if (gradient_ids.size() >= 3) { + std::vector weights = decode_gradient_component_weights(entry.gradient_component_weights, gradient_ids.size()); + if (weights.empty()) + weights.assign(gradient_ids.size(), 1); + const std::vector percentages = normalize_weight_vector_to_percent(weights); + for (size_t idx = 0; idx < gradient_ids.size() && idx < percentages.size(); ++idx) + append_part(gradient_ids[idx], percentages[idx]); + } else { + const unsigned int component_a = (entry.component_a >= 1 && entry.component_a <= num_physical) + ? entry.component_a + : 1; + const unsigned int component_b = (entry.component_b >= 1 && entry.component_b <= num_physical) + ? entry.component_b + : component_a; + const int pct_b = clamp_int(entry.mix_b_percent, 0, 100); + append_part(component_a, 100 - pct_b); + append_part(component_b, pct_b); + } + + if (parts.empty()) + return "1:100%"; + + std::ostringstream out; + for (size_t idx = 0; idx < parts.size(); ++idx) { + if (idx > 0) + out << " + "; + out << parts[idx].first << ':' << parts[idx].second << '%'; + } + return out.str(); +} + // --------------------------------------------------------------------------- // MixedFilamentManager // --------------------------------------------------------------------------- diff --git a/src/libslic3r/MixedFilament.hpp b/src/libslic3r/MixedFilament.hpp index 16d678ba41..cad52fa85d 100644 --- a/src/libslic3r/MixedFilament.hpp +++ b/src/libslic3r/MixedFilament.hpp @@ -144,6 +144,11 @@ std::pair mixed_filament_apparent_pair_percentages(const MixedFilament bool bias_mode_enabled); std::string compute_mixed_filament_display_color(const MixedFilament &entry, const MixedFilamentDisplayContext &context); +// Build a standardized user-facing mixed filament name. +// - Pattern rows: "Pattern " (for example "Pattern 1212354"). +// - Mix rows: ":% + :% ..." (for example "1:30% + 2:20% + 3:50%"). +std::string mixed_filament_standardized_name(const MixedFilament &entry, size_t num_physical); + // --------------------------------------------------------------------------- // MixedFilamentManager // diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 18811ef241..dcb689c489 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -263,6 +263,33 @@ bool BitmapChoiceRenderer::GetValue(wxVariant& value) const return true; } +static std::vector mixed_aware_extruder_icons(bool thin_icon = false) +{ + std::vector icons = get_extruder_color_icons(thin_icon); + + if (Slic3r::GUI::wxGetApp().plater() == nullptr) + return icons; + + const std::vector all_colors = + Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, true); + if (all_colors.size() <= icons.size()) + return icons; + + const double em = Slic3r::GUI::wxGetApp().em_unit(); + const int icon_width = int((thin_icon ? 2.0 : 4.4) * em + 0.5); + const int icon_height = int(2.0 * em + 0.5); + for (size_t idx = icons.size(); idx < all_colors.size(); ++idx) { + if (all_colors[idx].empty()) { + icons.push_back(nullptr); + continue; + } + + icons.push_back(get_extruder_color_icon(all_colors[idx], std::to_string(idx + 1), icon_width, icon_height)); + } + + return icons; +} + bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) { const wxBitmap& icon = m_value.GetBitmap(); @@ -301,7 +328,7 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR if (can_create_editor_ctrl && !can_create_editor_ctrl()) return nullptr; - std::vector icons = get_extruder_color_icons(); + std::vector icons = mixed_aware_extruder_icons(false); if (icons.empty()) return nullptr; @@ -317,12 +344,14 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR c_editor->Append(_L("default"), *get_default_extruder_color_icon()); for (size_t i = 0; i < icons.size(); i++) - c_editor->Append(wxString::Format("%d", i+1), *icons[i]); + c_editor->Append(wxString::Format("%d", i + 1), icons[i] ? *icons[i] : wxNullBitmap); - if (has_default_extruder && has_default_extruder()) - c_editor->SetSelection(atoi(data.GetText().c_str())); - else - c_editor->SetSelection(atoi(data.GetText().c_str()) - 1); + int selection = (has_default_extruder && has_default_extruder()) + ? atoi(data.GetText().c_str()) + : atoi(data.GetText().c_str()) - 1; + if (selection < 0 || selection >= int(c_editor->GetCount())) + selection = 0; + c_editor->SetSelection(selection); c_editor->Bind(wxEVT_SET_FOCUS, [c_editor](wxFocusEvent& evt) { #ifdef __WXGTK__ diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 853fda16ab..13014c0373 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -56,7 +56,7 @@ static std::vector ui_ordered_filament_ids() return wxGetApp().plater()->sidebar().get_ui_ordered_filament_ids(); } -static wxString filament_menu_item_name(const int filament_id_1based, const int display_filament_id_1based) +static wxString filament_menu_item_name(const int filament_id_1based) { if (filament_id_1based <= 0) return _L("Default"); @@ -76,7 +76,12 @@ static wxString filament_menu_item_name(const int filament_id_1based, const int return wxString::Format(_L("Filament %d"), filament_id_1based); } - return wxString::Format(_L("Mixed Filament %d"), display_filament_id_1based); + const auto &mgr = wxGetApp().preset_bundle->mixed_filaments; + const MixedFilament *mixed = mgr.mixed_filament_from_id(unsigned(filament_id_1based), size_t(physical)); + if (mixed == nullptr) + return _L("Mixed Filament"); + + return from_u8(mixed_filament_standardized_name(*mixed, size_t(physical))); } static bool is_improper_category(const std::string& category, const int filaments_cnt, const bool is_object_settings = true) @@ -1108,7 +1113,7 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu) const bool is_active_extruder = actual_filament_id == initial_extruder; const int icon_idx = actual_filament_id == 0 ? 0 : actual_filament_id - 1; - wxString item_name = filament_menu_item_name(actual_filament_id, int(display_idx)); + wxString item_name = filament_menu_item_name(actual_filament_id); if (is_active_extruder) { item_name << " (" + _L("current") + ")"; @@ -2286,7 +2291,7 @@ void MenuFactory::append_menu_item_change_filament(wxMenu* menu) const int actual_filament_id = display_idx == 0 ? 0 : int(ordered_filament_ids[display_idx - 1]); bool is_active_extruder = false; - wxString item_name = filament_menu_item_name(actual_filament_id, int(display_idx)); + wxString item_name = filament_menu_item_name(actual_filament_id); if (is_active_extruder) { item_name << " (" + _L("current") + ")"; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index cec3fbc7dc..98a067fdc0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -6551,9 +6551,9 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) // BBS: remove "const" qualifier void ObjectList::set_extruder_for_selected_items(const int extruder) { - // BBS: check extruder id - std::vector colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); - if (extruder > colors.size()) + // Accept any configured filament id, including mixed virtual filaments. + const int max_filaments = filaments_count(); + if (extruder < 0 || extruder > max_filaments) return; wxDataViewItemArray sels; diff --git a/src/slic3r/GUI/GUI_ObjectTable.cpp b/src/slic3r/GUI/GUI_ObjectTable.cpp index e47c32ccdf..b3898b0430 100644 --- a/src/slic3r/GUI/GUI_ObjectTable.cpp +++ b/src/slic3r/GUI/GUI_ObjectTable.cpp @@ -2804,13 +2804,28 @@ int ObjectTablePanel::init_bitmap() m_undo_bitmap = create_scaled_bitmap("lock_normal", nullptr, 18); m_color_bitmaps = get_extruder_color_icons(); + const std::vector all_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, true); + if (all_colors.size() > m_color_bitmaps.size()) { + const double em = wxGetApp().em_unit(); + const int icon_width = int(4.4 * em + 0.5); + const int icon_height = int(2.0 * em + 0.5); + for (size_t idx = m_color_bitmaps.size(); idx < all_colors.size(); ++idx) { + if (all_colors[idx].empty()) { + m_color_bitmaps.push_back(nullptr); + continue; + } + + m_color_bitmaps.push_back(get_extruder_color_icon(all_colors[idx], std::to_string(idx + 1), icon_width, icon_height)); + } + } + return 0; } int ObjectTablePanel::init_filaments_and_colors() { const std::vector filament_presets = wxGetApp().preset_bundle->filament_presets; - const std::vector filament_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + const std::vector filament_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, true); m_filaments_count = filament_colors.size(); if (m_filaments_count <= 0) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not get filaments, count: %1%, set to default") % m_filaments_count; @@ -2844,9 +2859,9 @@ int ObjectTablePanel::init_filaments_and_colors() continue; } - m_filaments_name[i] = wxString::Format("%d: Mixed Filament %d (F%u + F%u)", - i + 1, i + 1, - unsigned(mf.component_a), unsigned(mf.component_b)); + m_filaments_name[i] = wxString::Format("%d: %s", + i + 1, + from_u8(mixed_filament_standardized_name(mf, physical_count))); break; } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 7944718e59..2112e8b025 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -423,15 +423,31 @@ void ObjectDataViewModelNode::UpdateExtruderAndColorIcon(wxString extruder /*= " } } + const size_t extruder_id_1based = extruder_idx; if (extruder_idx > 0) --extruder_idx; + // Create the bitmap with color bars. std::vector bmps = get_extruder_color_icons(false);// use wide icons - if (bmps.empty()) { - m_extruder_bmp = wxNullBitmap; + if (!bmps.empty() && extruder_idx < bmps.size()) { + m_extruder_bmp = *bmps[extruder_idx]; return; } - m_extruder_bmp = *bmps[extruder_idx >= bmps.size() ? 0 : extruder_idx]; + // Fallback for mixed virtual filaments when the shared icon vector only covers physical filaments. + if (wxGetApp().plater() != nullptr) { + const std::vector all_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, true); + if (extruder_id_1based >= 1 && extruder_id_1based <= all_colors.size() && !all_colors[extruder_id_1based - 1].empty()) { + const double em = wxGetApp().em_unit(); + const int icon_width = int(4.4 * em + 0.5); + const int icon_height = int(2.0 * em + 0.5); + if (wxBitmap *bmp = get_extruder_color_icon(all_colors[extruder_id_1based - 1], std::to_string(extruder_id_1based), icon_width, icon_height)) { + m_extruder_bmp = *bmp; + return; + } + } + } + + m_extruder_bmp = bmps.empty() ? wxNullBitmap : *bmps.front(); } // ***************************************************************************** diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b48b7064bd..2b5276b7ca 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -963,9 +963,17 @@ struct DynamicFilamentList : DynamicList return wxString::Format(_L("Filament %d"), int(filament_id_1based)); } - static wxString mixed_filament_label() + static wxString mixed_filament_label(unsigned int filament_id_1based, size_t num_physical) { - return _L("Mixed Filament"); + if (wxGetApp().preset_bundle == nullptr) + return _L("Mixed Filament"); + + const auto &mgr = wxGetApp().preset_bundle->mixed_filaments; + const MixedFilament *mixed = mgr.mixed_filament_from_id(filament_id_1based, num_physical); + if (mixed == nullptr) + return _L("Mixed Filament"); + + return from_u8(mixed_filament_standardized_name(*mixed, num_physical)); } static wxBitmap *fallback_icon_from_color(unsigned int filament_id_1based, @@ -994,7 +1002,7 @@ struct DynamicFilamentList : DynamicList wxString label = filament_id_1based <= num_physical ? physical_filament_label(filament_id_1based) - : mixed_filament_label(); + : mixed_filament_label(filament_id_1based, num_physical); items.push_back({label, icon}); filament_ids.push_back(int(filament_id_1based)); @@ -4430,14 +4438,14 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) for (const std::string &hex : physical_colors) palette.emplace_back(parse_mixed_color(hex)); - auto mixed_summary_text = [&mixed](size_t mixed_id) -> wxString { + auto mixed_display_name = [&mixed, num_physical](size_t mixed_id) -> wxString { if (mixed_id >= mixed.size()) return wxString(); const MixedFilament &entry = mixed[mixed_id]; - if (!entry.custom) - return wxString::Format("(Filament %u + Filament %u)", unsigned(entry.component_a), unsigned(entry.component_b)); - const std::string normalized = MixedFilamentManager::normalize_manual_pattern(entry.manual_pattern); - if (!normalized.empty()) return _L("(Pattern)"); - return wxString::Format("(F%u + F%u)", unsigned(entry.component_a), unsigned(entry.component_b)); + return from_u8(mixed_filament_standardized_name(entry, num_physical)); + }; + + auto mixed_summary_text = [](size_t) -> wxString { + return wxString(); }; auto apply_mixed_entry_changes = [this, preset_bundle, print_cfg, num_physical] @@ -4545,9 +4553,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) swatch->SetMinSize(wxSize(FromDIP(12), FromDIP(12))); header_sizer->Add(swatch, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x); - const int virtual_filament_id = int(num_physical + display_idx + 1); - auto *name_label = new wxStaticText(header_panel, wxID_ANY, - wxString::Format("Mixed Filament %d", virtual_filament_id)); + auto *name_label = new wxStaticText(header_panel, wxID_ANY, mixed_display_name(mixed_id)); name_label->SetForegroundColour(mixed_text_fg); header_sizer->Add(name_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x); @@ -4642,9 +4648,9 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) }; auto ensure_editor = [this, mixed_id, num_physical, physical_colors, nozzle_diameters, palette, - preview_settings, component_bias_enabled, preset_bundle, - editor_host, editor_sizer, swatch, summary_label, header_panel, row, - rows_scroller, mixed_summary_text, apply_mixed_entry_changes, &mixed]() { + preview_settings, component_bias_enabled, preset_bundle, + editor_host, editor_sizer, swatch, name_label, summary_label, header_panel, row, + rows_scroller, mixed_display_name, mixed_summary_text, apply_mixed_entry_changes, &mixed]() { if (!preset_bundle || !editor_sizer || editor_sizer->GetItemCount() > 0) return; auto &mgr2 = preset_bundle->mixed_filaments; auto &mfs2 = mgr2.mixed_filaments(); @@ -4653,14 +4659,16 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) editor_host, mixed_id, mfs2[mixed_id], num_physical, physical_colors, nozzle_diameters, palette, preview_settings, component_bias_enabled, - [this, mixed_id, swatch, summary_label, header_panel, row, rows_scroller, - mixed_summary_text, apply_mixed_entry_changes] + [this, mixed_id, swatch, name_label, summary_label, header_panel, row, rows_scroller, + mixed_display_name, mixed_summary_text, apply_mixed_entry_changes] (const MixedFilament &updated_mf) { apply_mixed_entry_changes(mixed_id, updated_mf, true); if (swatch) { swatch->SetBackgroundColour(parse_mixed_color(updated_mf.display_color)); swatch->Refresh(); } + if (name_label) + name_label->SetLabel(mixed_display_name(mixed_id)); if (summary_label) summary_label->SetLabel(mixed_summary_text(mixed_id)); if (header_panel) header_panel->Layout(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 66ad109fab..e245bacc81 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -783,6 +783,24 @@ void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, bool use_thin_icon/* = false*/) { std::vector icons = get_extruder_color_icons(use_thin_icon); + if (dynamic_cast(parent) != nullptr && Slic3r::GUI::wxGetApp().plater() != nullptr) { + const std::vector all_colors = + Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, true); + + if (all_colors.size() > icons.size()) { + const double em = Slic3r::GUI::wxGetApp().em_unit(); + const int icon_width = int((use_thin_icon ? 2.0 : 4.4) * em + 0.5); + const int icon_height = int(2.0 * em + 0.5); + for (size_t idx = icons.size(); idx < all_colors.size(); ++idx) { + if (all_colors[idx].empty()) { + icons.push_back(nullptr); + continue; + } + + icons.push_back(get_extruder_color_icon(all_colors[idx], std::to_string(idx + 1), icon_width, icon_height)); + } + } + } if (!*ctrl) { *ctrl = new Slic3r::GUI::BitmapComboBox(parent, wxID_ANY, wxEmptyString, pos, size, 0, nullptr, wxCB_READONLY); @@ -811,13 +829,13 @@ void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, for (wxBitmap* bmp : icons) { if (i == 0) { if (!first_item.empty()) - (*ctrl)->Append(_(first_item), *bmp); + (*ctrl)->Append(_(first_item), bmp ? *bmp : wxNullBitmap); ++i; } (*ctrl)->Append(use_full_item_name ? Slic3r::GUI::from_u8((boost::format("%1% %2%") % str % i).str()) - : wxString::Format("%d", i), *bmp); + : wxString::Format("%d", i), bmp ? *bmp : wxNullBitmap); ++i; } (*ctrl)->SetSelection(0);