refactor(imex): extract imex_pem_tool_for helper + unit tests

The physical_extruder_map translation used by IMEX per-tool PA emission was
inlined identically at two sites in GCode.cpp (tool-change and second-layer
transition). Extract to IMEXHelpers so the routing rule ("parallel mode AND
populated pem → physical index, else -1") is testable in isolation and the
call sites read as intent rather than re-deriving the conditional.

Production change is behavior-preserving:
- Same predicate (`!mode.empty() && mode != "primary"`)
- Same empty-pem short-circuit returning -1
- Same get_at() dispatch on hit
- Both call sites replaced with a single call

Four unit tests in [IMEX] cover the routing matrix:
  - non-IMEX ("") and primary mode short-circuit
  - parallel mode + empty pem short-circuits (defense-in-depth; get_at would
    throw on empty values otherwise)
  - identity pem (non-MMU IDEX) routes filament to itself
  - MMU collapse routes multiple logical slots to one physical (7-slot profile
    with 4-lane MMU on physical 0 and direct drives on 1/2/3)

All IMEX + Variant regression suites pass post-refactor (133 assertions / 48
cases under libslic3r, 25 assertions / 6 cases under fff_print [PressureAdvance]).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-04-24 23:07:23 -04:00
co-authored by Claude Opus 4.7
parent cb15f35444
commit c2492ccc47
4 changed files with 52 additions and 12 deletions
+2 -12
View File
@@ -7589,13 +7589,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
// In IMEX parallel modes each carriage needs an explicit tool address.
// In primary mode (single active tool) regular tool changes handle PA
// so no qualifier is needed — same as a non-IMEX printer.
// Guard the pem lookup: PrintApply populates pem when printer_extruder_id
// is set, but defense-in-depth prevents a throw from get_at on any
// empty-pem path that might slip through in exotic profiles.
const bool imex_parallel = !m_imex_parallel_mode.empty() && m_imex_parallel_mode != "primary";
const int pa_tool = (imex_parallel && !m_config.physical_extruder_map.values.empty())
? m_config.physical_extruder_map.get_at((int)new_filament_id)
: -1;
const int pa_tool = imex_pem_tool_for((int)new_filament_id, m_imex_parallel_mode, m_config.physical_extruder_map);
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
@@ -7895,11 +7889,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
gcode += m_ooze_prevention.post_toolchange(*this);
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
// Empty-pem guard mirrors the earlier PA site; get_at throws on empty values.
const bool imex_parallel = !m_imex_parallel_mode.empty() && m_imex_parallel_mode != "primary";
const int pa_tool = (imex_parallel && !m_config.physical_extruder_map.values.empty())
? m_config.physical_extruder_map.get_at((int)new_filament_id)
: -1;
const int pa_tool = imex_pem_tool_for((int)new_filament_id, m_imex_parallel_mode, m_config.physical_extruder_map);
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