feat: derive physical_extruder_map from printer_extruder_id; use for IMEX PA/temp

Auto-populate physical_extruder_map (0-indexed) from printer_extruder_id
(1-indexed) in Print::apply(). The guard only runs when the map is still
at default size (<= 1 element), so printer profiles that set it explicitly
are untouched.

All IMEX parallel-mode PA and temperature emission now routes tool slot
indices through physical_extruder_map before constructing firmware
extruder qualifiers (EXTRUDER=, M104 T, M572 D). This ensures AFC/MMU
setups where multiple slots share one physical extruder get the correct
qualifier -- e.g. T6 on physical extruder 3 emits EXTRUDER=extruder3
instead of EXTRUDER=extruder6.

Profile authoring for MMU/AFC printers:
  Add physical_extruder_map to the printer profile JSON as a 0-indexed
  string array, one entry per logical filament slot, whose value is the
  physical extruder carrying that slot. The array size must be > 1 for
  the explicit map to override the auto-derive. Example for a 7-slot
  printer with a 4-lane MMU on extruder 0 and three independent direct
  drives on extruders 1/2/3:

    "physical_extruder_map": ["0","0","0","0","1","2","3"]

  Non-MMU printers need no action -- printer_extruder_id already encodes
  the 1:1 mapping and the auto-derive handles it.

Deeper integration (zone validation, collision detection, filament
assignment grouping, Moonraker agent auto-population) is deferred.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-04-21 14:56:22 -04:00
co-authored by Claude Sonnet 4.6
parent 481cd7d8e7
commit 0530d6c4da
2 changed files with 24 additions and 4 deletions
+10 -4
View File
@@ -3232,7 +3232,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
if (tool_idx == (int)initial_extruder_id) continue;
if (!print.config().enable_pressure_advance.get_at(tool_idx)) continue;
file.write(m_writer.set_pressure_advance(
print.config().pressure_advance.get_at(tool_idx), tool_idx));
print.config().pressure_advance.get_at(tool_idx),
m_config.physical_extruder_map.get_at(tool_idx)));
}
}
}
@@ -4696,7 +4697,8 @@ LayerResult GCode::process_layer(
if (tool_idx >= num_nozzles) continue;
int temperature = print.config().nozzle_temperature.values[tool_idx];
if (temperature > 0)
gcode += GCodeWriter::set_temperature(temperature, m_writer.config.gcode_flavor, false, tool_idx, "set IMEX tool temperature");
gcode += GCodeWriter::set_temperature(temperature, m_writer.config.gcode_flavor, false,
m_config.physical_extruder_map.get_at(tool_idx), "set IMEX tool temperature");
}
} else {
for (const Extruder& extruder : m_writer.extruders()) {
@@ -7563,7 +7565,9 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
// In primary mode (single active tool) regular tool changes handle PA
// so no qualifier is needed — same as a non-IMEX printer.
const bool imex_parallel = !m_imex_parallel_mode.empty() && m_imex_parallel_mode != "primary";
const int pa_tool = imex_parallel ? (int)new_filament_id : -1;
const int pa_tool = imex_parallel
? m_config.physical_extruder_map.get_at((int)new_filament_id)
: -1;
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id), pa_tool);
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
@@ -7864,7 +7868,9 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
const bool imex_parallel = !m_imex_parallel_mode.empty() && m_imex_parallel_mode != "primary";
const int pa_tool = imex_parallel ? (int)new_filament_id : -1;
const int pa_tool = imex_parallel
? m_config.physical_extruder_map.get_at((int)new_filament_id)
: -1;
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id), pa_tool);
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
+14
View File
@@ -1161,6 +1161,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
//update print config related with variants
new_full_config.update_values_to_printer_extruders(new_full_config, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
// 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.
{
auto* pem = new_full_config.option<ConfigOptionInts>("physical_extruder_map", true);
const auto* pei = new_full_config.option<ConfigOptionInts>("printer_extruder_id");
if (pem && pei && !pei->values.empty() && pem->values.size() <= 1) {
pem->values.clear();
for (int v : pei->values)
pem->values.push_back(v - 1); // convert 1-indexed extruder IDs to 0-indexed
}
}
m_ori_full_print_config = new_full_config;
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant");
auto opt_filament_map = new_full_config.option<ConfigOptionInts>("filament_map");