From c8e7c5678baa2ce1842812475492437ec23c9e7d Mon Sep 17 00:00:00 2001 From: Clifford Garwood Date: Sat, 25 Apr 2026 03:09:34 -0400 Subject: [PATCH] fix(imex): primary's filament lookup must come from object assignment, not pem default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-slice warning's primary-tool filament lookup (added in fbc58d2a1d) used resolve_filament_for_head, which returns the FIRST slot routed to the primary's physical extruder via pem. That's the right rule for *secondaries* (no object owns them in copy/mirror mode; filament comes from the per-plate imex_head_filament_map override) but wrong for the *primary*: the primary prints the actual objects on the plate, and the slot it uses is whatever the user assigned to those objects — not whatever happens to be at the head of the AFC manifold. Symptom on the user's Neo XP 0.6: - pem = [0,0,0,0,1,2,3,3] (slots 0-3 share AFC manifold on physical T0) - filaments: slot 0 ABS, slot 2 PLA, slots 4-6 PLA, etc. - object assigned to slot 2 (PLA), IMEX mode "copy" (T0 primary, T1 copy) - User picks slot 5 PLA for T1 via the IMEX ghost picker (writes a per-plate imex_head_filament_map). - Warning reads slot 0 (ABS) for primary because that's first_filament_for_physical_head(pem, 0). Fires "T0 ABS vs T1 PLA type mismatch" even though the actual print uses slot 2 (PLA) for the primary — slicer and warning disagree. Split the lookup: logical_for_primary(physical_idx) Walks plate->get_extruders(true) (1-based slots used by objects on this plate), returns the first slot whose pem entry maps to physical_idx. Falls back to first_filament_for_physical_head if no object on the plate routes to this physical extruder (defensive). logical_for_secondary(physical_idx) Unchanged behavior: per-plate imex_head_filament_map override with first_filament_for_physical_head fallback. The user-facing "T%d" labels still display the physical extruder index (carriage identity); only the filament-info lookups change. Co-Authored-By: Claude Opus 4.7 --- src/slic3r/GUI/Plater.cpp | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c462428a87..10bc8a6cbf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -10022,19 +10022,40 @@ static std::vector collect_imex_warnings(PartPlate* plate) // imex_mode_active_tools). filament_presets and bed_temps are indexed by LOGICAL // filament slot. For MMU/AFC layouts where multiple logical slots feed one // physical extruder, looking up filament_presets[physical_idx] returns the wrong - // filament. Translate physical -> logical via the effective pem (with per-plate - // imex_head_filament_map override) before indexing. + // filament. Translate physical -> logical before indexing. + // + // Primary vs secondary use DIFFERENT translation rules: + // - Primary's filament: the slot the user assigned to the printing object(s). + // Walk plate->get_extruders() and pick a slot whose pem entry maps to the + // primary's physical index. If multiple slots qualify, the first match wins. + // - Secondary's filament: per-plate imex_head_filament_map override (set via + // the IMEX ghost picker). No object owns a secondary in copy/mirror mode — + // the firmware duplicates the primary, so the override is the only source. + // - Both fall back to first_filament_for_physical_head as a last resort so a + // warning still has *some* filament to name. const ConfigOptionInts pem = effective_physical_extruder_map(*bundle); std::map plate_head_map; if (auto* hfm = plate->config()->option("imex_head_filament_map")) plate_head_map = parse_imex_head_filament_map(hfm->value); - auto logical_for = [&](int physical_idx) -> int { + + auto logical_for_primary = [&](int physical_idx) -> int { + // get_extruders() returns 1-based filament slots used by objects on this plate. + const std::vector used_slots_1b = plate->get_extruders(true); + for (int slot_1b : used_slots_1b) { + const int slot_0b = slot_1b - 1; + if (slot_0b >= 0 && slot_0b < (int)pem.values.size() + && pem.values[slot_0b] == physical_idx) + return slot_0b; + } + return first_filament_for_physical_head(pem, physical_idx); + }; + auto logical_for_secondary = [&](int physical_idx) -> int { const int logical = resolve_filament_for_head(plate_head_map, pem, physical_idx); - return logical >= 0 ? logical : physical_idx; // fallback: identity if no routing found + return logical >= 0 ? logical : physical_idx; }; // Primary tool's bed temp and filament type (looked up by logical slot) - const int primary_logical = logical_for(primary_tool); + const int primary_logical = logical_for_primary(primary_tool); const int primary_bed_temp = (bed_temps && primary_logical < (int)bed_temps->values.size()) ? bed_temps->values[primary_logical] : 0; std::string primary_display_type; @@ -10048,7 +10069,7 @@ static std::vector collect_imex_warnings(PartPlate* plate) const int tool_idx = active_tools[i]; if (tool_idx == primary_tool) continue; - const int secondary_logical = logical_for(tool_idx); + const int secondary_logical = logical_for_secondary(tool_idx); std::string secondary_display_type; if (secondary_logical < (int)filament_presets.size()) {