Update from FS at b3c41fda4.

Slicing
  - align merge_segmented_layers shape with FS apply_mm_segmentation
    (size = num_facets_states, loop from 0, no -1 shift); painted mixed
    regions were previously attributed to filament_id-1 of intent.
    apply_fuzzy_skin_segmentation reads channel 1;
    apply_mixed_surface_indentation uses segmentation_channel_filament_id
  - port apply_mixed_surface_indentation, apply_mixed_component_surface_offsets,
    apply_mixed_region_surface_offsets, apply_surface_emboss_mixed_region_override,
    plus surface_emboss_mixed_* debug subsystem
  - refactor apply_mm_segmentation (by-value MM, bias_mode, surface-type-
    preserving intersection, region normalization, post-MM dump); hoist MM
    segmentation into slice_volumes so mixed apply_* flow can mutate it
  - restore clear_local_z_plan() invalidation hooks
    (PrintObject.cpp:805/1264/1286)

  GCode
  - add LayerTools::preserve_extruder_order, honored by collect_extruders,
    both reorder_extruders overloads, and
    reorder_filaments_for_minimum_flush_volume; helpers
    append_unique_preserve_order / remove_duplicates_preserve_order
  - wire MixedFilamentManager::ordered_perimeter_extruders for grouped
    manual-pattern walls; set preserve_extruder_order when >= 2
  - mixed-aware support: layer_height set for support-only layers,
    ExtrusionRole-based has_support/has_interface with erMixed short-
    circuit, support_filament / support_interface_filament routed through
    resolve_mixed

  Print
  - materialize mixed_filament_pointillism_{pixel_size,line_gap} in
    PrintApply's option-tracking block so in-session edits diff correctly

  GUI
  - Tab::on_value_change: dithering_local_z_mode cascading clears, 17-key
    project_config sync, update_mixed_filament_panel(false) on
    mixed_filament_component_bias_enabled change
  - GUI_Factories: physical_filaments_count, ui_ordered_filament_ids,
    filament_menu_item_name; filaments_count includes enabled mixed
    virtuals; right-click 'Change filament' submenus iterate UI-ordered IDs

  Tests
  - sentinel asserting MultiMaterialSegmentation uses FS-aligned shape
    (39 -> 40)
This commit is contained in:
SoftFever
2026-06-01 09:24:54 -03:00
committed by Ian Bassi
parent 0e0e34c8b4
commit c0dfe50bc5
10 changed files with 1516 additions and 184 deletions
+63 -51
View File
@@ -35,9 +35,48 @@ static PrinterTechnology printer_technology()
return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
}
static int physical_filaments_count()
{
return std::max(wxGetApp().filaments_cnt(), 0);
}
static int filaments_count()
{
return wxGetApp().filaments_cnt();
if (wxGetApp().preset_bundle == nullptr)
return 0;
const int physical = physical_filaments_count();
const auto &mixed_mgr = wxGetApp().preset_bundle->mixed_filaments;
return static_cast<int>(mixed_mgr.total_filaments(size_t(physical)));
}
static std::vector<unsigned int> ui_ordered_filament_ids()
{
if (wxGetApp().plater() == nullptr)
return {};
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)
{
if (filament_id_1based <= 0)
return _L("Default");
if (wxGetApp().preset_bundle == nullptr)
return wxString::Format(_L("Filament %d"), filament_id_1based);
const int physical = physical_filaments_count();
if (filament_id_1based <= physical) {
const size_t preset_idx = size_t(filament_id_1based - 1);
const auto &filament_presets = wxGetApp().preset_bundle->filament_presets;
if (preset_idx < filament_presets.size()) {
auto preset = wxGetApp().preset_bundle->filaments.find_preset(filament_presets[preset_idx]);
if (preset != nullptr)
return from_u8(preset->label(false));
}
return wxString::Format(_L("Filament %d"), filament_id_1based);
}
return wxString::Format(_L("Mixed Filament %d"), display_filament_id_1based);
}
static bool is_improper_category(const std::string& category, const int filaments_cnt, const bool is_object_settings = true)
@@ -1042,8 +1081,9 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
menu->Destroy(item_id);
}
const int filaments_cnt = filaments_count();
if (filaments_cnt <= 1)
// Use UI-ordered filament IDs (physical first, then enabled mixed in UI order)
const std::vector<unsigned int> ordered_filament_ids = ui_ordered_filament_ids();
if (ordered_filament_ids.size() <= 1)
return;
wxDataViewItemArray sels;
@@ -1059,24 +1099,16 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
if (sels.Count() == 1) {
const ModelConfig& config = obj_list()->get_item_config(sels[0]);
// BBS: set default extruder to 1
initial_extruder = config.has("extruder") ? config.extruder() : 1;
initial_extruder = config.has("extruder") ? config.extruder() : 0;
}
for (int i = 0; i <= filaments_cnt; i++)
for (size_t display_idx = 0; display_idx <= ordered_filament_ids.size(); ++display_idx)
{
bool is_active_extruder = i == initial_extruder;
int icon_idx = i == 0 ? 0 : i - 1;
const int actual_filament_id = display_idx == 0 ? 0 : int(ordered_filament_ids[display_idx - 1]);
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 = _L("Default");
if (i > 0) {
auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i - 1]);
if (preset == nullptr) {
item_name = wxString::Format(_L("Filament %d"), i);
} else {
item_name = from_u8(preset->label(false));
}
}
wxString item_name = filament_menu_item_name(actual_filament_id, int(display_idx));
if (is_active_extruder) {
item_name << " (" + _L("current") + ")";
@@ -1084,11 +1116,13 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
if (icon_idx >= 0 && icon_idx < icons.size()) {
append_menu_item(
extruder_selection_menu, wxID_ANY, item_name, "", [i](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(i); }, *icons[icon_idx], menu,
extruder_selection_menu, wxID_ANY, item_name, "",
[actual_filament_id](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(actual_filament_id); }, *icons[icon_idx], menu,
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
} else {
append_menu_item(
extruder_selection_menu, wxID_ANY, item_name, "", [i](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(i); }, "", menu,
extruder_selection_menu, wxID_ANY, item_name, "",
[actual_filament_id](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(actual_filament_id); }, "", menu,
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
}
}
@@ -2213,8 +2247,8 @@ void MenuFactory::append_menu_item_change_filament(wxMenu* menu)
menu->Destroy(item_id);
}
int filaments_cnt = filaments_count();
if (filaments_cnt <= 1)
const std::vector<unsigned int> ordered_filament_ids = ui_ordered_filament_ids();
if (ordered_filament_ids.size() <= 1)
return;
wxDataViewItemArray sels;
@@ -2229,12 +2263,10 @@ void MenuFactory::append_menu_item_change_filament(wxMenu* menu)
}
std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
if (icons.size() < filaments_cnt) {
BOOST_LOG_TRIVIAL(warning) << boost::format("Warning: icons size %1%, filaments_cnt=%2%")%icons.size()%filaments_cnt;
if (icons.size() < ordered_filament_ids.size()) {
BOOST_LOG_TRIVIAL(warning) << boost::format("Warning: icons size %1%, filaments_cnt=%2%") % icons.size() % ordered_filament_ids.size();
if (icons.size() <= 1)
return;
else
filaments_cnt = icons.size();
}
wxMenu* extruder_selection_menu = new wxMenu();
const wxString& name = sels.Count() == 1 ? names[0] : names[1];
@@ -2242,47 +2274,27 @@ void MenuFactory::append_menu_item_change_filament(wxMenu* menu)
int initial_extruder = -1; // negative value for multiple object/part selection
if (sels.Count() == 1) {
const ModelConfig& config = obj_list()->get_item_config(sels[0]);
// BBS
const auto sel_vol = obj_list()->get_selected_model_volume();
if (sel_vol && sel_vol->type() == ModelVolumeType::PARAMETER_MODIFIER)
initial_extruder = config.has("extruder") ? config.extruder() : 0;
else
initial_extruder = config.has("extruder") ? config.extruder() : 1;
initial_extruder = config.has("extruder") ? config.extruder() : 0;
}
// BBS
bool has_modifier = false;
for (auto sel : sels) {
if (obj_list()->GetModel()->GetVolumeType(sel) == ModelVolumeType::PARAMETER_MODIFIER) {
has_modifier = true;
break;
}
}
for (int i = has_modifier ? 0 : 1; i <= filaments_cnt; i++)
for (size_t display_idx = 0; display_idx <= ordered_filament_ids.size(); ++display_idx)
{
// BBS
//bool is_active_extruder = i == initial_extruder;
const int actual_filament_id = display_idx == 0 ? 0 : int(ordered_filament_ids[display_idx - 1]);
bool is_active_extruder = false;
wxString item_name = _L("Default");
if (i > 0) {
auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i - 1]);
if (preset == nullptr) {
item_name = wxString::Format(_L("Filament %d"), i);
} else {
item_name = from_u8(preset->label(false));
}
}
wxString item_name = filament_menu_item_name(actual_filament_id, int(display_idx));
if (is_active_extruder) {
item_name << " (" + _L("current") + ")";
}
wxBitmap bm = (i == 0 || size_t(i - 1) >= icons.size()) ? wxNullBitmap : *icons[i - 1];
append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "",
[i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, bm, menu,
[actual_filament_id](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(actual_filament_id); },
actual_filament_id == 0 || size_t(actual_filament_id - 1) >= icons.size() ? wxNullBitmap : *icons[size_t(actual_filament_id - 1)], menu,
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
}
menu->Append(wxID_ANY, name, extruder_selection_menu, _L("Change Filament"));