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
+11 -10
View File
@@ -1,6 +1,7 @@
#include "libslic3r/IMEXHelpers.hpp"
#include <algorithm>
#include <numeric>
#include <cassert>
#include <cctype>
#include <set>
@@ -12,17 +13,17 @@
namespace Slic3r {
ConfigOptionInts effective_physical_extruder_map(const ConfigOptionInts* explicit_pem,
const ConfigOptionInts* printer_extruder_id)
ConfigOptionInts effective_physical_extruder_map(const ConfigOptionInts* explicit_pem, int nozzle_count)
{
if (explicit_pem && explicit_pem->values.size() >= 2)
// One entry per logical extruder, so a profile has authored a map only when its length
// matches. The single-element PrintConfig default does not, and is replaced rather than
// treated as a one-extruder machine.
if (explicit_pem && nozzle_count > 0 && (int) explicit_pem->values.size() == nozzle_count)
return *explicit_pem;
ConfigOptionInts derived;
if (printer_extruder_id) {
derived.values.reserve(printer_extruder_id->values.size());
for (int v : printer_extruder_id->values)
derived.values.push_back(v - 1);
}
derived.values.resize(std::max(nozzle_count, 1));
std::iota(derived.values.begin(), derived.values.end(), 0);
return derived;
}
@@ -31,8 +32,8 @@ ConfigOptionInts effective_physical_extruder_map(const PresetBundle& pb)
const ConfigOptionInts* explicit_pem = pb.project_config.option<ConfigOptionInts>("physical_extruder_map");
if (!explicit_pem || explicit_pem->values.size() < 2)
explicit_pem = pb.printers.get_edited_preset().config.option<ConfigOptionInts>("physical_extruder_map");
const ConfigOptionInts* pei = pb.printers.get_edited_preset().config.option<ConfigOptionInts>("printer_extruder_id");
return effective_physical_extruder_map(explicit_pem, pei);
const auto* nozzles = pb.printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter");
return effective_physical_extruder_map(explicit_pem, nozzles ? (int) nozzles->values.size() : 0);
}
int imex_pem_tool_for(int filament_id, const std::string& parallel_mode, const ConfigOptionInts& pem)