fix(imex): primary's filament lookup must come from object assignment, not pem default

The pre-slice warning's primary-tool filament lookup (added in
fbc58d2a1d) used resolve_filament_for_head, which returns the FIRST
slot routed to the primary's physical extruder via pem. That's the
right rule for *secondaries* (no object owns them in copy/mirror mode;
filament comes from the per-plate imex_head_filament_map override) but
wrong for the *primary*: the primary prints the actual objects on the
plate, and the slot it uses is whatever the user assigned to those
objects — not whatever happens to be at the head of the AFC manifold.

Symptom on the user's Neo XP 0.6:
  - pem = [0,0,0,0,1,2,3,3] (slots 0-3 share AFC manifold on physical T0)
  - filaments: slot 0 ABS, slot 2 PLA, slots 4-6 PLA, etc.
  - object assigned to slot 2 (PLA), IMEX mode "copy" (T0 primary, T1 copy)
  - User picks slot 5 PLA for T1 via the IMEX ghost picker (writes a
    per-plate imex_head_filament_map).
  - Warning reads slot 0 (ABS) for primary because that's
    first_filament_for_physical_head(pem, 0). Fires "T0 ABS vs T1 PLA
    type mismatch" even though the actual print uses slot 2 (PLA) for
    the primary — slicer and warning disagree.

Split the lookup:
  logical_for_primary(physical_idx)
    Walks plate->get_extruders(true) (1-based slots used by objects on
    this plate), returns the first slot whose pem entry maps to
    physical_idx. Falls back to first_filament_for_physical_head if no
    object on the plate routes to this physical extruder (defensive).

  logical_for_secondary(physical_idx)
    Unchanged behavior: per-plate imex_head_filament_map override with
    first_filament_for_physical_head fallback.

The user-facing "T%d" labels still display the physical extruder index
(carriage identity); only the filament-info lookups change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-04-25 05:07:34 -04:00
co-authored by Claude Opus 4.7
parent 0db99d21dd
commit c8e7c5678b
+27 -6
View File
@@ -10022,19 +10022,40 @@ static std::vector<wxString> collect_imex_warnings(PartPlate* plate)
// imex_mode_active_tools). filament_presets and bed_temps are indexed by LOGICAL
// filament slot. For MMU/AFC layouts where multiple logical slots feed one
// physical extruder, looking up filament_presets[physical_idx] returns the wrong
// filament. Translate physical -> logical via the effective pem (with per-plate
// imex_head_filament_map override) before indexing.
// filament. Translate physical -> logical before indexing.
//
// Primary vs secondary use DIFFERENT translation rules:
// - Primary's filament: the slot the user assigned to the printing object(s).
// Walk plate->get_extruders() and pick a slot whose pem entry maps to the
// primary's physical index. If multiple slots qualify, the first match wins.
// - Secondary's filament: per-plate imex_head_filament_map override (set via
// the IMEX ghost picker). No object owns a secondary in copy/mirror mode —
// the firmware duplicates the primary, so the override is the only source.
// - Both fall back to first_filament_for_physical_head as a last resort so a
// warning still has *some* filament to name.
const ConfigOptionInts pem = effective_physical_extruder_map(*bundle);
std::map<int, int> plate_head_map;
if (auto* hfm = plate->config()->option<ConfigOptionString>("imex_head_filament_map"))
plate_head_map = parse_imex_head_filament_map(hfm->value);
auto logical_for = [&](int physical_idx) -> int {
auto logical_for_primary = [&](int physical_idx) -> int {
// get_extruders() returns 1-based filament slots used by objects on this plate.
const std::vector<int> used_slots_1b = plate->get_extruders(true);
for (int slot_1b : used_slots_1b) {
const int slot_0b = slot_1b - 1;
if (slot_0b >= 0 && slot_0b < (int)pem.values.size()
&& pem.values[slot_0b] == physical_idx)
return slot_0b;
}
return first_filament_for_physical_head(pem, physical_idx);
};
auto logical_for_secondary = [&](int physical_idx) -> int {
const int logical = resolve_filament_for_head(plate_head_map, pem, physical_idx);
return logical >= 0 ? logical : physical_idx; // fallback: identity if no routing found
return logical >= 0 ? logical : physical_idx;
};
// Primary tool's bed temp and filament type (looked up by logical slot)
const int primary_logical = logical_for(primary_tool);
const int primary_logical = logical_for_primary(primary_tool);
const int primary_bed_temp = (bed_temps && primary_logical < (int)bed_temps->values.size())
? bed_temps->values[primary_logical] : 0;
std::string primary_display_type;
@@ -10048,7 +10069,7 @@ static std::vector<wxString> collect_imex_warnings(PartPlate* plate)
const int tool_idx = active_tools[i];
if (tool_idx == primary_tool) continue;
const int secondary_logical = logical_for(tool_idx);
const int secondary_logical = logical_for_secondary(tool_idx);
std::string secondary_display_type;
if (secondary_logical < (int)filament_presets.size()) {