fix(imex): size physical_extruder_map from the nozzle count

physical_extruder_map has one entry per logical extruder -- the index space of
nozzle_diameter -- and its consumers size their own arrays from that count. It was
being derived from printer_extruder_id, which is indexed by variant slot: one entry
per extruder+variant pair. An X1 Carbon has one nozzle and printer_extruder_id
{1,1}; an H2D 0.4 has two nozzles and {1,1,2,2,2}. The two spaces coincide only
when every extruder declares a single variant.

The visible effect was on the standby cool-down. set_extruder skips it when the
outgoing and incoming filaments share a physical extruder, and that check is not
gated on IMEX. With the map built from the wrong array, two filaments on a
dual-nozzle machine read as sharing one hotend and the cool-down was dropped --
caught by "Toolchange temperature commands are unchanged when the wipe tower wait
is off", which failed on all five CI platforms with the ;cooldown line missing.

Derive the identity over the nozzle count instead, the same fallback Plater.cpp
already applies where a profile authors no map. A profile counts as authoring one
only when its length matches the nozzle count, so the single-element PrintConfig
default is replaced rather than read as a one-extruder machine. Authored maps pass
through untouched, including the {1,0} numbering permutation the BBL dual-nozzle
profiles ship.

Tests pin the four branches and the length invariant the consumers depend on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-08-07 12:20:52 -04:00
co-authored by Claude Opus 5
parent 1a30c49993
commit df4009e734
4 changed files with 62 additions and 52 deletions
+4 -6
View File
@@ -1230,15 +1230,13 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
}
}
// Derive physical_extruder_map (0-indexed) from printer_extruder_id (1-indexed) when the
// map hasn't been explicitly configured in the printer profile (size <= 1 = default).
// This gives all firmware code a consistent slot → physical-extruder translation,
// including AFC/MMU setups where multiple tool slots share one physical extruder.
// Fill in physical_extruder_map when the printer profile has not authored one. It has one
// entry per logical extruder, so it is sized from the nozzle count -- not from
// printer_extruder_id, which is indexed by variant slot.
{
auto* pem = new_full_config.option<ConfigOptionInts>("physical_extruder_map", true);
const auto* pei = new_full_config.option<ConfigOptionInts>("printer_extruder_id");
if (pem) {
pem->values = effective_physical_extruder_map(pem, pei).values;
pem->values = effective_physical_extruder_map(pem, extruder_count).values;
// m_ori_full_print_config was snapshotted above, before this derivation, and the
// selector write-back path rebuilds m_full_print_config from that snapshot. Without
// mirroring the derived map into it, m_full_print_config keeps the unexpanded default