From 1e1d9cbaf82e08e54d802f33c3edd88939c96d94 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 8 Jun 2026 18:57:52 +0800 Subject: [PATCH] Fix crash on printer switch from stale filament/extruder indices (#14103) Switching to a printer with fewer filaments (e.g. H2D -> X2D) threw std::out_of_range in check_filament_printable. Clear stale per-volume extruder config on count shrink and bound-check filament indices at the read sites. --- src/libslic3r/Model.cpp | 11 +++++++++++ src/slic3r/GUI/GCodeViewer.cpp | 6 ++++++ src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++++ src/slic3r/GUI/PartPlate.cpp | 15 ++++++++++++--- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0067684f15..d1ca3cf8c0 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2573,6 +2573,12 @@ void ModelVolume::update_extruder_count(size_t extruder_count) break; } } + // Clear a stale per-volume filament assignment that no longer exists after the extruder count + // shrank (e.g. printer switch to one with fewer filaments), so downstream readers never index + // per-filament config vectors out of range. Ported from BambuStudio (STUDIO-15763). + if (extruder_id() > extruder_count) { + this->config.erase("extruder"); + } } void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_count, size_t filament_id, int replace_filament_id) @@ -2584,6 +2590,11 @@ void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_cou break; } } + // Same stale-assignment cleanup as update_extruder_count, for the filament-delete path. + // Ported from BambuStudio (STUDIO-15763). + if (extruder_id() > extruder_count) { + this->config.erase("extruder"); + } } void ModelVolume::center_geometry_after_creation(bool update_source_offset) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 5dac81b7fe..79afc1daaf 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2639,8 +2639,14 @@ void GCodeViewer::render_all_plates_stats(const std::vectorget_slice_result()->print_statistics; auto plate_extruders = plate->get_extruders(true); + auto max_extruders_colors = wxGetApp().plater()->get_extruders_colors().size(); for (size_t extruder_id : plate_extruders) { extruder_id -= 1; + // Skip stale/overflow extruder indices (e.g. from object assignments that outlived a + // filament-count change) so downstream per-extruder lookups stay in range. Ported + // from BambuStudio (STUDIO-15763). + if (extruder_id >= max_extruders_colors) + continue; if (plate_print_statistics.model_volumes_per_extruder.find(extruder_id) == plate_print_statistics.model_volumes_per_extruder.end()) model_volume_of_extruders_all_plates[extruder_id] += 0; else { diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index bc36f20260..6b612dc5e0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -764,6 +764,10 @@ void ObjectList::update_filament_values_for_items(const size_t filaments_count) if (!object->volumes[id]->config.has("extruder") || size_t(object->volumes[id]->config.extruder()) > filaments_count) { extruder = wxString::Format("%d", object->config.extruder()); + // Clear the stale per-volume assignment so it falls back to the object's + // extruder; otherwise the out-of-range index survives. Ported from + // BambuStudio (STUDIO-15763). + object->volumes[id]->config.erase("extruder"); } else { extruder = wxString::Format("%d", object->volumes[id]->config.extruder()); diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index b3b4d4b9a5..e4980e168c 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -1931,11 +1931,20 @@ bool PartPlate::check_filament_printable(const DynamicPrintConfig &config, wxStr std::vector used_filaments = get_extruders(true); // 1 base if (!used_filaments.empty()) { + const std::vector& filament_types = config.option("filament_type")->values; + const std::vector& filament_printables = config.option("filament_printable")->values; + const std::vector& filament_map = get_real_filament_maps(config); + // This runs synchronously mid printer-switch (load_current_preset -> reload_scene), before the + // filament-count reconciliation clears stale per-object assignments, so the plate objects can + // still reference filament indices beyond the freshly selected printer config. Skip those to + // avoid an out-of-range access. Matches BambuStudio's guards in the same function. + const int filament_count = std::min({(int) filament_types.size(), (int) filament_printables.size(), (int) filament_map.size()}); for (auto filament_idx : used_filaments) { int filament_id = filament_idx - 1; - std::string filament_type = config.option("filament_type")->values.at(filament_id); - int filament_printable_status = config.option("filament_printable")->values.at(filament_id); - std::vector filament_map = get_real_filament_maps(config); + if (filament_id < 0 || filament_id >= filament_count) + continue; + std::string filament_type = filament_types[filament_id]; + int filament_printable_status = filament_printables[filament_id]; int extruder_idx = filament_map[filament_id] - 1; if (!(filament_printable_status >> extruder_idx & 1)) { wxString extruder_name = extruder_idx == 0 ? _L("left") : _L("right");