6.2 KiB
Filament-Extruder Mapping Analysis - Final Findings
The Key Discovery
After investigating the original implementation vs the optimized version, I found the critical missing piece: initialize_filament_extruder_map().
How It Actually Works
The Initialization Process
Called in PrintApply.cpp during apply():
// PrintApply.cpp line 1276
this->initialize_filament_extruder_map();
Implementation in Print.cpp (lines 497-544):
void Print::initialize_filament_extruder_map()
{
m_filament_extruder_map.clear();
// Get the number of physical extruders
size_t physical_extruder_count = m_config.nozzle_diameter.values.size();
// Get ALL configured filaments (not just used ones)
std::vector<unsigned int> filament_extruders = this->extruders();
if (filament_extruders.empty()) {
size_t filament_count = m_config.filament_diameter.size();
for (size_t i = 0; i < filament_count; ++i) {
filament_extruders.push_back((unsigned int)i);
}
}
// Create mapping: filament_id -> physical_extruder_id
// Mapping formula: physical_extruder = filament_id % physical_extruder_count
for (unsigned int filament_idx : filament_extruders) {
int physical_extruder = filament_idx % physical_extruder_count;
m_filament_extruder_map[filament_idx] = physical_extruder;
}
}
The Result
After initialization:
- Filament 0 → Extruder 0
- Filament 1 → Extruder 1
- Filament 2 → Extruder 2
- Filament 3 → Extruder 3
- Filament 4 → Extruder 0 (modulo)
- Filament 5 → Extruder 1 (modulo)
- Filament 6 → Extruder 2 (modulo)
- Filament 7 → Extruder 3 (modulo)
The map is ALWAYS populated before slicing!
Why Both Versions Work
Original Implementation (9d423d0714)
get_physical_extruder():
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
// Fallback: identity mapping (filament 5 → extruder 5)
int physical_extruder_id = (it != m_filament_extruder_map.end()) ? it->second : filament_idx;
return physical_extruder_id;
}
Why it worked: The fallback logic was never reached because initialize_filament_extruder_map() always populates the map with modulo mapping.
Optimized Implementation (989a53e124)
get_physical_extruder():
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
if (it != m_filament_extruder_map.end()) {
return it->second;
} else {
// Fallback: modulo mapping (filament 5 → extruder 1)
size_t physical_count = m_config.nozzle_diameter.values.size();
return filament_idx % physical_count;
}
}
Why it also works: Same reason - the map is always populated. The fallback is just defensive programming.
What Actually Changed
The key difference between the two versions is NOT the behavior during normal operation (both work the same), but:
- Defensive programming: The optimized version has a safer fallback (modulo instead of identity)
- Code simplification: The optimized version removed some redundant physical_extruder calculations in GCode.cpp
- Better logging: The optimized version has more detailed logging
- Validation: The optimized version added bounds checking in PrintApply.cpp
The Real Issue: Why 3MF Slicing Might Fail
If the user is experiencing issues with >4 filament 3MF slicing, it's NOT because of the identity vs modulo fallback difference (since that code path is never reached).
Possible causes:
-
initialize_filament_extruder_map() not being called: Check if there's a code path that bypasses Print::apply()
-
Config array access using filament_id instead of physical_extruder_id: The optimized version removed some explicit physical_extruder calculations in GCode.cpp. If those changes introduced direct filament_id access to config arrays, that would cause crashes.
-
3MF file format issues: The 3MF file might have inconsistent filament/extruder configurations
-
Placeholder replacement: GCode placeholder macros might still be using filament_id instead of physical_extruder_id
Code Changes That Matter
Potentially Problematic Change in GCode.cpp
Optimized version removed explicit physical_extruder calculation:
// BEFORE (9d423d0714):
int previous_physical_extruder = (previous_extruder_id >= 0) ?
gcode_writer.get_physical_extruder(previous_extruder_id) : -1;
int new_physical_extruder = gcode_writer.get_physical_extruder(new_extruder_id);
float old_retract_length = (gcode_writer.extruder() != nullptr && previous_physical_extruder >= 0) ?
full_config.retraction_length.get_at(previous_physical_extruder) : 0;
// AFTER (989a53e124):
float old_retract_length = (gcode_writer.extruder() != nullptr && previous_extruder_id >= 0) ?
full_config.retraction_length.get_at(previous_extruder_id) : 0; // Uses filament_id directly!
This is a BUG! The optimized version uses previous_extruder_id (filament_id) directly to access retraction_length array. This will cause array out-of-bounds access when filament_id >= physical_extruder_count.
Recommendation
The optimized version (989a53e124) may have introduced regressions by removing explicit physical_extruder calculations. Need to:
- Audit all config array access in GCode.cpp to ensure physical_extruder_id is used
- Add bounds checking or use the PHYSICAL_EXTRUDER_CONFIG macro consistently
- Test with 8-filament 3MF on 4-extruder configuration
Summary
| Aspect | Original (9d423d0714) |
Optimized (989a53e124) |
|---|---|---|
| Modulo mapping | ✓ Via initialize_filament_extruder_map() | ✓ Via initialize_filament_extruder_map() |
| Fallback logic | Identity (never used) | Modulo (never used) |
| Config array access | Explicit physical_extruder calculation | Some direct filament_id access (BUG?) |
| Safety | Good | Potentially introduced bugs |
Bottom line: Both versions use the same modulo mapping via initialize_filament_extruder_map(), but the optimized version may have introduced bugs by simplifying config array access.