Files
OrcaSlicer/FILAMENT_EXTRUDER_ANALYSIS_FINAL.md
xiaoyeliu d15a146329 Add Filament_Extruder_map (#160)
* Optimize: extruder_filament_map

* Fix: {print_time}
2026-02-09 20:29:53 +08:00

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:

  1. Defensive programming: The optimized version has a safer fallback (modulo instead of identity)
  2. Code simplification: The optimized version removed some redundant physical_extruder calculations in GCode.cpp
  3. Better logging: The optimized version has more detailed logging
  4. 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:

  1. initialize_filament_extruder_map() not being called: Check if there's a code path that bypasses Print::apply()

  2. 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.

  3. 3MF file format issues: The 3MF file might have inconsistent filament/extruder configurations

  4. 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:

  1. Audit all config array access in GCode.cpp to ensure physical_extruder_id is used
  2. Add bounds checking or use the PHYSICAL_EXTRUDER_CONFIG macro consistently
  3. 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.