From e93ce996d34a8e0747582aa09b89f467fc721086 Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Fri, 8 May 2026 18:08:57 +0100 Subject: [PATCH] Make texture mapping panel UI elements match the other slicer controls Make volume extruder ID take priority over object extruder even when an object only has a single volume --- src/libslic3r/Format/bbs_3mf.cpp | 12 +- src/libslic3r/TextureMapping.cpp | 6 +- src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_App.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 217 +++++++++++++++++++++++-------- 5 files changed, 181 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index facf4c641b5..da5eaded0e8 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -2905,7 +2905,17 @@ static void append_triangle_material_data(std::vector &uv_valid, mo->config.set_key_value("extruder", new ConfigOptionInt(1)); if (mo->volumes.size() == 1) { - mo->volumes[0]->config.erase("extruder"); + ModelVolume* mv = mo->volumes[0]; + const ConfigOptionInt* vol_extruder_opt = dynamic_cast(mv->config.option("extruder")); + if (vol_extruder_opt != nullptr) { + const int vol_extruder_id = vol_extruder_opt->getInt(); + if (vol_extruder_id > 0 && + (vol_extruder_id <= max_filament_id || + is_texture_mapping_virtual_filament_id(config, vol_extruder_id, size_t(max_filament_id))) && + vol_extruder_id != extruder_id) + mo->config.set_key_value("extruder", new ConfigOptionInt(vol_extruder_id)); + } + mv->config.erase("extruder"); } else { for (ModelVolume* mv : mo->volumes) { diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 7ef822ee605..f8770f65e5c 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -1427,8 +1427,10 @@ std::vector TextureMappingManager::effective_texture_component_ids }; if (zone.force_sequential_filaments) { - for (unsigned int id = 1; result.size() < expected && id <= num_physical; ++id) { - if (std::find(selected.begin(), selected.end(), id) != selected.end() && id < used.size() && !used[id]) { + for (const unsigned int id : selected) { + if (result.size() >= expected) + break; + if (id >= 1 && id <= num_physical && id < used.size() && !used[id]) { used[id] = true; result.emplace_back(id); } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f8d27c4631d..aa78eeeed83 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -7139,7 +7139,7 @@ bool GUI_App::checked_tab(Tab* tab) // Update UI / Tabs to reflect changes in the currently loaded presets //BBS: add preset combo box re-activate logic -void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool check_printer_presets_ /*= true*/) +void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool check_printer_presets_ /*= true*/, bool sync_filaments_to_nozzles /*= true*/) { // check printer_presets for the containing information about "Print Host upload" // and create physical printer from it, if any exists @@ -7150,7 +7150,7 @@ void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool ch PrinterTechnology printer_technology = edited_printer_preset.printer_technology(); // ORCA: Sync filament count with the printer's nozzle count before loading presets for multi-tool printers. // This ensures filament_presets vector is properly sized when combo boxes are created/updated. - if (printer_technology == ptFFF && !edited_printer_preset.config.opt_bool("single_extruder_multi_material")) { + if (sync_filaments_to_nozzles && printer_technology == ptFFF && !edited_printer_preset.config.opt_bool("single_extruder_multi_material")) { auto* nozzle_diameter = edited_printer_preset.config.option("nozzle_diameter"); if (nozzle_diameter) { preset_bundle->set_num_filaments(nozzle_diameter->values.size()); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 0cbc93221ef..3b074e18e4d 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -559,7 +559,7 @@ public: bool check_print_host_queue(); bool checked_tab(Tab* tab); //BBS: add preset combox re-active logic - void load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true); + void load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true, bool sync_filaments_to_nozzles = true); std::vector &get_delete_cache_presets(); std::vector get_delete_cache_presets_lock(); void delete_preset_from_cloud(std::string setting_id); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6f0cbe86ec5..a7d7200b9ab 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -145,7 +145,9 @@ #include "Widgets/RoundedRectangle.hpp" #include "Widgets/RadioGroup.hpp" #include "Widgets/CheckBox.hpp" +#include "Widgets/ComboBox.hpp" #include "Widgets/Button.hpp" +#include "Widgets/SpinInput.hpp" #include "Widgets/StaticGroup.hpp" #include "GUI_ObjectTable.hpp" @@ -590,17 +592,6 @@ static std::vector texture_mapping_selected_ids(const TextureMappi return ids; } -static std::vector texture_mapping_checkbox_ordered_ids(const TextureMappingZone &zone, size_t num_physical) -{ - const std::vector selected = texture_mapping_selected_ids(zone, num_physical); - std::vector ordered; - ordered.reserve(selected.size()); - for (size_t id = 1; id <= std::min(num_physical, 9); ++id) - if (std::find(selected.begin(), selected.end(), unsigned(id)) != selected.end()) - ordered.emplace_back(unsigned(id)); - return ordered.size() >= 2 ? ordered : selected; -} - static std::string encode_texture_mapping_float_values(const std::vector &values) { std::ostringstream ss; @@ -1264,7 +1255,7 @@ public: update_strength_offsets_visibility(false); m_force_sequential_filaments_checkbox = - new wxCheckBox(filament_page, wxID_ANY, _L("Force sequential order for filaments")); + new wxCheckBox(filament_page, wxID_ANY, _L("Trust color order for filaments (don't detect colors)")); m_force_sequential_filaments_checkbox->SetValue(force_sequential_filaments); filament_root->Add(m_force_sequential_filaments_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); @@ -1779,8 +1770,8 @@ private: { if (m_strength_offsets_toggle_button != nullptr) m_strength_offsets_toggle_button->SetLabel(m_strength_offsets_expanded ? - _L("Hide Filament Strengths & Offsets") : - _L("Show Filament Strengths & Offsets")); + _L("Hide Filament Strengths and Offsets") : + _L("Show Filament Strengths and Offsets")); if (m_strength_offsets_panel != nullptr) m_strength_offsets_panel->Show(m_strength_offsets_expanded); if (!fit_dialog) @@ -5456,6 +5447,55 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) const int gap = FromDIP(6); p->m_panel_texture_mapping_content->SetBackgroundColour(rows_bg); + auto make_sidebar_combo = [](wxWindow *parent, const wxArrayString &choices, int selection, const wxSize &size) { + auto *combo = new ::ComboBox(parent, wxID_ANY, wxString(), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY); + for (unsigned int i = 0; i < choices.GetCount(); ++i) + combo->Append(choices[i]); + if (choices.GetCount() > 0) + combo->SetSelection(std::clamp(selection, 0, int(choices.GetCount() - 1))); + return combo; + }; + + auto repaint_sidebar_checkbox = [](::CheckBox *checkbox) { + if (checkbox == nullptr) + return; + checkbox->Refresh(); + checkbox->Update(); + }; + + auto set_sidebar_checkbox_value = [repaint_sidebar_checkbox](::CheckBox *checkbox, bool value) { + if (checkbox == nullptr) + return; + checkbox->SetValue(value); + repaint_sidebar_checkbox(checkbox); + }; + + auto make_sidebar_checkbox_row = [text_fg, gap, repaint_sidebar_checkbox, set_sidebar_checkbox_value](wxWindow *parent, + ::CheckBox *&checkbox, + const wxString &label, + bool value) { + auto *row = new wxBoxSizer(wxHORIZONTAL); + checkbox = new ::CheckBox(parent); + set_sidebar_checkbox_value(checkbox, value); + checkbox->Bind(wxEVT_TOGGLEBUTTON, [checkbox, repaint_sidebar_checkbox](wxCommandEvent &evt) { + repaint_sidebar_checkbox(checkbox); + evt.Skip(); + }); + auto *text = new wxStaticText(parent, wxID_ANY, label); + text->SetForegroundColour(text_fg); + text->Bind(wxEVT_LEFT_DOWN, [checkbox, set_sidebar_checkbox_value](wxMouseEvent &) { + if (checkbox == nullptr || !checkbox->IsEnabled()) + return; + set_sidebar_checkbox_value(checkbox, !checkbox->GetValue()); + wxCommandEvent event(wxEVT_TOGGLEBUTTON, checkbox->GetId()); + event.SetEventObject(checkbox); + checkbox->GetEventHandler()->ProcessEvent(event); + }); + row->Add(checkbox, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(2, gap / 2)); + row->Add(text, 0, wxALIGN_CENTER_VERTICAL); + return row; + }; + std::vector palette; palette.reserve(physical_colors.size()); for (const std::string &color : physical_colors) @@ -5596,44 +5636,56 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) wxArrayString surface_choices; surface_choices.Add(_L("Image Texture")); surface_choices.Add(_L("2D Gradient")); - auto *surface_choice = new wxChoice(editor, wxID_ANY, wxDefaultPosition, wxDefaultSize, surface_choices); - surface_choice->SetSelection(entry.is_2d_gradient() ? 1 : 0); + auto *surface_choice = make_sidebar_combo(editor, surface_choices, entry.is_2d_gradient() ? 1 : 0, wxSize(FromDIP(170), -1)); surface_row->Add(surface_choice, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); editor_sizer->Add(surface_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); auto *filaments_row = new wxWrapSizer(wxHORIZONTAL, wxWRAPSIZER_DEFAULT_FLAGS); filaments_row->Add(new wxStaticText(editor, wxID_ANY, _L("Filaments")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); - std::vector filament_checks; + std::vector<::CheckBox*> filament_checks; const std::vector selected_ids = texture_mapping_selected_ids(entry, num_physical); for (size_t i = 1; i <= std::min(num_physical, 9); ++i) { - auto *chk = new wxCheckBox(editor, wxID_ANY, wxString::Format("F%d", int(i))); - chk->SetForegroundColour(text_fg); - chk->SetValue(std::find(selected_ids.begin(), selected_ids.end(), unsigned(i)) != selected_ids.end()); + ::CheckBox *chk = nullptr; + auto *chk_row = make_sidebar_checkbox_row(editor, + chk, + wxString::Format("F%d", int(i)), + std::find(selected_ids.begin(), selected_ids.end(), unsigned(i)) != selected_ids.end()); filament_checks.emplace_back(chk); - filaments_row->Add(chk, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(FromDIP(2), gap / 2)); + filaments_row->Add(chk_row, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(FromDIP(2), gap / 2)); } editor_sizer->Add(filaments_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); auto *mode_row = new wxWrapSizer(wxHORIZONTAL, wxWRAPSIZER_DEFAULT_FLAGS); mode_row->Add(new wxStaticText(editor, wxID_ANY, _L("Filament colors")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); - auto *mode_choice = new wxChoice(editor, wxID_ANY, wxDefaultPosition, wxDefaultSize, texture_mapping_color_mode_choices()); - mode_choice->SetSelection(texture_mapping_color_mode_selection(entry.filament_color_mode)); + auto *mode_choice = make_sidebar_combo(editor, + texture_mapping_color_mode_choices(), + texture_mapping_color_mode_selection(entry.filament_color_mode), + wxSize(FromDIP(220), -1)); mode_row->Add(mode_choice, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); - auto *preview_colors_chk = new wxCheckBox(editor, wxID_ANY, _L("Preview Result Colors")); - preview_colors_chk->SetValue(entry.preview_simulate_colors); - mode_row->Add(preview_colors_chk, 0, wxALIGN_CENTER_VERTICAL); + ::CheckBox *preview_colors_chk = nullptr; + mode_row->Add(make_sidebar_checkbox_row(editor, preview_colors_chk, _L("Preview Result Colors"), entry.preview_simulate_colors), + 0, + wxALIGN_CENTER_VERTICAL); editor_sizer->Add(mode_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); auto *contrast_row = new wxWrapSizer(wxHORIZONTAL, wxWRAPSIZER_DEFAULT_FLAGS); contrast_row->Add(new wxStaticText(editor, wxID_ANY, _L("Texture contrast")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); - auto *contrast_spin = new wxSpinCtrl(editor, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(72), -1), - wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, - 25, 300, std::clamp(int(std::lround(entry.contrast_pct)), 25, 300)); + auto *contrast_spin = new ::SpinInput(editor, + wxEmptyString, + wxEmptyString, + wxDefaultPosition, + wxSize(FromDIP(86), -1), + wxSP_ARROW_KEYS, + 25, + 300, + std::clamp(int(std::lround(entry.contrast_pct)), 25, 300), + 5); contrast_row->Add(contrast_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); contrast_row->Add(new wxStaticText(editor, wxID_ANY, _L("%")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); - auto *high_res_chk = new wxCheckBox(editor, wxID_ANY, _L("High-resolution texture sampling")); - high_res_chk->SetValue(entry.high_resolution_sampling); - contrast_row->Add(high_res_chk, 0, wxALIGN_CENTER_VERTICAL); + ::CheckBox *high_res_chk = nullptr; + contrast_row->Add(make_sidebar_checkbox_row(editor, high_res_chk, _L("High-resolution texture sampling"), entry.high_resolution_sampling), + 0, + wxALIGN_CENTER_VERTICAL); editor_sizer->Add(contrast_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); auto *button_row = new wxBoxSizer(wxHORIZONTAL); @@ -5697,15 +5749,31 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) }; update_pattern_visibility(); - surface_choice->Bind(wxEVT_CHOICE, [update_pattern_visibility, apply_controls](wxCommandEvent &) { + surface_choice->Bind(wxEVT_COMBOBOX, [update_pattern_visibility, apply_controls](wxCommandEvent &) { update_pattern_visibility(); apply_controls(); }); - for (wxCheckBox *chk : filament_checks) + for (::CheckBox *chk : filament_checks) if (chk != nullptr) - chk->Bind(wxEVT_CHECKBOX, [apply_controls](wxCommandEvent &) { apply_controls(); }); + chk->Bind(wxEVT_TOGGLEBUTTON, [apply_controls](wxCommandEvent &evt) { + apply_controls(); + evt.Skip(); + }); int last_mode_selection = mode_choice->GetSelection(); - mode_choice->Bind(wxEVT_CHOICE, [this, zone_index, mgr_ptr, num_physical, physical_colors, filament_checks, mode_choice, apply_controls, last_mode_selection](wxCommandEvent &) mutable { + auto on_mode_choice = [this, + zone_index, + mgr_ptr, + num_physical, + physical_colors, + filament_checks, + mode_choice, + surface_choice, + preview_colors_chk, + contrast_spin, + high_res_chk, + set_sidebar_checkbox_value, + apply_zone, + last_mode_selection](wxCommandEvent &) mutable { if (mode_choice != nullptr) { const int selection = mode_choice->GetSelection(); if (selection == texture_mapping_five_toolhead_marker_selection()) { @@ -5714,25 +5782,63 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) } last_mode_selection = selection; } - if (zone_index < mgr_ptr->zones().size()) { - TextureMappingZone &zone = mgr_ptr->zones()[zone_index]; - if (zone.auto_adjust_filament_selection) { - TextureMappingZone adjusted = zone; - adjusted.filament_color_mode = texture_mapping_color_mode_from_selection( - mode_choice != nullptr ? mode_choice->GetSelection() : -1, zone.filament_color_mode); - TextureMappingManager::auto_adjust_texture_component_ids(adjusted, num_physical, physical_colors); - const std::vector adjusted_ids = texture_mapping_selected_ids(adjusted, num_physical); - for (size_t idx = 0; idx < filament_checks.size(); ++idx) - if (filament_checks[idx] != nullptr) - filament_checks[idx]->SetValue(std::find(adjusted_ids.begin(), adjusted_ids.end(), unsigned(idx + 1)) != adjusted_ids.end()); + if (zone_index >= mgr_ptr->zones().size()) + return; + + TextureMappingZone updated = mgr_ptr->zones()[zone_index]; + std::vector ids; + for (size_t idx = 0; idx < filament_checks.size(); ++idx) + if (filament_checks[idx] != nullptr && filament_checks[idx]->GetValue()) + ids.emplace_back(unsigned(idx + 1)); + if (ids.size() < 2) + ids = texture_mapping_selected_ids(updated, num_physical); + + updated.enabled = true; + updated.surface_pattern = surface_choice != nullptr && surface_choice->GetSelection() == 1 ? + int(TextureMappingZone::Gradient2D) : int(TextureMappingZone::ImageTexture); + updated.component_ids = encode_texture_mapping_component_ids(ids); + updated.component_a = ids.empty() ? 1 : ids.front(); + updated.component_b = ids.size() > 1 ? ids[1] : updated.component_a; + updated.filament_color_mode = mode_choice != nullptr ? + texture_mapping_color_mode_from_selection(mode_choice->GetSelection(), updated.filament_color_mode) : + updated.filament_color_mode; + updated.preview_simulate_colors = preview_colors_chk != nullptr && preview_colors_chk->GetValue(); + updated.contrast_pct = contrast_spin != nullptr ? std::clamp(float(contrast_spin->GetValue()), 25.f, 300.f) : updated.contrast_pct; + updated.high_resolution_sampling = high_res_chk == nullptr || high_res_chk->GetValue(); + + if (updated.auto_adjust_filament_selection) { + TextureMappingZone adjusted = updated; + if (!adjusted.force_sequential_filaments) { + std::vector candidate_ids; + for (size_t i = 1; i <= std::min(num_physical, 9); ++i) + candidate_ids.emplace_back(unsigned(i)); + if (candidate_ids.size() >= 2) { + adjusted.component_ids = encode_texture_mapping_component_ids(candidate_ids); + adjusted.component_a = candidate_ids[0]; + adjusted.component_b = candidate_ids[1]; + } + } + if (TextureMappingManager::auto_adjust_texture_component_ids(adjusted, num_physical, physical_colors)) { + updated = std::move(adjusted); + ids = texture_mapping_selected_ids(updated, num_physical); } } + for (size_t idx = 0; idx < filament_checks.size(); ++idx) + if (filament_checks[idx] != nullptr) + set_sidebar_checkbox_value(filament_checks[idx], + std::find(ids.begin(), ids.end(), unsigned(idx + 1)) != ids.end()); + apply_zone(std::move(updated)); + }; + mode_choice->Bind(wxEVT_COMBOBOX, on_mode_choice); + preview_colors_chk->Bind(wxEVT_TOGGLEBUTTON, [apply_controls](wxCommandEvent &evt) { apply_controls(); - update_texture_mapping_panel(false); + evt.Skip(); }); - preview_colors_chk->Bind(wxEVT_CHECKBOX, [apply_controls](wxCommandEvent &) { apply_controls(); }); - high_res_chk->Bind(wxEVT_CHECKBOX, [apply_controls](wxCommandEvent &) { apply_controls(); }); - contrast_spin->Bind(wxEVT_SPINCTRL, [apply_controls](wxSpinEvent &) { apply_controls(); }); + high_res_chk->Bind(wxEVT_TOGGLEBUTTON, [apply_controls](wxCommandEvent &evt) { + apply_controls(); + evt.Skip(); + }); + contrast_spin->Bind(wxEVT_SPINCTRL, [apply_controls](wxCommandEvent &) { apply_controls(); }); contrast_spin->Bind(wxEVT_TEXT_ENTER, [apply_controls](wxCommandEvent &) { apply_controls(); }); contrast_spin->Bind(wxEVT_KILL_FOCUS, [apply_controls](wxFocusEvent &evt) { apply_controls(); @@ -5753,15 +5859,18 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) zone_index, mgr_ptr, palette, + physical_colors, apply_zone, bundle, set_config_string](wxCommandEvent &) { if (zone_index >= mgr_ptr->zones().size()) return; TextureMappingZone updated = mgr_ptr->zones()[zone_index]; - const std::vector ids = texture_mapping_checkbox_ordered_ids(updated, palette.size()); + std::vector ids = updated.is_image_texture() ? + TextureMappingManager::effective_texture_component_ids(updated, palette.size(), physical_colors) : + texture_mapping_selected_ids(updated, palette.size()); if (ids.empty()) - return; + ids = texture_mapping_selected_ids(updated, palette.size()); std::vector strengths; std::vector offsets; std::vector transmission_distances; @@ -8645,7 +8754,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ // For exporting from the amf/3mf we shouldn't check printer_presets for the containing information about "Print Host upload" // BBS: add preset combo box re-active logic // currently found only needs re-active here - wxGetApp().load_current_presets(false, false); + wxGetApp().load_current_presets(false, false, false); // Update filament colors for the MM-printer profile in the full config // to avoid black (default) colors for Extruders in the ObjectList, // when for extruder colors are used filament colors