From d41e0d09b497fc4f0fcee9ea51e62b6787cae35e Mon Sep 17 00:00:00 2001 From: Clifford Garwood Date: Sat, 25 Apr 2026 04:02:55 -0400 Subject: [PATCH] fix(imex): skip primary in IMEX is_extruder_used marking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a38b95bf45 translated physical -> logical for IMEX active tools but used resolve_filament_for_head for *all* active tools including the primary. For the primary, that falls back to first_filament_for_physical_head — which returns the FIRST logical slot routed to the primary's physical extruder, not the slot the user actually assigned to the printing object. On the user's Neo XP 0.6 (pem [0,0,0,0,1,2,3,3]) printing in copy mode with the object on slot 2 (PLA dark grey): - tool_ordering correctly marks slot 2 (object's filament). - The IMEX-marking loop then *also* marked slot 0 (ABS) as the primary's "first-routed" slot — wrong: slot 0 isn't loaded, slot 2 is. - Start-gcode template emitted EXTRUDER=260 EXTRUDER2=235 EXTRUDER4=235; the ABS-temp emission for slot 0 was harmless noise in this macro design but conceptually bogus. The primary's filament is already correctly covered by tool_ordering.all_extruders() — that lists the slots the objects on the plate are assigned to. Skip the primary in the IMEX-marking loop using the same pattern as the IMEX PA emission path at GCode.cpp:3265 (translate initial_extruder_id -> physical via pem, skip that physical). Result on the user's setup post-fix: - is_extruder_used: slot 2 (object), slot 4 (T1's filament in copy). - Start-gcode emits EXTRUDER2=235 EXTRUDER4=235 — exactly two temps, one per active heater, with no spurious ABS bookkeeping. Co-Authored-By: Claude Opus 4.7 --- src/libslic3r/GCode.cpp | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 61b8640d62..ee936e95ca 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2869,22 +2869,35 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato for (unsigned int extruder : tool_ordering.all_extruders()) is_extruder_used[extruder] = true; - // Orca IMEX: also mark the LOGICAL filament slot that each active secondary - // carriage will load during the print. is_extruder_used is logical-slot indexed + // Orca IMEX: mark the LOGICAL filament slot each active SECONDARY carriage + // will load during the print. is_extruder_used is logical-slot indexed // (consumed as `is_extruder_used[N]` in machine_start_gcode templates), but - // get_imex_active_tools returns PHYSICAL extruder indices. On any printer with - // physical_extruder_map size > 1 (MMU/AFC), writing the physical index into a - // logical-indexed array marks the wrong slot — start-gcode templates would emit - // the wrong filament's temp / not emit the correct one. + // get_imex_active_tools returns PHYSICAL extruder indices. On any printer + // with physical_extruder_map size > 1 (MMU/AFC), writing the physical index + // into a logical-indexed array marks the wrong slot. // - // Translate physical -> logical via the per-plate imex_head_filament_map (set by - // the IMEX ghost picker), with first_filament_for_physical_head as the fallback - // when no override is set. - if (print.config().is_imex.value && !print.objects().empty()) { + // The primary is intentionally SKIPPED here — its filament is already + // covered by tool_ordering.all_extruders() above (which lists the slots the + // objects on the plate are actually assigned to). Marking the primary + // again via the pem `first_filament_for_physical_head` fallback would + // pollute is_extruder_used with the *first* slot routed to the primary's + // physical extruder, which is generally not the slot the user assigned to + // the printing object. Same skip-primary pattern as the IMEX PA emission + // path (GCode.cpp ~3265). + // + // For secondaries: translate physical -> logical via the per-plate + // imex_head_filament_map (set by the IMEX ghost picker), with + // first_filament_for_physical_head as the fallback when no override is set. + if (print.config().is_imex.value && !print.objects().empty() + && initial_extruder_id != (unsigned int)-1) { const auto plate_head_map = parse_imex_head_filament_map( print.objects().front()->config().imex_head_filament_map.value); const ConfigOptionInts& pem = print.config().physical_extruder_map; + const int primary_physical = pem.values.empty() + ? -1 + : pem.get_at((int)initial_extruder_id); for (int phys_tool : get_imex_active_tools(print)) { + if (phys_tool == primary_physical) continue; const int logical = resolve_filament_for_head(plate_head_map, pem, phys_tool); if (logical >= 0 && logical < (int)is_extruder_used.size()) is_extruder_used[logical] = true;