Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ deps_src/build/
.claude/
.hive-mind/
nul
.omc/
.claude-flow/
152 changes: 152 additions & 0 deletions FILAMENT_EXTRUDER_ANALYSIS_FINAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# 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():**
```cpp
// PrintApply.cpp line 1276
this->initialize_filament_extruder_map();
```

**Implementation in Print.cpp (lines 497-544):**
```cpp
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():**
```cpp
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():**
```cpp
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:**
```cpp
// 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.
Loading
Loading