Bound the second-layer temperature lookup in filament-slot space

nozzle_temperature is variant-expanded, so its length is columns rather than
filament slots. On the dynamic-nozzle path that makes it longer than the
slot count, and an out-of-slot index reached get_filament_config_index() and
came back as filament 0 - the clamp the bounds-checking was meant to remove.
Bound by the slot count instead; on the ordinary path the two are equal and
nothing changes. The is_extruder_used write gains the matching lower-bound
guard.

IMEXHelpers.hpp now states both halves of the rule its call sites follow.
Bound anything derived from the extruder map against the filament slot count
before using it as a filament id, not against the option about to be read.
And a miss is -1, which is a correct tool qualifier but matches no physical
head, so it cannot serve as a skip-the-primary sentinel: which head prints a
filament is answered by the filament and the map, never by a mode role,
since a primary-mode print may use any or all tools, one at a time.

The consequence is recorded there rather than left implicit. The two skip
sites skip nothing for a slot past the end of the map, so a plate with more
slots than nozzles double-writes the primary's pressure advance. It is
narrow and unreported, and a guard there would be a smaller change than
naming a head.

The header also records that RepRapFirmware sends an unqualified pressure
advance as M572 D0, naming drive 0 absolutely rather than the active tool.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-09-18 01:32:27 -04:00
co-authored by Claude Opus 5
parent c62385214f
commit 3c31856b85
3 changed files with 50 additions and 8 deletions
+17 -6
View File
@@ -2883,8 +2883,8 @@ static std::vector<int> get_imex_active_tools(const Print& print)
if (active_mode == kImexPrimaryMode)
return active_tools;
// An unresolved mode, and a mode the tools array is too short to cover, both hand back
// an empty tools string, which parses to no tools.
// An unresolved mode, and a mode the tools array is too short to cover, both hand back an
// empty tools string, which parses to no tools.
for (const auto& [phys, role] : parse_imex_active_tools(find_imex_mode(print.config(), active_mode).active_tools))
active_tools.push_back(phys);
return active_tools;
@@ -3418,16 +3418,23 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
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;
// Bounds-checked, not get_at() -- see IMEXHelpers.hpp. The value is a skip-primary
// sentinel below, so a clamp would suppress whichever head sits at pem[0].
// Bounds-checked, not get_at(): a clamp would suppress whichever head sits at pem[0].
// A miss stays -1 and matches no head, so nothing is skipped -- see IMEXHelpers.hpp.
const int primary_physical =
((int) initial_extruder_id >= 0 &&
(int) initial_extruder_id < (int) pem.values.size())
? pem.values[(int) initial_extruder_id]
: -1;
// Bound by the array, not by the filament count. The array is padded to
// MAXIMUM_EXTRUDER_NUMBER on purpose: start G-code addresses HEADS through it
// (fdm_toolchanger_common.json gates M104 T0..T5 on it), and a parallel copy print has
// more heads than filaments by definition. PlaceholderParser clamps an out-of-range
// first_layer_temperature read to filament 0, which is the right temperature when every
// head is printing the same filament. Narrowing this to the filament count leaves the
// secondary carriages unheated.
for (int logical : imex_secondary_logical_slots(
get_imex_active_tools(print), primary_physical, plate_head_map, pem))
if (logical < (int)is_extruder_used.size())
if (logical >= 0 && logical < (int) is_extruder_used.size())
is_extruder_used[logical] = true;
}
@@ -5955,7 +5962,11 @@ LayerResult GCode::process_layer(
// Mutually exclusive with the `else` below, so a head skipped here gets no
// transition at all. `tool_idx` is physical; the printing head uses this layer's
// own filament, the parallel carriages resolve through the head map.
const int num_filament_columns = (int)print.config().nozzle_temperature.values.size();
// nozzle_temperature is variant-expanded, so its length is columns, not slots:
// bound in slot space, or an out-of-slot logical reaches get_filament_config_index
// and comes back as filament 0.
const int num_filament_columns = std::min((int) print.config().nozzle_temperature.values.size(),
(int) print.config().filament_diameter.values.size());
// Bounds-checked, not get_at() -- see IMEXHelpers.hpp. A clamp would hand the
// "initial" branch below to whichever secondary sits on pem[0], giving it the wrong
// filament's transition temperature and never its own.