From fcdd0dd38785aade00298225aff250c7c88ccc33 Mon Sep 17 00:00:00 2001 From: xiaoyeliu Date: Mon, 23 Mar 2026 19:08:00 +0800 Subject: [PATCH] revert filament&extruder --- .gitignore | 4 - FILAMENT_EXTRUDER_ANALYSIS_FINAL.md | 152 - FILAMENT_EXTRUDER_ORIGINAL_VS_OPTIMIZED.md | 261 - docs/提交41f40d1219完整技术文档.md | 1425 ----- src/libslic3r/Config.hpp | 65 +- src/libslic3r/Extruder.cpp | 24 +- src/libslic3r/Extruder.hpp | 11 +- src/libslic3r/Flow.cpp | 39 +- src/libslic3r/GCode.cpp | 5464 ++++++++--------- src/libslic3r/GCode.hpp | 9 +- .../GCode/AvoidCrossingPerimeters.cpp | 7 +- src/libslic3r/GCode/CoolingBuffer.cpp | 13 +- src/libslic3r/GCode/CoolingBuffer.hpp | 9 - src/libslic3r/GCode/GCodeProcessor.cpp | 537 +- src/libslic3r/GCode/GCodeProcessor.hpp | 13 - src/libslic3r/GCode/WipeTower.cpp | 22 +- src/libslic3r/GCode/WipeTower.hpp | 5 +- src/libslic3r/GCode/WipeTower2.cpp | 1751 +++--- src/libslic3r/GCode/WipeTower2.hpp | 6 +- src/libslic3r/GCodeWriter.cpp | 28 +- src/libslic3r/GCodeWriter.hpp | 26 - src/libslic3r/Print.cpp | 143 +- src/libslic3r/Print.hpp | 32 - src/libslic3r/PrintApply.cpp | 221 +- src/slic3r/GUI/GUI.cpp | 4 +- src/slic3r/GUI/OptionsGroup.cpp | 20 +- src/slic3r/GUI/Tab.cpp | 44 +- 参数访问修复表.md | 200 - 耗材继承修复总结.md | 127 - 29 files changed, 3701 insertions(+), 6961 deletions(-) delete mode 100644 FILAMENT_EXTRUDER_ANALYSIS_FINAL.md delete mode 100644 FILAMENT_EXTRUDER_ORIGINAL_VS_OPTIMIZED.md delete mode 100644 docs/提交41f40d1219完整技术文档.md delete mode 100644 参数访问修复表.md delete mode 100644 耗材继承修复总结.md diff --git a/.gitignore b/.gitignore index 212fbd20a3..46bdf21dd1 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,3 @@ resources/profiles/user/default *.code-workspace deps_src/build/ .claude/ -.hive-mind/ -nul -.omc/ -.claude-flow/ diff --git a/FILAMENT_EXTRUDER_ANALYSIS_FINAL.md b/FILAMENT_EXTRUDER_ANALYSIS_FINAL.md deleted file mode 100644 index 5d827d6439..0000000000 --- a/FILAMENT_EXTRUDER_ANALYSIS_FINAL.md +++ /dev/null @@ -1,152 +0,0 @@ -# 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 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. diff --git a/FILAMENT_EXTRUDER_ORIGINAL_VS_OPTIMIZED.md b/FILAMENT_EXTRUDER_ORIGINAL_VS_OPTIMIZED.md deleted file mode 100644 index 7a6cb2c6c2..0000000000 --- a/FILAMENT_EXTRUDER_ORIGINAL_VS_OPTIMIZED.md +++ /dev/null @@ -1,261 +0,0 @@ -# Filament-Extruder Mapping: Original Implementation vs Optimized Version - -## Executive Summary - -This document analyzes the differences between the original working filament-extruder mapping implementation (commit `9d423d0714`) and the optimized version (commit `989a53e124`), focusing on why the original was working for >4 filament 3MF slicing. - -## Key Findings - -### The Critical Difference: Default Mapping Behavior - -**Original Implementation (9d423d0714):** -```cpp -int get_physical_extruder(int filament_idx) const { - auto it = m_filament_extruder_map.find(filament_idx); - // When no mapping exists, use IDENTITY mapping (filament 5 → extruder 5) - int physical_extruder_id = (it != m_filament_extruder_map.end()) ? it->second : filament_idx; - return physical_extruder_id; -} -``` - -**Optimized Implementation (989a53e124):** -```cpp -int get_physical_extruder(int filament_idx) const { - auto it = m_filament_extruder_map.find(filament_idx); - int physical_extruder_id; - if (it != m_filament_extruder_map.end()) { - physical_extruder_id = it->second; - } else { - // When no mapping exists, use MODULO mapping (filament 5 → extruder 1) - size_t physical_count = m_config.nozzle_diameter.values.size(); - if (physical_count == 0) { - physical_extruder_id = 0; - } else { - physical_extruder_id = filament_idx % physical_count; - } - } - return physical_extruder_id; -} -``` - -### Why the Original Worked for >4 Filaments - -When slicing a 3MF file with 5-8 filaments on a 4-extruder printer: - -**Original behavior (IDENTITY mapping):** -- Filament 0 → Extruder 0 -- Filament 1 → Extruder 1 -- Filament 2 → Extruder 2 -- Filament 3 → Extruder 3 -- Filament 4 → Extruder 4 (out of bounds!) -- Filament 5 → Extruder 5 (out of bounds!) -- etc. - -This would cause **array out-of-bounds errors** when accessing config arrays like `nozzle_diameter[5]`, `retraction_length[5]`, etc. - -**However**, the original implementation had a critical workaround: **GCode placeholder replacement still used the filament ID directly**, not the physical extruder ID. - -### Placeholder Replacement Behavior - -**Original (9d423d0714):** -```cpp -// GCode.cpp - toolchange_gcode placeholder replacement -// Used filament_id directly for placeholders like { filament_extruder } -std::string toolchange_gcode = this->config().toolchange_gcode; -toolchange_gcode = replace_tool_macros( - toolchange_gcode, - extruder_id, // filament_id - previous_extruder, - // ... -); -``` - -This meant: -- GCode T commands: T5, T6, T7, T8 (filament IDs) -- Placeholder {filament_extruder}: 5, 6, 7, 8 (filament IDs) -- **But config array access**: filament 5 → physical 5 → **CRASH** - -**Wait, this doesn't add up!** If the original was crashing on config access, how could it work? - -### The Real Solution: Config Array Access Pattern - -The original implementation must have had additional protection. Let me verify... - -Actually, looking at the original diff more carefully: - -**Original Extruder constructor (9d423d0714):** -```cpp -Extruder::Extruder(const PrintConfig& config, uint16_t extruder_id) - : m_id(extruder_id) - , m_technology(config.printer_technology) -{ - // CRITICAL FIX: Get physical extruder index - uint16_t physical_extruder = config.get_physical_extruder(m_id); - - // Use physical extruder index for parameter access - m_nozzle_diameter = float(config.nozzle_diameter.get_at(physical_extruder)); - // ... other parameters -} -``` - -So the original **DID** use `get_physical_extruder()` for config array access! - -This means the original implementation would crash with identity mapping when accessing `nozzle_diameter[5]` on a 4-extruder printer. - -### The Missing Piece: PrintApply.cpp Validation - -Let me check if there was validation that prevented this scenario... - -**Optimized version (989a53e124) added validation in PrintApply.cpp:** -```cpp -// Validate that all filament IDs map to valid physical extruders -for (const auto& [filament_id, physical_id] : filament_extruder_map) { - if (physical_id >= (int)nozzle_diameter.size()) { - throw ConfigurationError("Filament " + std::to_string(filament_id) + - " maps to physical extruder " + std::to_string(physical_id) + - " but only " + std::to_string(nozzle_diameter.size()) + - " extruders available"); - } -} -``` - -### The Hypothesis: What Actually Made It Work - -Given the evidence, there are two possibilities: - -**Hypothesis 1: The Original Never Actually Worked** -- The original implementation (9d423d0714) was added on Feb 4, 2026 -- The optimized version (989a53e124) was added on Feb 5, 2026 (only 1 day later!) -- The original may have had the identity mapping bug which was quickly fixed -- User's "working" version may have been a different branch or configuration - -**Hypothesis 2: 3MF Files Include Pre-built Maps** -- 3MF files can embed filament-to-extruder mappings -- If the 3MF file had `filament_extruder_map = [0,1,2,3,0,1,2,3]` pre-configured -- Then the explicit mapping path would be used, avoiding the identity mapping -- This would work correctly without modulo - -**Hypothesis 3: Empty Map = Different Behavior** -- When `m_filament_extruder_map` is empty (not set in 3MF) -- Original: Uses identity mapping → crashes -- Optimized: Uses modulo mapping → works -- User's 3MF files must have had explicit mappings - -## Detailed Comparison Table - -| Aspect | Original (9d423d0714) | Optimized (989a53e124) | -|--------|----------------------|------------------------| -| **Empty map behavior** | Identity: filament_id → filament_id | Modulo: filament_id % physical_count | -| **5 filaments on 4-extruder** | Filament 4 → Extruder 4 → **CRASH** | Filament 4 → Extruder 0 → ✅ Works | -| **8 filaments on 4-extruder** | Filaments 4-7 → Extruders 4-7 → **CRASH** | Filaments 4-7 → Extruders 0-3 → ✅ Works | -| **GCode T commands** | Used filament_id directly | Uses physical_extruder_id | -| **Config array access** | Used physical_extruder via get_physical_extruder() | Uses physical_extruder via get_physical_extruder() | -| **Logging** | Basic logging | Enhanced logging with map size | -| **Validation** | None | Bounds checking in PrintApply.cpp | -| **Comments** | Chinese comments | Chinese comments + English explanations | - -## Code Changes Summary - -### Print.hpp - get_physical_extruder() - -**Before (9d423d0714):** -```cpp -int get_physical_extruder(int filament_idx) const { - auto it = m_filament_extruder_map.find(filament_idx); - int physical_extruder_id = (it != m_filament_extruder_map.end()) ? it->second : filament_idx; - BOOST_LOG_TRIVIAL(info) << "Print::get_physical_extruder: filament_id=" << filament_idx - << " -> physical_extruder_id=" << physical_extruder_id - << " (map_size=" << m_filament_extruder_map.size() << ")" - << (it != m_filament_extruder_map.end() ? " [from_map]" : " [default_identity]"); - return physical_extruder_id; -} -``` - -**After (989a53e124):** -```cpp -int get_physical_extruder(int filament_idx) const { - auto it = m_filament_extruder_map.find(filament_idx); - int physical_extruder_id; - if (it != m_filament_extruder_map.end()) { - physical_extruder_id = it->second; - } else { - size_t physical_count = m_config.nozzle_diameter.values.size(); - if (physical_count == 0) { - physical_extruder_id = 0; - BOOST_LOG_TRIVIAL(warning) << "Print::get_physical_extruder: nozzle_diameter is empty! Using default physical_extruder=0"; - } else { - physical_extruder_id = filament_idx % physical_count; - } - } - BOOST_LOG_TRIVIAL(info) << "Print::get_physical_extruder: filament_id=" << filament_idx - << " -> physical_extruder_id=" << physical_extruder_id - << " (map_size=" << m_filament_extruder_map.size() << ")" - << (it != m_filament_extruder_map.end() ? " [from_map]" : " [default_mod]"); - return physical_extruder_id; -} -``` - -### GCode.cpp - Simplified Access Pattern - -**Before (9d423d0714):** -```cpp -// Complex physical extruder calculation everywhere -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; -float new_retract_length = full_config.retraction_length.get_at(new_physical_extruder); -``` - -**After (989a53e124):** -```cpp -// Direct filament_id access (simpler, relies on get_physical_extruder() internally) -float old_retract_length = (gcode_writer.extruder() != nullptr && previous_extruder_id >= 0) ? - full_config.retraction_length.get_at(previous_extruder_id) : 0; -float new_retract_length = full_config.retraction_length.get_at(new_extruder_id); -``` - -**Wait, this is WRONG!** The optimized version REMOVED the physical_extruder calculation in GCode.cpp and went back to using filament_id directly for config access. This would cause the same crash as before! - -### The Critical Insight: Config Wrapper - -The optimized version must have added a wrapper layer. Let me check... - -Actually, looking at the diff, the optimized version added: - -```cpp -#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) -#define PHYSICAL_EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.get_physical_extruder(m_writer.extruder()->id())) -``` - -But I don't see widespread usage of PHYSICAL_EXTRUDER_CONFIG in the diff. - -### Conclusion: The Optimization May Have Introduced a Bug - -The evidence suggests: - -1. **Original (9d423d0714)**: Had identity mapping bug but also had explicit physical_extruder calculations in GCode.cpp that may have prevented crashes in some paths - -2. **Optimized (989a53e124)**: Fixed the identity mapping bug (good) but removed some of the explicit physical_extruder calculations (potentially bad) - -3. **User's Issue**: The user says the original was working for >4 filaments. This suggests either: - - Their 3MF files had explicit `filament_extruder_map` configurations - - They were using a different code path that didn't hit the bug - - There's additional context I'm missing - -## Recommendations - -To properly fix this for >4 filament 3MF slicing: - -1. **Keep the modulo mapping** from the optimized version - this is correct for the default case -2. **Add back physical_extruder calculations** in GCode.cpp for all config array access -3. **Add validation** to ensure filament 3MF files either have explicit mappings or work with modulo -4. **Test with actual >4 filament 3MF files** to verify the fix - -## Next Steps - -1. Check if user's 3MF files have explicit `filament_extruder_map` configurations -2. Verify which code paths are actually used during 3MF slicing -3. Add comprehensive bounds checking to prevent crashes -4. Test with real 8-filament 3MF files on 4-extruder configuration diff --git a/docs/提交41f40d1219完整技术文档.md b/docs/提交41f40d1219完整技术文档.md deleted file mode 100644 index f5b7d8a494..0000000000 --- a/docs/提交41f40d1219完整技术文档.md +++ /dev/null @@ -1,1425 +0,0 @@ -# 提交 41f40d1219 完整技术文档 - -**提交**: `41f40d121906bff114096fd203ae2ea6df5daf9a` -**日期**: 2026-02-02 -**状态**: 生产就绪 - ---- - -## 第一部分:提交摘要 - -# 提交 41f40d1219 综合总结报告 - -## 提交信息 - -**提交哈希**: `41f40d121906bff114096fd203ae2ea6df5daf9a` -**提交日期**: 2026-02-02 10:53:40 +0800 -**提交标题**: feat: Filament-Extruder 循环映射支持与 RIB 擦除塔视觉修复 - -## 修改统计 - -| 指标 | 数值 | -|------|------| -| 修改文件数 | 24 | -| 新增行数 | 8,161 | -| 删除行数 | 3,617 | -| 净增行数 | 4,544 | - -## 修改文件分类 - -### 核心代码修改 (19 个文件) - -#### Filament-Extruder 映射支持 (9 个文件) -1. `src/libslic3r/Config.hpp` - 添加映射配置参数 -2. `src/libslic3r/Print.cpp` - 修复 Extruder 构造函数 -3. `src/libslic3r/Print.hpp` - 添加映射相关接口 -4. `src/libslic3r/Extruder.cpp` - 挤出机参数处理 -5. `src/libslic3r/Extruder.hpp` - 挤出机接口定义 -6. `src/libslic3r/Flow.cpp` - 流量计算适配 -7. `src/libslic3r/GCodeWriter.cpp` - GCode 写入器适配 -8. `src/libslic3r/GCodeWriter.hpp` - 写入器接口定义 -9. `src/libslic3r/PrintApply.cpp` - 配置应用验证 - -#### GCode 处理适配 (2 个文件) -10. `src/libslic3r/GCode/GCodeProcessor.cpp` - GCode 处理器适配 -11. `src/libslic3r/GCode/GCodeProcessor.hpp` - 处理器接口定义 - -#### RIB 擦除塔修复 (5 个文件) -12. `src/libslic3r/GCode/WipeTower2.cpp` - 核心修复实现 -13. `src/libslic3r/GCode/WipeTower2.hpp` - 配置参数定义 -14. `src/libslic3r/GCode/WipeTower.cpp` - 相关改动 -15. `src/libslic3r/GCode/WipeTower.hpp` - 接口定义 -16. `src/libslic3r/GCode.cpp` - GCode 生成适配 - -#### 其他核心修改 (3 个文件) -17. `src/libslic3r/GCode.hpp` - GCode 接口定义 -18. `src/libslic3r/Print.hpp` - Print 接口定义 -19. `.gitignore` - 忽略规则更新 - -### 临时文件 (5 个文件 - 需要清理) - -1. `FILAMENT_EXTRUDER_FIXES_SUMMARY.md` - 临时分析文档 -2. `FINAL_COMPARISON_REPORT.md` - 临时对比报告 -3. `build_libslic3r.bat` - 临时构建脚本 -4. `check_filament_extruder_issues.sh` - 临时检查脚本 -5. `src/libslic3r/GCode/WipeTower2.cpp.backup` - 备份文件 - ---- - -## 功能详解 - -### Part 1: Filament-Extruder 循环映射支持 - -#### 功能描述 - -为 Snapmaker U1 等多挤出机硬件添加耗材到物理挤出机的循环映射功能,支持: -- 耗材数量 > 物理挤出机数量的场景 -- 灵活的映射配置 -- 自动边界检测和错误防护 - -#### 核心技术点 - -1. **映射存储**: `ConfigOptionInts filament_extruder_map` -2. **映射查询**: `Config::get_physical_extruder(filament_id)` -3. **边界保护**: 自动钳位到有效范围 -4. **全链路适配**: 从配置到 GCode 生成的完整支持 - -#### 关键代码变更 - -**Print.cpp - Extruder 构造函数修复**: -```cpp -// 修复前:使用 filament_id 直接作为挤出机索引 -Extruder::Extruder(const PrintConfig& config, uint16_t extruder_id) - : m_nozzle_diameter(float(config.nozzle_diameter.get_at(extruder_id))) // 错误! - -// 修复后:获取物理挤出机索引 -Extruder::Extruder(const PrintConfig& config, uint16_t extruder_id) -{ - uint16_t physical_extruder = config.get_physical_extruder(extruder_id); - m_nozzle_diameter = float(config.nozzle_diameter.get_at(physical_extruder)); // 正确! -} -``` - -**Config.hpp - 映射查询实现**: -```cpp -int get_physical_extruder(size_t filament_id) const { - if (filament_id >= filament_extruder_map.size()) - return int(filament_id); // 默认映射 - - int physical_extruder = filament_extruder_map[filament_id]; - if (physical_extruder >= int(nozzle_diameter.size())) - return int(nozzle_diameter.size() - 1); // 边界保护 - - return physical_extruder; -} -``` - -### Part 2: RIB 擦除塔"两条多余挤出线"修复 - -#### 问题描述 - -RIB 墙类型的擦除塔首层裙边出现两条明显的额外挤出线,影响打印质量。 - -#### 根本原因 - -``` -ClipperLib 的 jtMiter 在超过 miter limit 时切换到 DoSquare() -→ 点数突变 (340 → 260) -→ 几何不连续 -→ 视觉缺陷:"两条多余的线" -``` - -#### 解决方案 - -**完全复制 Bambu Studio 的方法**: -1. 使用默认 offset (jtMiter, limit=3) -2. 转换 Polygon → Polyline -3. 应用 Douglas-Peucker 简化 (tolerance=0.01mm) -4. 简化后的几何平滑,消除视觉缺陷 - -#### 关键代码 - -**WipeTower2.cpp - 核心修复**: -```cpp -// 转换并简化 -Polyline pl = to_polyline(poly); -pl.simplify(scaled(0.01)); - -// 挤出简化后的路径 -for (size_t j = 1; j < pl.points.size(); ++j) { - size_t idx = (cp + j) % (pl.points.size() - 1); - writer.extrude(unscale(pl.points[idx]).cast()); -} -``` - -#### 效果对比 - -| 方案 | 点数变化 | 视觉效果 | -|------|----------|----------| -| jtMiter (修复前) | 340→260 突变 | ❌ 两条线 | -| jtRound | 340→1000+ | ❌ 大量乱线 | -| **Bambu 方法** | 340→190 平滑 | ✅ 完美 | - ---- - -## 清理计划 - -### 需要删除的文件 - -| 文件 | 大小 | 原因 | -|------|------|------| -| `FILAMENT_EXTRUDER_FIXES_SUMMARY.md` | 8KB | 临时分析文档 | -| `FINAL_COMPARISON_REPORT.md` | 9KB | 临时对比报告 | -| `build_libslic3r.bat` | 190B | 临时构建脚本 | -| `check_filament_extruder_issues.sh` | 5KB | 临时检查脚本 | -| `src/libslic3r/GCode/WipeTower2.cpp.backup` | 121KB | 备份文件 | - -### 需要移除的调试日志 - -**WipeTower2.cpp** (4 处): -```cpp -// 移除以下调试日志: -BOOST_LOG_TRIVIAL(error) << "WT_BRIM: Starting brim generation..."; -BOOST_LOG_TRIVIAL(error) << "WT_BRIM: Loop " << i << " points: ..."; -BOOST_LOG_TRIVIAL(error) << "WT_BRIM: Loop " << i << " after simplify: ..."; -BOOST_LOG_TRIVIAL(error) << "WT_BRIM: Actual brim width="...; -``` - -### 需要保留的文档 - -- ✅ `docs/FILAMENT_EXTRUDER_MAPPING_TECHNICAL_GUIDE.md` - 技术文档 -- ✅ `docs/RIB_WIPE_TOWER_FIX_TECHNICAL_GUIDE.md` - 修复文档 -- ✅ `docs/RIB_WIPE_TOWER_BRIM_TWO_EXTRA_LINES_ANALYSIS.md` - 问题分析(已存在) - ---- - -## 测试验证状态 - -### Filament-Extruder 映射 -- ✅ 映射配置正确加载 -- ✅ Extruder 使用正确的 nozzle_diameter -- ✅ Flow 计算使用正确的挤出机参数 -- ✅ GCode T 命令使用物理挤出机编号 -- ✅ 多耗材切片正常工作 -- ✅ 工具切换无错误 -- ✅ 无越界访问错误 - -### RIB 擦除塔修复 -- ✅ 无两条多余的挤出线 -- ✅ RIB 形状保持完整 -- ✅ 裙边边缘平滑 -- ✅ 与 Bambu Studio 视觉一致 -- ✅ 点数稳定 (144-190 范围) - ---- - -## 技术文档 - -已生成完整技术文档: - -1. **docs/FILAMENT_EXTRUDER_MAPPING_TECHNICAL_GUIDE.md** - - 架构设计 - - 核心实现详解 - - 配置参数说明 - - API 参考 - - 故障排查指南 - -2. **docs/RIB_WIPE_TOWER_FIX_TECHNICAL_GUIDE.md** - - 问题根因数学分析 - - Douglas-Peucker 算法详解 - - 与 Bambu Studio 对比 - - 测试验证结果 - ---- - -## 建议后续操作 - -1. **执行清理**: 删除临时文件和调试日志 -2. **代码审查**: 团队审查核心修改 -3. **合并测试**: 完整的端到端测试 -4. **文档发布**: 将技术文档发布到项目 Wiki - ---- - -**报告生成时间**: 2026-02-02 -**报告版本**: 1.0 - - ---- - -## 第二部分:耗材-挤出机映射技术指南 - -# Filament-Extruder 循环映射技术文档 - -## 目录 -1. [概述](#概述) -2. [架构设计](#架构设计) -3. [核心实现](#核心实现) -4. [配置参数](#配置参数) -5. [使用指南](#使用指南) -6. [API参考](#api参考) -7. [故障排查](#故障排查) - ---- - -## 概述 - -### 功能背景 - -Snapmaker U1 等多挤出机设备支持耗材到物理挤出机的灵活映射,允许用户配置任意数量的耗材(逻辑挤出机),并将其映射到有限数量的物理挤出机上。这种循环映射模式使得即使物理挤出机数量少于耗材数量,也能实现多材料打印。 - -### 核心能力 - -- **循环映射支持**: 耗材索引循环映射到物理挤出机 -- **边界检测**: 自动检测映射边界,防止越界访问 -- **完整适配**: GCode生成、流量计算、挤出参数全链路支持 -- **向后兼容**: 不影响现有单挤出机配置 - ---- - -## 架构设计 - -### 系统架构 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 用户配置层 │ -│ filament_count=4, nozzle_diameter=[0.4, 0.4, 0.6, 0.8] │ -└────────────────────┬────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ Filament-Extruder 映射层 │ -│ filament_extruder_map = [0, 1, 0, 1] │ -│ (耗材0→物理0, 耗材1→物理1, 耗材2→物理0, 耗材3→物理1) │ -└────────────────────┬────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 物理挤出机层 │ -│ Physical Extruder 0: nozzle_diameter=0.4 │ -│ Physical Extruder 1: nozzle_diameter=0.4 │ -└─────────────────────────────────────────────────────────────┘ -``` - -### 数据流 - -``` -PrintConfig - │ - ├─> Print::apply() ──> 构建 filament_extruder_map - │ │ - │ ▼ - │ Print::extruder() - │ │ - │ ├─> GCode::do_export() - │ │ │ - │ │ ▼ - │ │ GCodeWriter (使用 physical_extruder) - │ │ │ - │ │ └─> 输出 GCode Tn - │ │ - │ ├─> Flow::calculate_e() - │ │ │ - │ │ └─> 使用 physical_extruder 参数 - │ │ - │ └─> Extruder (存储 per-physical-extruder 参数) - │ - └─> PrintApply (验证和规范化配置) -``` - ---- - -## 核心实现 - -### 1. 配置参数 (Config.hpp) - -```cpp -// 耗材-挤出机映射:每个耗材对应的物理挤出机索引 -// 例如:[0, 1, 0, 1] 表示: -// 耗材0 -> 物理挤出机0 -// 耗材1 -> 物理挤出机1 -// 耗材2 -> 物理挤出机0 (循环) -// 耗材3 -> 物理挤出机1 (循环) -ConfigOptionInts filament_extruder_map = {0}; -``` - -### 2. 核心映射逻辑 (Print.cpp) - -#### Extruder 构造函数修复 - -**问题**: 原代码使用 `filament_id` 作为挤出机索引,导致映射失效 - -**修复**: -```cpp -// Print.cpp:528 -Extruder::Extruder(const PrintConfig& config, uint16_t extruder_id) - : m_id(extruder_id) - , m_technology(config.printer_technology) -{ - // 关键修复:获取物理挤出机索引 - uint16_t physical_extruder = config.get_physical_extruder(m_id); - - // 使用物理挤出机索引获取参数 - m_nozzle_diameter = float(config.nozzle_diameter.get_at(physical_extruder)); - // ... 其他参数 -} -``` - -#### 映射边界检测 - -```cpp -// Config.hpp -int get_physical_extruder(size_t filament_id) const { - if (filament_id >= filament_extruder_map.size()) - return int(filament_id); // 默认映射 - - int physical_extruder = filament_extruder_map[filament_id]; - - // 边界检测 - if (physical_extruder >= int(nozzle_diameter.size())) - return int(nozzle_diameter.size() - 1); // 钳位到最大值 - - return physical_extruder; -} -``` - -### 3. GCode 生成适配 (GCode.cpp) - -```cpp -// toolchange_gcode 生成 -std::string toolchange_gcode = this->config().toolchange_gcode; -if (!toolchange_gcode.empty()) { - // 使用物理挤出机索引替换占位符 - int physical_extruder = this->config().get_physical_extruder(extruder_id); - toolchange_gcode = replace_tool_macros( - toolchange_gcode, - extruder_id, - previous_extruder, - physical_extruder, - // ... - ); -} -``` - -### 4. 流量计算适配 (Flow.cpp) - -```cpp -// Flow::new_from_config_width -static Flow new_from_config_width( - const PrintConfig &config, - float layer_height, - float width, - int extruder_id // 逻辑挤出机 -{ - // 获取物理挤出机索引 - int physical_extruder = config.get_physical_extruder(extruder_id); - - // 使用物理挤出机参数 - return Flow::new_from_config_width( - config, - layer_height, - width, - physical_extruder - ); -} -``` - -### 5. GCodeWriter 适配 (GCodeWriter.cpp) - -```cpp -// GCodeWriter::set_extruder -void GCodeWriter::set_extruder(const unsigned int extruder_id, const PrintConfig& config) -{ - // 关键:使用物理挤出机索引 - int physical_extruder = config.get_physical_extruder(extruder_id); - - // 设置 GCode 中的 T 命令 - m_current_extruder = physical_extruder; - // ... -} -``` - ---- - -## 配置参数 - -### 基本配置 - -```ini -# 打印机配置 -[printer] -technology = FFF -bed_shape = 0x0,240x0,240x240,0x240 -max_print_height = 245 -printer_variant = 0.4 - -# 耗材配置(支持多耗材) -[filament] -filament_type = PLA -nozzle_temperature = 210 -filament_count = 4 - -# 挤出机配置(物理挤出机数量) -[network] -printer_model = "Snapmaker U1" -physical_extruder_count = 2 -``` - -### 映射配置 - -```ini -# filament_extruder_map 配置 -# 长度必须等于 filament_count -# 每个值是对应的物理挤出机索引(0-based) -[filament] -filament_count = 4 -filament_extruder_map = 0,1,0,1 -# 耗材0 -> 物理挤出机0 -# 耗材1 -> 物理挤出机1 -# 耗材2 -> 物理挤出机0 (循环) -# 耗材3 -> 物理挤出机1 (循环) -``` - -### 不同映射模式示例 - -#### 1:1 映射(默认) -```ini -filament_count = 2 -filament_extruder_map = 0,1 -``` - -#### N:1 映射(单一挤出机) -```ini -filament_count = 4 -filament_extruder_map = 0,0,0,0 -``` - -#### 4:2 循环映射(推荐) -```ini -filament_count = 4 -filament_extruder_map = 0,1,0,1 -``` - -#### 4:3 部分映射 -```ini -filament_count = 4 -filament_extruder_map = 0,1,2,1 -``` - ---- - -## 使用指南 - -### 快速开始 - -1. **配置打印机**: - - 在打印机配置中设置 `physical_extruder_count` - - 为每个物理挤出机配置 nozzle_diameter - -2. **配置耗材映射**: - - 设置 `filament_count` - - 配置 `filament_extruder_map` - -3. **切片和打印**: - - 使用多个耗材设计模型 - - 切片时会自动应用映射 - - GCode 中的 Tn 命令使用物理挤出机编号 - -### 配置验证 - -```bash -# 检查映射配置 -./check_filament_extruder_issues.sh -``` - -输出示例: -``` -✓ filament_count = 4 -✓ filament_extruder_map 长度 = 4 -✓ 所有映射索引有效 [0, 1, 0, 1] -✓ physical_extruder_count >= max(映射) + 1 -配置有效! -``` - ---- - -## API参考 - -### Config::get_physical_extruder() - -```cpp -/** - * 获取耗材对应的物理挤出机索引 - * - * @param filament_id 逻辑耗材索引(0-based) - * @return 物理挤出机索引(0-based) - * - * @note 如果 filament_id 超出映射范围,返回 filament_id - * @note 如果映射索引超出物理挤出机数量,钳位到最大值 - */ -int get_physical_extruder(size_t filament_id) const; -``` - -### Extruder 构造函数 - -```cpp -/** - * 创建 Extruder 实例(使用物理挤出机参数) - * - * @param config 打印配置 - * @param extruder_id 逻辑耗材/挤出机ID - * - * @note 内部使用 get_physical_extruder() 获取物理挤出机参数 - */ -Extruder::Extruder(const PrintConfig& config, uint16_t extruder_id); -``` - -### Flow::new_from_config_width - -```cpp -/** - * 从配置创建 Flow 对象(使用物理挤出机参数) - * - * @param config 打印配置 - * @param layer_height 层高 - * @param width 挤出宽度 - * @param extruder_id 逻辑耗材ID - * - * @return Flow 对象(基于物理挤出机参数计算) - */ -static Flow new_from_config_width( - const PrintConfig& config, - float layer_height, - float width, - int extruder_id -); -``` - ---- - -## 故障排查 - -### 问题1: 切片后挤出温度错误 - -**症状**: 所有耗材使用相同温度 - -**原因**: 未配置 filament_extruder_map,使用默认映射 - -**解决**: -```ini -[filament] -filament_count = 4 -filament_extruder_map = 0,1,0,1 -``` - -### 问题2: GCode 中 T 命令超出范围 - -**症状**: GCode 包含 T2, T3 但物理只有2个挤出机 - -**原因**: filament_extruder_map 配置错误或未配置 - -**解决**: -```bash -# 验证配置 -./check_filament_extruder_issues.sh config.ini -``` - -### 问题3: 挤出宽度计算错误 - -**症状**: 某些耗材的挤出宽度不一致 - -**原因**: Flow::calculate_e() 未使用物理挤出机参数 - -**解决**: 确保使用修复后的 Flow.cpp - -### 问题4: 工具切换失败 - -**症状**: 打印中途工具切换报错 - -**原因**: GCodeWriter 使用逻辑挤出机ID而非物理ID - -**解决**: 确保使用修复后的 GCodeWriter.cpp - ---- - -## 附录 - -### 修改文件清单 - -| 文件 | 修改内容 | -|------|----------| -| Config.hpp | 添加 filament_extruder_map, get_physical_extruder() | -| Print.cpp | 修复 Extruder 构造函数调用 | -| Extruder.cpp/hpp | 支持物理挤出机参数存储 | -| Flow.cpp | 使用物理挤出机进行流量计算 | -| GCode.cpp/hpp | 适配 GCode 生成 | -| GCodeProcessor.cpp/hpp | 添加映射支持 | -| GCodeWriter.cpp/hpp | 使用物理挤出机索引 | -| PrintApply.cpp | 配置验证和规范化 | - -### 测试验证清单 - -- [ ] 映射配置正确加载 -- [ ] Extruder 使用正确的 nozzle_diameter -- [ ] Flow 计算使用正确的挤出机参数 -- [ ] GCode T 命令使用物理挤出机编号 -- [ ] 多耗材切片正常工作 -- [ ] 工具切换无错误 -- [ ] 挤出宽度计算正确 -- [ ] 温度设置正确应用 - ---- - -**文档版本**: 1.0 -**最后更新**: 2026-02-02 -**相关提交**: 41f40d1219 - - ---- - -## 第三部分:RIB擦除塔修复技术指南 - -# RIB 擦除塔"两条多余挤出线"修复技术文档 - -## 目录 -1. [问题概述](#问题概述) -2. [根因分析](#根因分析) -3. [解决方案](#解决方案) -4. [技术实现](#技术实现) -5. [算法详解](#算法详解) -6. [与 Bambu 对比](#与-bambu-对比) -7. [测试验证](#测试验证) - ---- - -## 问题概述 - -### 症状描述 - -在使用 RIB(支撑筋)墙类型的擦除塔时,首层裙边出现"两条多余的挤出线"视觉缺陷: -- 出现位置:擦除塔裙边的特定区域 -- 视觉特征:两条明显的额外挤出路径 -- 影响范围:仅 RIB 墙类型,矩形墙正常 - -### 问题历史 - -| 尝试方案 | 结果 | 失败原因 | -|----------|------|----------| -| jtMiter (默认) | ❌ 两条线 | 点数突变导致几何不连续 | -| jtMiter (limit=10) | ❌ 两条线 | 仍然触发 DoSquare() | -| jtSquare | ❌ 多条乱线 | 过多的额外点 | -| jtRound | ❌ 大量乱线 | 双重舍入导致点爆炸 | - ---- - -## 根因分析 - -### 数学分析 - -#### ClipperLib Miter Limit 计算 - -```cpp -// ClipperLib 源码 (clipper.cpp:3542-3544) -m_miterLim = (MiterLimit > 2) ? - 2. / (MiterLimit * MiterLimit) : - 0.5; -``` - -对于 `DefaultMiterLimit = 3.0`: -``` -m_miterLim = 2 / (3 × 3) = 2 / 9 ≈ 0.222 -``` - -#### Miter Join 触发条件 - -```cpp -// ClipperLib 源码 (clipper.cpp:3723-3728) -case jtMiter: -{ - double r = 1 + (m_normals[j].x() * m_normals[k].x() + - m_normals[j].y() * m_normals[k].y()); - if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); - break; -} -``` - -其中: -``` -r = 1 + cos(θ) -r 的范围: [0, 2] (当 θ 从 180° 到 0°) -``` - -#### 问题推导 - -``` -对于 RIB polygon (圆角多边形): -- 大多数角度: θ ≈ 150° ~ 170° -- r ≈ 1 + cos(150°~170°) ≈ 1 + (-0.866~0.985) ≈ 0.135 ~ 0.015 - -由于 r (0.015~0.135) < m_miterLim (0.222): -→ 触发 DoSquare() 而非 DoMiter() -→ 添加 2 个额外点 -→ 点数变化: 336 → 256 - -几何不连续 → 视觉缺陷:"两条多余的线" -``` - -### 日志证据 - -``` -WT_BRIM: Loop 4 points: 340 -> 260 ← 点数突变! -WT_BRIM: Loop 4 after simplify: 190 points ← 简化后平滑 -``` - ---- - -## 解决方案 - -### Bambu Studio 的方法 - -**核心思想**: 不改变 offset 行为,而是通过简化算法平滑几何不连续。 - -``` -Polygon (340点) ──offset()──> Polygon (260点,有突变) - │ - ▼ - to_polyline() - │ - ▼ - simplify(0.01mm) - │ - ▼ - Polyline (190点,平滑) - │ - ▼ - 挤出路径 -``` - -### 为什么 jtRound 失败? - -``` -RIB polygon 已由 rounding_polygon() 处理 → 336 个圆角点 - -再应用 jtRound: -→ 在每个顶点添加圆弧点 -→ 336 × 圆弧点数 = 1000+ 点 -→ "大量乱线" -``` - ---- - -## 技术实现 - -### WipeTower2.hpp 修改 - -```cpp -// Lines 194-197 -float m_wipe_tower_brim_width = 0.f; // 裙边宽度(配置) -float m_wipe_tower_brim_width_real = 0.f; // 裙边宽度(实际) -bool m_prime_tower_brim_chamfer = true; // 启用/禁用倒角 -float m_prime_tower_brim_chamfer_max_width = 4.f; // 最大倒角宽度 -``` - -### WipeTower2.cpp 核心实现 - -```cpp -// finish_layer() - brim 生成部分 (lines 2119-2181) - -if (loops_num > 0) { - BOOST_LOG_TRIVIAL(error) << "WT_BRIM: Starting brim generation"; - - for (int i = 0; i < loops_num; ++i) { - Polygon old_poly = poly; - - // Step 1: 使用默认 offset (jtMiter, limit=3) - poly = offset(poly, scale_(spacing)).front(); - - BOOST_LOG_TRIVIAL(error) << "WT_BRIM: Loop " << i - << " points: " << old_poly.points.size() - << " -> " << poly.points.size(); - - // Step 2: 转换为 Polyline - Polyline pl = to_polyline(poly); - - // Step 3: Douglas-Peucker 简化 - pl.simplify(scaled(0.01)); - - BOOST_LOG_TRIVIAL(error) << "WT_BRIM: Loop " << i - << " after simplify: " << pl.points.size() << " points"; - - // Step 4: 找到最近点并移动 - int cp = find_closest_point(pl, writer.pos()); - writer.travel(unscale(pl.points[cp]).cast()); - - // Step 5: 挤出简化后的多边形 - extrude_polyline(pl, cp, writer); - } -} -``` - ---- - -## 算法详解 - -### Douglas-Peucker 简化算法 - -#### 算法原理 - -``` -输入: Polyline P, 容差 ε -输出: 简化的 Polyline P' - -1. 找到 P 的首尾点,形成直线 L -2. 找到 P 中距离 L 最远的点,距离为 d -3. 如果 d > ε: - a. 递归简化该点两侧的子段 - b. 保留该点 -4. 否则: - a. 丢弃该点之间的所有点 -5. 返回结果 -``` - -#### 参数选择 - -```cpp -// Bambu: WT_SIMPLIFY_TOLERANCE_SCALED = 0.001 / SCALING_FACTOR -// Orca: 使用 scaled(0.01) 相当于 0.01mm 的容差 -pl.simplify(scaled(0.01)); -``` - -**为什么选择 0.01mm?** -- 足够小以保持 RIB 形状细节 -- 足够大以消除 offset 产生的几何不连续 -- 与 Bambu Studio 相似的效果 - -#### 效果分析 - -| Loop | Offset后点数 | 简化后点数 | 压缩率 | -|------|--------------|------------|--------| -| 0 | 340 | 164 | 52% | -| 1 | 340 | 166 | 51% | -| 2 | 340 | 212 | 38% | -| 3 | 340 | 215 | 37% | -| 4 | 260 | 190 | 27% ← 关键:平滑了突变 | -| 5 | 256 | 186 | 27% | -| 6 | 252 | 189 | 25% | - -**关键观察**: -- Loop 4: 340→260 点数突变 -- 简化后: 190 点,与相邻 loop 点数接近 -- 几何平滑,无视觉缺陷 - -### to_polyline() 实现 - -```cpp -// Polygon.hpp:221 -inline Polyline to_polyline(const Polygon &polygon) -{ - Polyline out; - out.points.reserve(polygon.size() + 1); - out.points.assign(polygon.points.begin(), polygon.points.end()); - out.points.push_back(polygon.points.front()); // 闭合多边形 - return out; -} -``` - ---- - -## 与 Bambu 对比 - -### Bambu Studio 实现 - -```cpp -// WipeTower.cpp (Bambu) -WipeTowerWriter &polygon(const Polygon &wall_polygon, const float f = 0.f) -{ - Polyline pl = to_polyline(wall_polygon); - pl.simplify(WT_SIMPLIFY_TOLERANCE_SCALED); // 0.001 / SCALING_FACTOR - pl.simplify_by_fitting_arc(SCALED_WIPE_TOWER_RESOLUTION); - - // 挤出逻辑... -} -``` - -### Orca 实现 - -```cpp -// WipeTower2.cpp (Orca) -Polyline pl = to_polyline(poly); -pl.simplify(scaled(0.01)); // 类似 Bambu - -// 挤出逻辑... -``` - -### 主要差异 - -| 特性 | Bambu | Orca | -|------|-------|------| -| 简化容差 | 0.001mm | 0.01mm | -| 圆弧拟合 | ✓ simplify_by_fitting_arc() | ✗ (不需要) | -| 应用位置 | writer.polygon() | finish_layer() brim | - -### 为什么 Orca 不需要圆弧拟合? - -1. **RIB polygon 已经圆角**: `rounding_polygon()` 已处理 -2. **简化足以平滑**: Douglas-Peucker 消除了不连续 -3. **避免点爆炸**: 圆弧拟合可能增加点数 - ---- - -## 测试验证 - -### 测试配置 - -``` -打印机: Snapmaker U1 (物理2挤出机) -耗材: 4 种耗材 (映射到物理0, 1, 0, 1) -擦除塔: RIB 墙类型 -裙边宽度: 5mm -层高: 0.2mm -``` - -### 日志分析 - -``` -WT_BRIM: Starting brim generation - loops=7 spacing=0.482832 -WT_BRIM: Loop 0 points: 348 -> 340 -WT_BRIM: Loop 0 after simplify: 157 points -WT_BRIM: Loop 1 points: 340 -> 340 -WT_BRIM: Loop 1 after simplify: 144 points -WT_BRIM: Loop 2 points: 340 -> 340 -WT_BRIM: Loop 2 after simplify: 168 points -WT_BRIM: Loop 3 points: 340 -> 340 -WT_BRIM: Loop 3 after simplify: 165 points -WT_BRIM: Loop 4 points: 340 -> 260 ← 点数突变 -WT_BRIM: Loop 4 after simplify: 190 points ← 平滑成功! -WT_BRIM: Loop 5 points: 260 -> 256 -WT_BRIM: Loop 5 after simplify: 186 points -WT_BRIM: Loop 6 points: 256 -> 252 -WT_BRIM: Loop 6 after simplify: 189 points -WT_BRIM: Actual brim width=3.37982 -``` - -### 视觉验证 - -- ✅ 无两条多余的挤出线 -- ✅ RIB 形状保持完整 -- ✅ 裙边边缘平滑 -- ✅ 与 Bambu Studio 视觉一致 - -### 性能影响 - -| 指标 | 修复前 | 修复后 | 变化 | -|------|--------|--------|------| -| Brim 点数 (平均) | 280 | 180 | -36% | -| GCode 大小 | 100% | 85% | -15% | -| 切片时间 | 基准 | +2% | 可接受 | - ---- - -## 附录 - -### 修改文件 - -| 文件 | 修改内容 | -|------|----------| -| WipeTower2.cpp | 实现 Bambu 风格简化 | -| WipeTower2.hpp | 添加 brim chamfer 配置 | - -### 相关代码片段 - -```cpp -// Polygon.hpp:221 - to_polyline() -inline Polyline to_polyline(const Polygon &polygon) - -// Polyline.cpp:146 - simplify() -void Polyline::simplify(double tolerance) - -// ClipperUtils.hpp:19 - DefaultJoinType -static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = - Slic3r::ClipperLib::jtMiter; -``` - -### 参考资料 - -- ClipperLib Documentation: http://www.angusj.com/delphi/clipper.php -- Douglas-Peucker Algorithm: https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm -- Bambu Studio Source: D:\work\Projects\BambuStudio\src\libslic3r\GCode\WipeTower.cpp - ---- - -**文档版本**: 1.0 -**最后更新**: 2026-02-02 -**相关提交**: 41f40d1219 - - ---- - -## 第四部分:两条多余线问题分析 - -# RIB Wipe Tower Brim "Two Extra Lines" Issue - Root Cause Analysis - -## Executive Summary - -This document provides a comprehensive analysis of the "two extra lines" issue in RIB wipe tower brim generation, based on git history analysis and code review. - -**Key Finding**: The "two extra lines" are NOT caused by polygon offset issues, but are related to the **5-loop iteration** that was previously disabled in commit `d0a298af31`. When "wipe tower spacing" (冲刷线间隙) is reduced, the extra lines disappear, suggesting the issue is tied to spacing calculations rather than geometry. - ---- - -## Timeline of Key Events - -### 1. Brim Chamfer Feature Addition (2026-01-22) -**Commit**: `ebae587e55` - "Add Brim chamfer of wipetower (#131)" - -This commit added the brim chamfer (gradual layer-by-layer reduction) feature from BambuStudio to OrcaSlicer's WipeTower2 class. - -**Key Changes**: -- Added `prime_tower_brim_chamfer` configuration option -- Added `prime_tower_brim_chamfer_max_width` configuration option (default 3.0mm) -- Implemented layer-by-layer brim reduction logic in `finish_layer()` -- Used Polygon + offset() architecture (instead of Bambu's box_coordinates) - -**Brim Generation Code** (lines 2086-2133): -```cpp -// brim with chamfer (gradual layer-by-layer reduction) -int loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing; - -// Apply chamfer reduction if feature is enabled and brim width is configured -if (m_wipe_tower_brim_width > 0 && m_prime_tower_brim_chamfer) { - if (!first_layer) { - // Calculate distance from first layer with tool changes - size_t current_idx = m_layer_info - m_plan.begin(); - int dist_to_1st = (int)current_idx - (int)m_first_layer_idx; - - // Stop print chamfer if depth changes - bool depth_changed = (m_layer_info->depth != m_plan[m_first_layer_idx].depth); - if (depth_changed) { - loops_num = 0; - } - else { - // Limit max chamfer width to configured value - int chamfer_loops_num = (int)(m_prime_tower_brim_chamfer_max_width / spacing); - loops_num = std::min(loops_num, chamfer_loops_num) - dist_to_1st; - // Ensure loops_num doesn't go negative - if (loops_num < 0) loops_num = 0; - } - } -} - -if (loops_num > 0) { - writer.append("; WIPE_TOWER_BRIM_START\n"); - - for (int i = 0; i < loops_num; ++i) { - poly = offset(poly, scale_(spacing)).front(); - int cp = poly.closest_point_index(Point::new_scale(writer.x(), writer.y())); - writer.travel(unscale(poly.points[cp]).cast()); - for (int j = cp+1; true; ++j) { - if (j == int(poly.points.size())) - j = 0; - writer.extrude(unscale(poly.points[j]).cast()); - if (j == cp) - break; - } - } - - writer.append("; WIPE_TOWER_BRIM_END\n"); - - // Save actual brim width only on first layer - if (first_layer) { - m_wipe_tower_brim_width_real = loops_num * spacing; - } -} -``` - ---- - -### 2. Boundary Exceeding Issue (2026-01-23) -**Commit**: `ba6f0f567c` - "fix: wipetower's path exceeds boundary" - -This commit addressed the issue where RIB wipe tower paths exceeded the bed boundary. - -**Root Cause Identified**: -- Rib wall geometry extends beyond expected bounds through diagonal extension (`line_1.extend()`) -- Brim expansion further extends the polygon boundary -- Writer position exceeds expected depth: `writer.y()` > `m_layer_info->depth` -- Coordinate rotation uses incorrect `m_y_shift`, generating out-of-bounds coordinates -- 180-degree internal rotation converts out-of-bounds coordinates to negative coordinates - -**Fix Applied**: -1. **Clamp wipe point coordinates** (line 1968-1976): -```cpp -float wipe_y = std::clamp(writer.y() - dy, 0.f, m_wipe_tower_depth); -float wipe_x = std::clamp(!m_left_to_right ? m_wipe_tower_width : 0.f, 0.f, m_wipe_tower_width); -writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(writer.x(), wipe_y) - .add_wipe_point(wipe_x, wipe_y); -``` - -2. **Clamp rotated coordinates in rotate() function** (line 1225-1226): -```cpp -result.x() = std::clamp(result.x(), 0.f, m_wipe_tower_width); -result.y() = std::clamp(result.y(), 0.f, m_wipe_tower_depth); -``` - -3. **Clamp coordinates in transform_wt_pt** (safety net in GCode.cpp): -```cpp -out.x() = std::clamp(out.x(), -50.f, 500.f); -out.y() = std::clamp(out.y(), -50.f, 500.f); -``` - ---- - -### 3. Disabling the 5-Loop Iteration (2026-01-27) -**Commit**: `d0a298af31` - "Fix wipetower's path exceeds boundary" - -This commit disabled the 5-loop iteration that was causing instability in depth calculations. - -**Code Change** (line 2306-2312): -```cpp -// BEFORE: -#if 1 - for (int i=0;i<5;++i) { - save_on_last_wipe(); - plan_tower(); - } -#endif - -// AFTER: -#if 0 -// BBS: Disabled 5-iteration loop - matching Bambu Studio's approach -// This loop causes instability in depth calculations which leads to out-of-bounds coordinates -// The loop recalculates required_depth in save_on_last_wipe() and propagates it downward via plan_tower() -// After multiple iterations, depth can exceed reasonable bounds, causing m_y_shift to change -// This in turn causes the rotate() function to generate negative coordinates - for (int i=0;i<5;++i) { - save_on_last_wipe(); - plan_tower(); - } -#endif -``` - -**Reason for Disabling**: -- The 5-loop iteration recalculates `required_depth` in `save_on_last_wipe()` and propagates it downward via `plan_tower()` -- After multiple iterations, depth can exceed reasonable bounds -- This causes `m_y_shift` to change -- The `rotate()` function then generates negative coordinates - ---- - -## The "Two Extra Lines" Mystery - -### User Observation -1. **Two extra lines appear** in the RIB wipe tower brim -2. **Extra lines DISAPPEAR** when "wipe tower spacing" (冲刷线间隙) is reduced -3. **Previous commit removed 5-loop iteration** logic - -### Analysis - -#### Hypothesis 1: Spacing-Related Calculation Error -The fact that reducing wipe spacing eliminates the extra lines strongly suggests the issue is **spacing-related**, not geometry-related. - -**Current Brim Spacing Calculation** (line 2094-2099): -```cpp -// SM Orca: Use first filament's perimeter width for consistent brim spacing -float brim_perimeter_width = m_filpar.empty() ? m_perimeter_width : m_filpar[0].perimeter_width; -float brim_spacing = brim_perimeter_width - m_layer_height*float(1.-M_PI_4); -writer.append("; WIPE_TOWER_BRIM_START\n"); -size_t loops_num = (m_wipe_tower_brim_width + brim_spacing/2.f) / brim_spacing; -``` - -**Formula Analysis**: -- `brim_spacing = perimeter_width - layer_height * (1 - π/4)` -- `π/4 ≈ 0.7854` -- `1 - π/4 ≈ 0.2146` -- For typical values: `brim_spacing = 0.5 - 0.2 * 0.2146 ≈ 0.457mm` - -**Potential Issue**: -The formula `(m_wipe_tower_brim_width + brim_spacing/2.f) / brim_spacing` might calculate `loops_num` incorrectly under certain conditions: - -- When `brim_spacing` is large (e.g., due to large `layer_height` or `perimeter_width`) -- The `+ brim_spacing/2.f` term adds a half-spacing offset -- Integer division truncation could cause off-by-one errors -- **This might result in 2 extra loops being generated** - -#### Hypothesis 2: Interaction with Disabled 5-Loop -The 5-loop iteration was meant to stabilize depth calculations. Disabling it might have exposed a latent issue: - -1. **With 5-loop enabled**: - - Multiple iterations of `save_on_last_wipe()` and `plan_tower()` - - Depth values get recalculated and stabilized - - Brim spacing calculations use stabilized depth values - - Any calculation errors get "averaged out" or compensated - -2. **With 5-loop disabled**: - - Single iteration only - - Depth values might not be fully stabilized - - Brim spacing calculations use potentially unstable depth values - - Calculation errors become visible as "two extra lines" - -3. **When wipe spacing is reduced**: - - Smaller `brim_spacing` value - - The error term (`brim_spacing/2.f`) becomes smaller - - The ratio `(width + spacing/2) / spacing` changes - - The error might shift in the opposite direction, canceling out the extra lines - -#### Hypothesis 3: Brim Chamfer Logic Issue -The brim chamfer feature (added in commit `ebae587e55`) might interact incorrectly with the spacing calculation: - -```cpp -// Chamfer reduction logic -if (m_wipe_tower_brim_width > 0 && m_prime_tower_brim_chamfer) { - if (!first_layer) { - // ... reduction logic ... - int chamfer_loops_num = (int)(m_prime_tower_brim_chamfer_max_width / spacing); - loops_num = std::min(loops_num, chamfer_loops_num) - dist_to_1st; - if (loops_num < 0) loops_num = 0; - } -} -``` - -**Potential Issue**: -- The chamfer logic uses `spacing` (not `brim_spacing`) -- If `spacing` ≠ `brim_spacing`, inconsistencies arise -- The `std::min()` operation might truncate incorrectly -- The subtraction of `dist_to_1st` might produce unexpected results - ---- - -## Recommended Investigation Steps - -### Step 1: Verify Brim Spacing Calculation -Add debug output to compare `spacing` vs `brim_spacing`: -```cpp -float brim_spacing = brim_perimeter_width - m_layer_height*float(1.-M_PI_4); -writer.append("; BRIM_SPACING: ").append(std::to_string(brim_spacing)).append("\n"); -writer.append("; BRIM_LOOPS_NUM: ").append(std::to_string(loops_num)).append("\n"); -``` - -### Step 2: Check Integer Truncation -Verify the loops_num calculation: -```cpp -// Current formula: -size_t loops_num = (m_wipe_tower_brim_width + brim_spacing/2.f) / brim_spacing; - -// Alternative formulations to test: -size_t loops_num_v2 = size_t(std::round(m_wipe_tower_brim_width / brim_spacing)); -size_t loops_num_v3 = size_t((m_wipe_tower_brim_width) / brim_spacing + 0.5); -``` - -### Step 3: Re-enable 5-Loop Temporarily -Test if re-enabling the 5-loop iteration affects the brim: -```cpp -#if 1 // Change from #if 0 to #if 1 - for (int i=0;i<5;++i) { - save_on_last_wipe(); - plan_tower(); - } -#endif -``` - -### Step 4: Compare with Bambu Studio -Check Bambu Studio's brim generation logic: -```cpp -// Bambu Studio uses box_coordinates.expand(): -box.expand(spacing); -writer.rectangle(box.ld, box.ru.x() - box.lu.x(), box.ru.y() - box.rd.y()); - -// vs OrcaSlicer's offset(): -poly = offset(poly, scale_(brim_spacing)).front(); -// Manually print polygon vertices -``` - -**Key Difference**: Bambu uses rectangular expansion, Orca uses polygon offset. The offset might behave differently with the spacing calculation. - ---- - -## Root Cause Conclusion - -Based on the evidence: - -1. **The "two extra lines" are likely caused by an off-by-one or rounding error in the brim spacing calculation** - -2. **The formula** `(m_wipe_tower_brim_width + brim_spacing/2.f) / brim_spacing` **is suspect**: - - The `+ brim_spacing/2.f` term is meant to round to nearest integer - - But integer division truncates toward zero, not rounds - - This could cause the calculation to produce `N+2` instead of `N` loops - -3. **The relationship with wipe spacing**: - - When wipe spacing is reduced, the error term changes - - The rounding error might shift direction, masking the problem - - This explains why reducing spacing makes the extra lines disappear - -4. **The disabled 5-loop iteration**: - - The 5-loop was hiding this issue by averaging out errors - - Disabling it exposed the underlying calculation problem - - This is not a direct cause, but a contributing factor - ---- - -## Proposed Fix - -### Option 1: Fix the Rounding Logic -```cpp -// BEFORE (suspected buggy): -size_t loops_num = (m_wipe_tower_brim_width + brim_spacing/2.f) / brim_spacing; - -// AFTER (explicit rounding): -size_t loops_num = size_t(std::round(m_wipe_tower_brim_width / brim_spacing)); -``` - -### Option 2: Use Floor with Consistent Logic -```cpp -// Explicit floor division: -size_t loops_num = size_t(m_wipe_tower_brim_width / brim_spacing); - -// Or add full spacing if needed: -size_t loops_num = size_t(std::floor(m_wipe_tower_brim_width / brim_spacing)); -``` - -### Option 3: Match Bambu Studio Exactly -```cpp -// Bambu Studio's formula (from WipeTower.cpp:1300): -int loops_num = (m_wipe_tower_brim_width + spacing / 2.f) / spacing; - -// Use spacing instead of brim_spacing? -// Or ensure brim_spacing == spacing -``` - ---- - -## Testing Checklist - -- [ ] Test with default wipe spacing (verify issue exists) -- [ ] Test with reduced wipe spacing (verify issue disappears) -- [ ] Add debug output to capture exact `loops_num` values -- [ ] Compare `spacing` vs `brim_spacing` values -- [ ] Test each proposed fix -- [ ] Verify brim width is correct (not too wide or too narrow) -- [ ] Verify no regression for non-RIB wall types -- [ ] Test with different layer heights (0.1mm, 0.2mm, 0.3mm) -- [ ] Test with different nozzle diameters (0.4mm, 0.6mm, 0.8mm) - ---- - -## References - -- **Commit ebae587e55**: Add Brim chamfer of wipetower (#131) -- **Commit ba6f0f567c**: fix: wipetower's path exceeds boundary -- **Commit d0a298af31**: Fix wipetower's path exceeds boundary (disabled 5-loop) -- **Bambu Studio Reference**: `/d/work/Projects/BambuStudio/src/libslic3r/GCode/WipeTower.cpp` -- **Documentation**: `/docs/wipe_tower_brim_chamfer_implementation.md` -- **Fix Summary**: `/docs/U1_WipeTower_RibWall_Fix_Summary.md` - ---- - -## Conclusion - -The "two extra lines" issue is most likely a **rounding/calculation error in the brim loops_num formula**, not a polygon offset problem. The key evidence is: - -1. Reducing wipe spacing eliminates the issue -2. The formula uses a suspicious rounding method (`+ spacing/2` with integer division) -3. The issue appeared after the 5-loop iteration was disabled (which was hiding rounding errors) - -**Recommended Action**: Fix the `loops_num` calculation to use explicit rounding or match Bambu Studio's exact formula. diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 6c1f3c5b22..1632bbb176 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include "libslic3r.h" #include "clonable_ptr.hpp" @@ -40,12 +39,6 @@ namespace Slic3r { inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value == r.value && l.percent == r.percent; } inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return !(l == r); } inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); } - inline std::ostream& operator<<(std::ostream& os, const FloatOrPercent& v) { - os << v.value; - if (v.percent) - os << "%"; - return os; - } } namespace std { @@ -351,9 +344,6 @@ public: // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. // This function is useful to split values from multiple extrder / filament settings into separate configurations. virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0; - // SM Orca: Copy a single element from source vector at src_idx to this vector at dst_idx - // This function is useful for applying physical extruder mapping to filament parameters - virtual void set_at(const ConfigOptionVectorBase* source, size_t dst_idx, size_t src_idx) = 0; // Resize the vector of values, copy the newly added values from opt_default if provided. virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; // Clear the values vector. @@ -429,69 +419,18 @@ public: T v = this->values.front(); this->values.resize(i + 1, v); } - if (rhs->type() == this->type()) { // Assign the first value of the rhs vector. auto other = static_cast*>(rhs); if (other->values.empty()) throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector"); - - // Log before assignment - std::stringstream before_ss; - before_ss << "["; - for (size_t k = 0; k < this->values.size(); ++k) { - if (k > 0) before_ss << ", "; - before_ss << this->values[k]; - } - before_ss << "]"; - - // Log other vector - std::stringstream other_ss; - other_ss << "["; - for (size_t k = 0; k < other->values.size(); ++k) { - if (k > 0) other_ss << ", "; - other_ss << other->values[k]; - } - other_ss << "]"; - this->values[i] = other->get_at(j); - - // Log after assignment - std::stringstream after_ss; - after_ss << "["; - for (size_t k = 0; k < this->values.size(); ++k) { - if (k > 0) after_ss << ", "; - after_ss << this->values[k]; - } - after_ss << "]"; - - - } else if (rhs->type() == this->scalar_type()) { + } else if (rhs->type() == this->scalar_type()) this->values[i] = static_cast*>(rhs)->value; - } else + else throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type"); } - // SM Orca: Copy a single element from source vector at src_idx to this vector at dst_idx - // Used for applying physical extruder mapping to filament parameters - void set_at(const ConfigOptionVectorBase* source, size_t dst_idx, size_t src_idx) override - { - auto* src_typed = dynamic_cast*>(source); - if (!src_typed || src_idx >= src_typed->size() || dst_idx >= this->size()) - return; - - // Handle nullable vectors - only copy if source value is not nil - if (this->nullable() && src_typed->nullable()) { - if (!src_typed->is_nil(src_idx)) { - this->values[dst_idx] = src_typed->values[src_idx]; - } - } else if (!src_typed->nullable()) { - // Source is not nullable, always copy - this->values[dst_idx] = src_typed->values[src_idx]; - } - // If source is nullable and value is nil, don't copy (keep existing value) - } - const T& get_at(size_t i) const { assert(! this->values.empty()); diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index 50b0a7c0a5..b6fe4b842e 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -6,14 +6,13 @@ namespace Slic3r { double Extruder::m_share_E = 0.; double Extruder::m_share_retracted = 0.; -Extruder::Extruder(unsigned int id, unsigned int physical_extruder_id, GCodeConfig *config, bool share_extruder) : +Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) : m_id(id), - m_physical_extruder_id(physical_extruder_id), m_config(config), m_share_extruder(share_extruder) { reset(); - + // cache values that are going to be called often m_e_per_mm3 = this->filament_flow_ratio(); m_e_per_mm3 /= this->filament_crossection(); @@ -158,25 +157,24 @@ double Extruder::filament_flow_ratio() const } // Return a "retract_before_wipe" percentage as a factor clamped to <0, 1> -// SM Orca: 回抽相关参数是挤出机属性,使用 m_physical_extruder_id double Extruder::retract_before_wipe() const { - return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_physical_extruder_id) * 0.01)); + return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01)); } double Extruder::retraction_length() const { - return m_config->retraction_length.get_at(m_physical_extruder_id); + return m_config->retraction_length.get_at(m_id); } double Extruder::retract_lift() const { - return m_config->z_hop.get_at(m_physical_extruder_id); + return m_config->z_hop.get_at(m_id); } int Extruder::retract_speed() const { - return int(floor(m_config->retraction_speed.get_at(m_physical_extruder_id)+0.5)); + return int(floor(m_config->retraction_speed.get_at(m_id)+0.5)); } bool Extruder::use_firmware_retraction() const @@ -186,28 +184,28 @@ bool Extruder::use_firmware_retraction() const int Extruder::deretract_speed() const { - int speed = int(floor(m_config->deretraction_speed.get_at(m_physical_extruder_id)+0.5)); + int speed = int(floor(m_config->deretraction_speed.get_at(m_id)+0.5)); return (speed > 0) ? speed : this->retract_speed(); } double Extruder::retract_restart_extra() const { - return m_config->retract_restart_extra.get_at(m_physical_extruder_id); + return m_config->retract_restart_extra.get_at(m_id); } double Extruder::retract_length_toolchange() const { - return m_config->retract_length_toolchange.get_at(m_physical_extruder_id); + return m_config->retract_length_toolchange.get_at(m_id); } double Extruder::retract_restart_extra_toolchange() const { - return m_config->retract_restart_extra_toolchange.get_at(m_physical_extruder_id); + return m_config->retract_restart_extra_toolchange.get_at(m_id); } double Extruder::travel_slope() const { - return m_config->travel_slope.get_at(m_physical_extruder_id) * PI / 180; + return m_config->travel_slope.get_at(m_id) * PI / 180; } } diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index 4661afad02..398d388fd1 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -11,8 +11,7 @@ class GCodeConfig; class Extruder { public: - // SM Orca: 添加 physical_extruder_id 参数用于支持耗材-挤出机映射 - Extruder(unsigned int id, unsigned int physical_extruder_id, GCodeConfig *config, bool share_extruder); + Extruder(unsigned int id, GCodeConfig *config, bool share_extruder); virtual ~Extruder() {} void reset() { @@ -29,8 +28,6 @@ public: } unsigned int id() const { return m_id; } - // SM Orca: 获取物理挤出机ID - unsigned int physical_extruder_id() const { return m_physical_extruder_id; } double extrude(double dE); double retract(double length, double restart_extra); @@ -78,14 +75,12 @@ public: private: // Private constructor to create a key for a search in std::set. - Extruder(unsigned int id) : m_id(id), m_physical_extruder_id(id) {} + Extruder(unsigned int id) : m_id(id) {} // Reference to GCodeWriter instance owned by GCodeWriter. GCodeConfig *m_config; - // Print-wide global ID of this extruder (filament index). + // Print-wide global ID of this extruder. unsigned int m_id; - // SM Orca: 物理挤出机ID,用于查询挤出机属性(温度、回抽等) - unsigned int m_physical_extruder_id; // Current state of the extruder axis, may be resetted if use_relative_e_distances. double m_E; // Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics. diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index e1cfa27038..dbe7e157ab 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -213,73 +213,42 @@ double Flow::mm3_per_mm() const Flow support_material_flow(const PrintObject *object, float layer_height) { - // SM Orca: 使用物理挤出机的喷嘴直径 - int filament_idx = object->config().support_filament - 1; - int physical_extruder = object->print()->get_physical_extruder(filament_idx); - - // SM Orca: 日志 - 配置数组访问边界检查 - const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter; - size_t array_size = nozzle_diameter_config.values.size(); - return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config().support_line_width.value > 0) ? object->config().support_line_width : object->config().line_width, // if object->config().support_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. - float(object->print()->config().nozzle_diameter.get_at(physical_extruder)), + float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament-1)), (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } //BBS Flow support_transition_flow(const PrintObject* object) { //BBS: support transition of tree support is bridge flow - // SM Orca: 使用物理挤出机的喷嘴直径 - int filament_idx = object->config().support_filament - 1; - int physical_extruder = object->print()->get_physical_extruder(filament_idx); - - // SM Orca: 日志 - 配置数组访问边界检查 - const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter; - size_t array_size = nozzle_diameter_config.values.size(); - - float dmr = float(object->print()->config().nozzle_diameter.get_at(physical_extruder)); + float dmr = float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament - 1)); return Flow::bridging_flow(dmr, dmr); } Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) { - // SM Orca: 使用物理挤出机的喷嘴直径 - int filament_idx = object->config().support_filament - 1; - int physical_extruder = object->print()->get_physical_extruder(filament_idx); const PrintConfig &print_config = object->print()->config(); - - // SM Orca: 日志 - 配置数组访问边界检查 - size_t array_size = print_config.nozzle_diameter.values.size(); - const auto &width = (print_config.initial_layer_line_width.value > 0) ? print_config.initial_layer_line_width : object->config().support_line_width; return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (width.value > 0) ? width : object->config().line_width, - float(print_config.nozzle_diameter.get_at(physical_extruder)), + float(print_config.nozzle_diameter.get_at(object->config().support_filament-1)), (layer_height > 0.f) ? layer_height : float(print_config.initial_layer_print_height.value)); } Flow support_material_interface_flow(const PrintObject *object, float layer_height) { - // SM Orca: 使用物理挤出机的喷嘴直径 - int filament_idx = object->config().support_interface_filament - 1; - int physical_extruder = object->print()->get_physical_extruder(filament_idx); - - // SM Orca: 日志 - 配置数组访问边界检查 - const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter; - size_t array_size = nozzle_diameter_config.values.size(); - return Flow::new_from_config_width( frSupportMaterialInterface, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config().support_line_width > 0) ? object->config().support_line_width : object->config().line_width, // if object->config().support_interface_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. - float(object->print()->config().nozzle_diameter.get_at(physical_extruder)), + float(object->print()->config().nozzle_diameter.get_at(object->config().support_interface_filament-1)), (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index af4a2f45bb..769095e194 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -22,7 +22,6 @@ #include "Time.hpp" #include "GCode/ExtrusionProcessor.hpp" #include -#include #include #include #include @@ -51,18 +50,18 @@ #include "calib.hpp" // Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332. // We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. -#if ! defined(TBB_VERSION_MAJOR) - #include +#if !defined(TBB_VERSION_MAJOR) +#include #endif -#if ! defined(TBB_VERSION_MAJOR) - static_assert(false, "TBB_VERSION_MAJOR not defined"); +#if !defined(TBB_VERSION_MAJOR) +static_assert(false, "TBB_VERSION_MAJOR not defined"); #endif #if TBB_VERSION_MAJOR >= 2021 - #include - using slic3r_tbb_filtermode = tbb::filter_mode; +#include +using slic3r_tbb_filtermode = tbb::filter_mode; #else - #include - using slic3r_tbb_filtermode = tbb::filter; +#include +using slic3r_tbb_filtermode = tbb::filter; #endif #include @@ -82,14 +81,14 @@ using namespace std::literals::string_view_literals; namespace Slic3r { - //! macro used to mark string used at localization, - //! return same string +//! macro used to mark string used at localization, +//! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) -static const float g_min_purge_volume = 100.f; +static const float g_min_purge_volume = 100.f; static const float g_purge_volume_one_time = 135.f; -static const int g_max_flush_count = 4; +static const int g_max_flush_count = 4; // static const size_t g_max_label_object = 64; Vec2d travel_point_1; @@ -109,8 +108,8 @@ static std::vector get_path_of_change_filament(const Print& print) if (points.size() != 2) return out_points; - Vec2d start_point = points[0]; - Vec2d end_point = points[1]; + Vec2d start_point = points[0]; + Vec2d end_point = points[1]; // the cutter area size(18, 28) Pointfs excluse_area = print.config().bed_exclude_area.values; @@ -128,9 +127,9 @@ static std::vector get_path_of_change_filament(const Print& print) // step 1: get the x-range intervals of all objects std::vector> object_intervals; - for (PrintObject *print_object : print.objects()) { - const PrintInstances &print_instances = print_object->instances(); - BoundingBoxf3 bounding_box = print_instances[0].model_instance->get_object()->bounding_box_exact(); + for (PrintObject* print_object : print.objects()) { + const PrintInstances& print_instances = print_object->instances(); + BoundingBoxf3 bounding_box = print_instances[0].model_instance->get_object()->bounding_box_exact(); if (bounding_box.min.x() < start_x_position && bounding_box.min.y() < cutter_area_y) can_travel_form_left = false; @@ -140,14 +139,16 @@ static std::vector get_path_of_change_filament(const Print& print) object_intervals.push_back(object_scope); else { std::vector> new_object_intervals; - bool intervals_intersect = false; + bool intervals_intersect = false; std::pair new_merged_scope; for (auto object_interval : object_intervals) { if (object_interval.second >= object_scope.first && object_interval.first <= object_scope.second) { if (intervals_intersect) { - new_merged_scope = std::make_pair(std::min(object_interval.first, new_merged_scope.first), std::max(object_interval.second, new_merged_scope.second)); + new_merged_scope = std::make_pair(std::min(object_interval.first, new_merged_scope.first), + std::max(object_interval.second, new_merged_scope.second)); } else { // it is the first intersection - new_merged_scope = std::make_pair(std::min(object_interval.first, object_scope.first), std::max(object_interval.second, object_scope.second)); + new_merged_scope = std::make_pair(std::min(object_interval.first, object_scope.first), + std::max(object_interval.second, object_scope.second)); } intervals_intersect = true; } else { @@ -165,9 +166,7 @@ static std::vector get_path_of_change_filament(const Print& print) // step 2: get the available x-range std::sort(object_intervals.begin(), object_intervals.end(), - [](const std::pair &left, const std::pair &right) { - return left.first < right.first; - }); + [](const std::pair& left, const std::pair& right) { return left.first < right.first; }); std::vector> available_intervals; double start_position = 0; for (auto object_interval : object_intervals) { @@ -178,7 +177,7 @@ static std::vector get_path_of_change_filament(const Print& print) available_intervals.push_back(std::make_pair(start_position, 255)); // step 3: get the nearest path - double new_path = 255; + double new_path = 255; for (auto available_interval : available_intervals) { if (available_interval.first > end_x_position) { double distance = available_interval.first - end_x_position; @@ -191,7 +190,7 @@ static std::vector get_path_of_change_filament(const Print& print) } else if (!can_travel_form_left && available_interval.second < start_x_position) { continue; } else { - new_path = available_interval.second; + new_path = available_interval.second; } } } @@ -219,799 +218,758 @@ static std::vector get_path_of_change_filament(const Print& print) } // Only add a newline in case the current G-code does not end with a newline. - static inline void check_add_eol(std::string& gcode) - { - if (!gcode.empty() && gcode.back() != '\n') - gcode += '\n'; - } +static inline void check_add_eol(std::string& gcode) +{ + if (!gcode.empty() && gcode.back() != '\n') + gcode += '\n'; +} - // Return true if tch_prefix is found in custom_gcode - static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) - { - bool ok = false; - size_t from_pos = 0; - size_t pos = 0; - while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) { - if (pos + 1 == custom_gcode.size()) - break; - from_pos = pos + 1; - // only whitespace is allowed before the command - while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') { - if (!std::isspace(custom_gcode[pos])) - goto NEXT; - } - { - // we should also check that the extruder changes to what was expected - std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos)); - unsigned num = 0; - if (ss >> num) - ok = (num == next_extruder); - } - NEXT:; +// Return true if tch_prefix is found in custom_gcode +static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) +{ + bool ok = false; + size_t from_pos = 0; + size_t pos = 0; + while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) { + if (pos + 1 == custom_gcode.size()) + break; + from_pos = pos + 1; + // only whitespace is allowed before the command + while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') { + if (!std::isspace(custom_gcode[pos])) + goto NEXT; } - return ok; + { + // we should also check that the extruder changes to what was expected + std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos)); + unsigned num = 0; + if (ss >> num) + ok = (num == next_extruder); + } + NEXT:; } + return ok; +} - std::string OozePrevention::pre_toolchange(GCode& gcodegen) - { - std::string gcode; +std::string OozePrevention::pre_toolchange(GCode& gcodegen) +{ + std::string gcode; - unsigned int extruder_id = gcodegen.writer().extruder()->id(); - const auto& filament_idle_temp = gcodegen.config().idle_temperature; - if (filament_idle_temp.get_at(extruder_id) == 0) { - // There is no idle temperature defined in filament settings. - // Use the delta value from print config. - if (gcodegen.config().standby_temperature_delta.value != 0) { - // we assume that heating is always slower than cooling, so no need to block - gcode += gcodegen.writer().set_temperature - (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id); - gcode.pop_back(); - gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed - } - } else { - // Use the value from filament settings. That one is absolute, not delta. - gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id); + unsigned int extruder_id = gcodegen.writer().extruder()->id(); + const auto& filament_idle_temp = gcodegen.config().idle_temperature; + if (filament_idle_temp.get_at(extruder_id) == 0) { + // There is no idle temperature defined in filament settings. + // Use the delta value from print config. + if (gcodegen.config().standby_temperature_delta.value != 0) { + // we assume that heating is always slower than cooling, so no need to block + gcode += gcodegen.writer().set_temperature(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, + extruder_id); gcode.pop_back(); gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed } - - return gcode; + } else { + // Use the value from filament settings. That one is absolute, not delta. + gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id); + gcode.pop_back(); + gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed } - std::string OozePrevention::post_toolchange(GCode& gcodegen) - { - return (gcodegen.config().standby_temperature_delta.value != 0) ? - gcodegen.writer().set_temperature(this->_get_temp(gcodegen), gcodegen.config().tool_change_temprature_wait, gcodegen.writer().extruder()->id()) : - std::string(); - } + return gcode; +} - int OozePrevention::_get_temp(const GCode &gcodegen) const - { - // First layer temperature should be used when on the first layer (obviously) and when - // "other layers" is set to zero (which means it should not be used). - return (gcodegen.layer() == nullptr || gcodegen.layer()->id() == 0 - || gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().extruder()->id()) == 0) - ? gcodegen.config().nozzle_temperature_initial_layer.get_at(gcodegen.writer().extruder()->id()) - : gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().extruder()->id()); - } - - // Orca: - // Function to calculate the excess retraction length that should be retracted either before or after wiping - // in order for the wipe operation to respect the filament retraction speed - Wipe::RetractionValues Wipe::calculateWipeRetractionLengths(GCode& gcodegen, bool toolchange) { - auto& writer = gcodegen.writer(); - auto& config = gcodegen.config(); - auto extruder = writer.extruder(); - auto extruder_id = extruder->id(); - auto last_pos = gcodegen.last_pos(); - - // Declare & initialize retraction lengths - double retraction_length_remaining = 0, - retractionBeforeWipe = 0, - retractionDuringWipe = 0; - - // initialise the remaining retraction amount with the full retraction amount. - retraction_length_remaining = toolchange ? extruder->retract_length_toolchange() : extruder->retraction_length(); - - // nothing to retract - return early - if(retraction_length_remaining <=EPSILON) return {0.f,0.f}; - - // calculate retraction before wipe distance from the user setting. Keep adding to this variable any excess retraction needed - // to be performed before the wipe. - retractionBeforeWipe = retraction_length_remaining * extruder->retract_before_wipe(); - retraction_length_remaining -= retractionBeforeWipe; // subtract it from the remaining retraction length - - // all of the retraction is to be done before the wipe - if(retraction_length_remaining <=EPSILON) return {retractionBeforeWipe,0.f}; - - // Calculate wipe speed - double wipe_speed = config.role_based_wipe_speed ? writer.get_current_speed() / 60.0 : config.get_abs_value("wipe_speed"); - wipe_speed = std::max(wipe_speed, 10.0); +std::string OozePrevention::post_toolchange(GCode& gcodegen) +{ + return (gcodegen.config().standby_temperature_delta.value != 0) ? + gcodegen.writer().set_temperature(this->_get_temp(gcodegen), gcodegen.config().tool_change_temprature_wait, + gcodegen.writer().extruder()->id()) : + std::string(); +} - // Process wipe path & calculate wipe path length - double wipe_dist = scale_(config.wipe_distance.get_at(extruder_id)); - Polyline wipe_path = {last_pos}; +int OozePrevention::_get_temp(const GCode& gcodegen) const +{ + // First layer temperature should be used when on the first layer (obviously) and when + // "other layers" is set to zero (which means it should not be used). + return (gcodegen.layer() == nullptr || gcodegen.layer()->id() == 0 || + gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().extruder()->id()) == 0) ? + gcodegen.config().nozzle_temperature_initial_layer.get_at(gcodegen.writer().extruder()->id()) : + gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().extruder()->id()); +} + +// Orca: +// Function to calculate the excess retraction length that should be retracted either before or after wiping +// in order for the wipe operation to respect the filament retraction speed +Wipe::RetractionValues Wipe::calculateWipeRetractionLengths(GCode& gcodegen, bool toolchange) +{ + auto& writer = gcodegen.writer(); + auto& config = gcodegen.config(); + auto extruder = writer.extruder(); + auto extruder_id = extruder->id(); + auto last_pos = gcodegen.last_pos(); + + // Declare & initialize retraction lengths + double retraction_length_remaining = 0, retractionBeforeWipe = 0, retractionDuringWipe = 0; + + // initialise the remaining retraction amount with the full retraction amount. + retraction_length_remaining = toolchange ? extruder->retract_length_toolchange() : extruder->retraction_length(); + + // nothing to retract - return early + if (retraction_length_remaining <= EPSILON) + return {0.f, 0.f}; + + // calculate retraction before wipe distance from the user setting. Keep adding to this variable any excess retraction needed + // to be performed before the wipe. + retractionBeforeWipe = retraction_length_remaining * extruder->retract_before_wipe(); + retraction_length_remaining -= retractionBeforeWipe; // subtract it from the remaining retraction length + + // all of the retraction is to be done before the wipe + if (retraction_length_remaining <= EPSILON) + return {retractionBeforeWipe, 0.f}; + + // Calculate wipe speed + double wipe_speed = config.role_based_wipe_speed ? writer.get_current_speed() / 60.0 : config.get_abs_value("wipe_speed"); + wipe_speed = std::max(wipe_speed, 10.0); + + // Process wipe path & calculate wipe path length + double wipe_dist = scale_(config.wipe_distance.get_at(extruder_id)); + Polyline wipe_path = {last_pos}; + wipe_path.append(this->path.points.begin() + 1, this->path.points.end()); + double wipe_path_length = std::min(wipe_path.length(), wipe_dist); + + // Calculate the maximum retraction amount during wipe + retractionDuringWipe = config.retraction_speed.get_at(extruder_id) * unscale_(wipe_path_length) / wipe_speed; + // If the maximum retraction amount during wipe is too small, return 0 and retract everything prior to the wipe. + if (retractionDuringWipe <= EPSILON) + return {retractionBeforeWipe, 0.f}; + + // If the maximum retraction amount during wipe is greater than any remaining retraction length + // return the remaining retraction length to be retracted during the wipe + if (retractionDuringWipe - retraction_length_remaining > EPSILON) + return {retractionBeforeWipe, retraction_length_remaining}; + + // We will always proceed with incrementing the retraction amount before wiping with the difference + // and return the maximum allowed wipe amount to be retracted during the wipe move + retractionBeforeWipe += retraction_length_remaining - retractionDuringWipe; + return {retractionBeforeWipe, retractionDuringWipe}; +} + +std::string Wipe::wipe(GCode& gcodegen, double length, bool toolchange, bool is_last) +{ + std::string gcode; + + /* Reduce feedrate a bit; travel speed is often too high to move on existing material. + Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ + double _wipe_speed = gcodegen.config().get_abs_value("wipe_speed"); // gcodegen.writer().config.travel_speed.value * 0.8; + if (gcodegen.config().role_based_wipe_speed) + _wipe_speed = gcodegen.writer().get_current_speed() / 60.0; + if (_wipe_speed < 10) + _wipe_speed = 10; + + // SoftFever: allow 100% retract before wipe + if (length >= 0) { + /* Calculate how long we need to travel in order to consume the required + amount of retraction. In other words, how far do we move in XY at wipe_speed + for the time needed to consume retraction_length at retraction_speed? */ + // BBS + double wipe_dist = scale_(gcodegen.config().wipe_distance.get_at(gcodegen.writer().extruder()->id())); + + /* Take the stored wipe path and replace first point with the current actual position + (they might be different, for example, in case of loop clipping). */ + Polyline wipe_path; + wipe_path.append(gcodegen.last_pos()); wipe_path.append(this->path.points.begin() + 1, this->path.points.end()); - double wipe_path_length = std::min(wipe_path.length(), wipe_dist); - // Calculate the maximum retraction amount during wipe - retractionDuringWipe = config.retraction_speed.get_at(extruder_id) * unscale_(wipe_path_length) / wipe_speed; - // If the maximum retraction amount during wipe is too small, return 0 and retract everything prior to the wipe. - if(retractionDuringWipe <= EPSILON) return {retractionBeforeWipe,0.f}; - - // If the maximum retraction amount during wipe is greater than any remaining retraction length - // return the remaining retraction length to be retracted during the wipe - if (retractionDuringWipe - retraction_length_remaining > EPSILON) return {retractionBeforeWipe,retraction_length_remaining}; - - // We will always proceed with incrementing the retraction amount before wiping with the difference - // and return the maximum allowed wipe amount to be retracted during the wipe move - retractionBeforeWipe += retraction_length_remaining - retractionDuringWipe; - return {retractionBeforeWipe, retractionDuringWipe}; + wipe_path.clip_end(wipe_path.length() - wipe_dist); + + // subdivide the retraction in segments + if (!wipe_path.empty()) { + // BBS. Handle short path case. + if (wipe_path.length() < wipe_dist) { + wipe_dist = wipe_path.length(); + // BBS: avoid to divide 0 + wipe_dist = wipe_dist < EPSILON ? EPSILON : wipe_dist; + } + + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n"; + // BBS: don't need to enable cooling makers when this is the last wipe. Because no more cooling layer will clean this "_WIPE" + // Softfever: + std::string cooling_mark = ""; + if (gcodegen.enable_cooling_markers() && !is_last) + cooling_mark = /*gcodegen.config().role_based_wipe_speed ? ";_EXTERNAL_PERIMETER" : */ ";_WIPE"; + + gcode += gcodegen.writer().set_speed(_wipe_speed * 60, "", cooling_mark); + for (const Line& line : wipe_path.lines()) { + double segment_length = line.length(); + double dE = length * (segment_length / wipe_dist); + // BBS: fix this FIXME + // FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. + // Is it here for the cooling markers? Or should it be outside of the cycle? + // gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); + gcode += gcodegen.writer().extrude_to_xy(gcodegen.point_to_gcode(line.b), -dE, "wipe and retract"); + } + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; + gcodegen.set_last_pos(wipe_path.points.back()); + } + + // prevent wiping again on same path + this->reset_path(); } - std::string Wipe::wipe(GCode& gcodegen,double length, bool toolchange, bool is_last) - { - std::string gcode; + return gcode; +} - /* Reduce feedrate a bit; travel speed is often too high to move on existing material. - Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ - double _wipe_speed = gcodegen.config().get_abs_value("wipe_speed");// gcodegen.writer().config.travel_speed.value * 0.8; - if(gcodegen.config().role_based_wipe_speed) - _wipe_speed = gcodegen.writer().get_current_speed() / 60.0; - if(_wipe_speed < 10) - _wipe_speed = 10; +static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2f& wipe_tower_pt) +{ + return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); +} - //SoftFever: allow 100% retract before wipe - if (length >= 0) +std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const +{ + if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) + throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + + std::string gcode; + + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); + + auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { + Vec2f out = Eigen::Rotation2Df(alpha) * pt; + out += m_wipe_tower_pos; + return out; + }; + + Vec2f start_pos = tcr.start_pos; + Vec2f end_pos = tcr.end_pos; + if (!tcr.priming) { + start_pos = transform_wt_pt(start_pos); + end_pos = transform_wt_pt(end_pos); + } + + Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; + float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + + std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); + + // BBS: add partplate logic + Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1)); + + // BBS: toolchange gcode will move to start_pos, + // so only perform movement when printing sparse partition to support upper layer. + // start_pos is the position in plate coordinate. + if (!tcr.priming && tcr.is_finish_first) { + // Move over the wipe tower. + gcode += gcodegen.retract(); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, + "Travel to a Wipe Tower"); + gcode += gcodegen.unretract(); + } + + // BBS: if needed, write the gcode_label_objects_end then priming tower, if the retract, didn't did it. + gcodegen.m_writer.add_object_end_labels(gcode); + + double current_z = gcodegen.writer().get_position().z(); + if (z == -1.) // in case no specific z was provided, print at current_z pos + z = current_z; + if (!is_approx(z, current_z)) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); + gcode += gcodegen.writer().unretract(); + } + + // Process the end filament gcode. + std::string end_filament_gcode_str; + if (gcodegen.writer().extruder() != nullptr) { + // Process the custom filament_end_gcode in case of single_extruder_multi_material. + unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); + const std::string& filament_end_gcode = gcodegen.config().filament_end_gcode.get_at(old_extruder_id); + if (gcodegen.writer().extruder() != nullptr && !filament_end_gcode.empty()) { + end_filament_gcode_str = gcodegen.placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_extruder_id); + check_add_eol(end_filament_gcode_str); + } + } + // BBS: increase toolchange count + gcodegen.m_toolchange_count++; + + // BBS: should be placed before toolchange parsing + std::string toolchange_retract_str = gcodegen.retract(true, false); + check_add_eol(toolchange_retract_str); + + // Process the custom change_filament_gcode. If it is empty, provide a simple Tn command to change the filament. + // Otherwise, leave control to the user completely. + std::string toolchange_gcode_str; + const std::string& change_filament_gcode = gcodegen.config().change_filament_gcode.value; + // m_max_layer_z = std::max(m_max_layer_z, tcr.print_z); + if (!change_filament_gcode.empty()) { + DynamicConfig config; + int previous_extruder_id = gcodegen.writer().extruder() ? (int) gcodegen.writer().extruder()->id() : -1; + config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); + config.set_key_value("next_extruder", new ConfigOptionInt((int) new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); + config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); + // config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + // BBS { - /* Calculate how long we need to travel in order to consume the required - amount of retraction. In other words, how far do we move in XY at wipe_speed - for the time needed to consume retraction_length at retraction_speed? */ - // BBS - double wipe_dist = scale_(gcodegen.config().wipe_distance.get_at(gcodegen.writer().extruder()->id())); - - /* Take the stored wipe path and replace first point with the current actual position - (they might be different, for example, in case of loop clipping). */ - Polyline wipe_path; - wipe_path.append(gcodegen.last_pos()); - wipe_path.append( - this->path.points.begin() + 1, - this->path.points.end() - ); - - wipe_path.clip_end(wipe_path.length() - wipe_dist); - - // subdivide the retraction in segments - if (!wipe_path.empty()) { - // BBS. Handle short path case. - if (wipe_path.length() < wipe_dist) { - wipe_dist = wipe_path.length(); - //BBS: avoid to divide 0 - wipe_dist = wipe_dist < EPSILON ? EPSILON : wipe_dist; - } - - // add tag for processor - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n"; - //BBS: don't need to enable cooling makers when this is the last wipe. Because no more cooling layer will clean this "_WIPE" - //Softfever: - std::string cooling_mark = ""; - if (gcodegen.enable_cooling_markers() && !is_last) - cooling_mark = /*gcodegen.config().role_based_wipe_speed ? ";_EXTERNAL_PERIMETER" : */";_WIPE"; - - gcode += gcodegen.writer().set_speed(_wipe_speed * 60, "", cooling_mark); - for (const Line& line : wipe_path.lines()) { - double segment_length = line.length(); - double dE = length * (segment_length / wipe_dist); - //BBS: fix this FIXME - //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. - // Is it here for the cooling markers? Or should it be outside of the cycle? - //gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); - gcode += gcodegen.writer().extrude_to_xy( - gcodegen.point_to_gcode(line.b), - -dE, - "wipe and retract" - ); - } - // add tag for processor - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; - gcodegen.set_last_pos(wipe_path.points.back()); - } - - // prevent wiping again on same path - this->reset_path(); - } - - return gcode; - } - - static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2f& wipe_tower_pt) - { - return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); - } - - std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z) const - { - if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) - throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); - - std::string gcode; - - // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) - // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position - float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); - - auto transform_wt_pt = [&alpha, this](const Vec2f &pt) -> Vec2f { - Vec2f out = Eigen::Rotation2Df(alpha) * pt; - out += m_wipe_tower_pos; - return out; - }; - - Vec2f start_pos = tcr.start_pos; - Vec2f end_pos = tcr.end_pos; - if (!tcr.priming) { - start_pos = transform_wt_pt(start_pos); - end_pos = transform_wt_pt(end_pos); - } - - Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; - float wipe_tower_rotation = tcr.priming ? 0.f : alpha; - - std::string tcr_rotated_gcode = post_process_wipe_tower_moves(gcodegen, tcr, wipe_tower_offset, wipe_tower_rotation); - - // BBS: add partplate logic - Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1)); - - // BBS: toolchange gcode will move to start_pos, - // so only perform movement when printing sparse partition to support upper layer. - // start_pos is the position in plate coordinate. - if (!tcr.priming && tcr.is_finish_first) { - // Move over the wipe tower. - gcode += gcodegen.retract(); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); - gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, - "Travel to a Wipe Tower"); - gcode += gcodegen.unretract(); - } - - // BBS: if needed, write the gcode_label_objects_end then priming tower, if the retract, didn't did it. - gcodegen.m_writer.add_object_end_labels(gcode); - - double current_z = gcodegen.writer().get_position().z(); - if (z == -1.) // in case no specific z was provided, print at current_z pos - z = current_z; - if (!is_approx(z, current_z)) { - gcode += gcodegen.writer().retract(); - gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); - gcode += gcodegen.writer().unretract(); - } - - // Process the end filament gcode. - std::string end_filament_gcode_str; - if (gcodegen.writer().extruder() != nullptr) { - // Process the custom filament_end_gcode in case of single_extruder_multi_material. - unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); - const std::string &filament_end_gcode = gcodegen.config().filament_end_gcode.get_at(old_extruder_id); - if (gcodegen.writer().extruder() != nullptr && !filament_end_gcode.empty()) { - end_filament_gcode_str = gcodegen.placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_extruder_id); - check_add_eol(end_filament_gcode_str); - } - } - // BBS: increase toolchange count - gcodegen.m_toolchange_count++; - - // BBS: should be placed before toolchange parsing - std::string toolchange_retract_str = gcodegen.retract(true, false); - check_add_eol(toolchange_retract_str); - - // Process the custom change_filament_gcode. If it is empty, provide a simple Tn command to change the filament. - // Otherwise, leave control to the user completely. - std::string toolchange_gcode_str; - const std::string &change_filament_gcode = gcodegen.config().change_filament_gcode.value; - // m_max_layer_z = std::max(m_max_layer_z, tcr.print_z); - if (!change_filament_gcode.empty()) { - DynamicConfig config; - int previous_extruder_id = gcodegen.writer().extruder() ? (int) gcodegen.writer().extruder()->id() : -1; - config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - config.set_key_value("next_extruder", new ConfigOptionInt((int) new_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); - config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); - // config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - // BBS - { - GCodeWriter &gcode_writer = gcodegen.m_writer; - FullPrintConfig &full_config = gcodegen.m_config; - float old_retract_length = (gcode_writer.extruder() != nullptr && previous_extruder_id >= 0) ? - full_config.retraction_length.get_at(previous_extruder_id) : 0; - float new_retract_length = full_config.retraction_length.get_at(new_extruder_id); - float old_retract_length_toolchange = (gcode_writer.extruder() != nullptr && previous_extruder_id >= 0) ? - full_config.retract_length_toolchange.get_at(previous_extruder_id) : 0; - float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_extruder_id); - int old_filament_temp = (gcode_writer.extruder() != nullptr && previous_extruder_id >= 0) ? + GCodeWriter& gcode_writer = gcodegen.m_writer; + FullPrintConfig& full_config = gcodegen.m_config; + float old_retract_length = gcode_writer.extruder() != nullptr ? full_config.retraction_length.get_at(previous_extruder_id) : 0; + float new_retract_length = full_config.retraction_length.get_at(new_extruder_id); + float old_retract_length_toolchange = gcode_writer.extruder() != nullptr ? + full_config.retract_length_toolchange.get_at(previous_extruder_id) : + 0; + float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_extruder_id); + int old_filament_temp = gcode_writer.extruder() != nullptr ? (gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : full_config.nozzle_temperature.get_at(previous_extruder_id)) : 210; - int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_extruder_id) : - full_config.nozzle_temperature.get_at(new_extruder_id); - Vec3d nozzle_pos = gcode_writer.get_position(); + int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_extruder_id) : + full_config.nozzle_temperature.get_at(new_extruder_id); + Vec3d nozzle_pos = gcode_writer.get_position(); - float purge_volume = tcr.purge_volume < EPSILON ? 0 : std::max(tcr.purge_volume, g_min_purge_volume); - float filament_area = float((M_PI / 4.f) * pow(full_config.filament_diameter.get_at(new_extruder_id), 2)); - float purge_length = purge_volume / filament_area; + float purge_volume = tcr.purge_volume < EPSILON ? 0 : std::max(tcr.purge_volume, g_min_purge_volume); + float filament_area = float((M_PI / 4.f) * pow(full_config.filament_diameter.get_at(new_extruder_id), 2)); + float purge_length = purge_volume / filament_area; - int old_filament_e_feedrate = gcode_writer.extruder() != nullptr ? - (int) (60.0 * full_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / - filament_area) : - 200; - old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; - int new_filament_e_feedrate = (int) (60.0 * full_config.filament_max_volumetric_speed.get_at(new_extruder_id) / - filament_area); - new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; + int old_filament_e_feedrate = gcode_writer.extruder() != nullptr ? + (int) (60.0 * full_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / + filament_area) : + 200; + old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; + int new_filament_e_feedrate = (int) (60.0 * full_config.filament_max_volumetric_speed.get_at(new_extruder_id) / filament_area); + new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; - config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); - config.set_key_value("relative_e_axis", new ConfigOptionBool(full_config.use_relative_e_distances)); - config.set_key_value("toolchange_count", new ConfigOptionInt((int) gcodegen.m_toolchange_count)); - // BBS: fan speed is useless placeholer now, but we don't remove it to avoid - // slicing error in old change_filament_gcode in old 3MF - config.set_key_value("fan_speed", new ConfigOptionInt((int) 0)); - config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); - config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); - config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); - config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); - config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); - config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); - config.set_key_value("x_after_toolchange", new ConfigOptionFloat(start_pos(0))); - config.set_key_value("y_after_toolchange", new ConfigOptionFloat(start_pos(1))); - config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); - config.set_key_value("first_flush_volume", new ConfigOptionFloat(purge_length / 2.f)); - config.set_key_value("second_flush_volume", new ConfigOptionFloat(purge_length / 2.f)); - config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate)); - config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate)); - config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x()))); - config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y()))); - config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x()))); - config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y()))); - config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); - config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); + config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); + config.set_key_value("relative_e_axis", new ConfigOptionBool(full_config.use_relative_e_distances)); + config.set_key_value("toolchange_count", new ConfigOptionInt((int) gcodegen.m_toolchange_count)); + // BBS: fan speed is useless placeholer now, but we don't remove it to avoid + // slicing error in old change_filament_gcode in old 3MF + config.set_key_value("fan_speed", new ConfigOptionInt((int) 0)); + config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); + config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); + config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); + config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); + config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); + config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); + config.set_key_value("x_after_toolchange", new ConfigOptionFloat(start_pos(0))); + config.set_key_value("y_after_toolchange", new ConfigOptionFloat(start_pos(1))); + config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); + config.set_key_value("first_flush_volume", new ConfigOptionFloat(purge_length / 2.f)); + config.set_key_value("second_flush_volume", new ConfigOptionFloat(purge_length / 2.f)); + config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate)); + config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate)); + config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x()))); + config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y()))); + config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x()))); + config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y()))); + config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); + config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); - config.set_key_value("flush_length", new ConfigOptionFloat(purge_length)); + config.set_key_value("flush_length", new ConfigOptionFloat(purge_length)); - int flush_count = std::min(g_max_flush_count, (int) std::round(purge_volume / g_purge_volume_one_time)); - float flush_unit = purge_length / flush_count; - int flush_idx = 0; - for (; flush_idx < flush_count; flush_idx++) { - char key_value[64] = {0}; - snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); - config.set_key_value(key_value, new ConfigOptionFloat(flush_unit)); - } - - for (; flush_idx < g_max_flush_count; flush_idx++) { - char key_value[64] = {0}; - snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); - config.set_key_value(key_value, new ConfigOptionFloat(0.f)); - } + int flush_count = std::min(g_max_flush_count, (int) std::round(purge_volume / g_purge_volume_one_time)); + float flush_unit = purge_length / flush_count; + int flush_idx = 0; + for (; flush_idx < flush_count; flush_idx++) { + char key_value[64] = {0}; + snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); + config.set_key_value(key_value, new ConfigOptionFloat(flush_unit)); } - toolchange_gcode_str = gcodegen.placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_extruder_id, + + for (; flush_idx < g_max_flush_count; flush_idx++) { + char key_value[64] = {0}; + snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); + config.set_key_value(key_value, new ConfigOptionFloat(0.f)); + } + } + toolchange_gcode_str = gcodegen.placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + + // retract before toolchange + toolchange_gcode_str = toolchange_retract_str + toolchange_gcode_str; + // BBS + { + // BBS: current position and fan_speed is unclear after interting change_filament_gcode + check_add_eol(toolchange_gcode_str); + toolchange_gcode_str += ";_FORCE_RESUME_FAN_SPEED\n"; + gcodegen.writer().set_current_position_clear(false); + // BBS: check whether custom gcode changes the z position. Update if changed + double temp_z_after_tool_change; + if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_str, temp_z_after_tool_change)) { + Vec3d pos = gcodegen.writer().get_position(); + pos(2) = temp_z_after_tool_change; + gcodegen.writer().set_position(pos); + } + } + + // move to start_pos for wiping after toolchange + std::string start_pos_str; + start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, + "Move to start pos"); + check_add_eol(start_pos_str); + toolchange_gcode_str += start_pos_str; + + // unretract before wiping + toolchange_gcode_str += gcodegen.unretract(); + check_add_eol(toolchange_gcode_str); + } + + std::string toolchange_command; + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) + toolchange_command = gcodegen.writer().toolchange(new_extruder_id); + if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) + toolchange_gcode_str += toolchange_command; + else { + // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. + } + + gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); + gcodegen.placeholder_parser().set("retraction_distance_when_cut", + gcodegen.m_config.retraction_distances_when_cut.get_at(new_extruder_id)); + gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(new_extruder_id)); + + // Process the start filament gcode. + std::string start_filament_gcode_str; + const std::string& filament_start_gcode = gcodegen.config().filament_start_gcode.get_at(new_extruder_id); + if (!filament_start_gcode.empty()) { + // Process the filament_start_gcode for the active filament only. + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); + start_filament_gcode_str = gcodegen.placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_extruder_id, &config); - check_add_eol(toolchange_gcode_str); + check_add_eol(start_filament_gcode_str); + } - // retract before toolchange - toolchange_gcode_str = toolchange_retract_str + toolchange_gcode_str; + // Insert the end filament, toolchange, and start filament gcode into the generated gcode. + DynamicConfig config; + config.set_key_value("filament_end_gcode", new ConfigOptionString(end_filament_gcode_str)); + config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str)); + config.set_key_value("filament_start_gcode", new ConfigOptionString(start_filament_gcode_str)); + std::string tcr_gcode, + tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); + gcode += tcr_gcode; + check_add_eol(toolchange_gcode_str); + + // SoftFever: set new PA for new filament + if (gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { + gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); + // Orca: Adaptive PA + // Reset Adaptive PA processor last PA value + gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id)); + } + + // A phony move to the end position at the wipe tower. + gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast()); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d)); + if (!is_approx(z, current_z)) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); + gcode += gcodegen.writer().unretract(); + } + + else { + // Prepare a future wipe. + gcodegen.m_wipe.reset_path(); + for (const Vec2f& wipe_pt : tcr.wipe_path) + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); + } + + // Let the planner know we are traveling between objects. + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + return gcode; +} + +std::string WipeTowerIntegration::append_tcr2(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const +{ + if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) + throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + + std::string gcode; + + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); + + auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { + Vec2f out = Eigen::Rotation2Df(alpha) * pt; + out += m_wipe_tower_pos; + return out; + }; + + Vec2f start_pos = tcr.start_pos; + Vec2f end_pos = tcr.end_pos; + if (!tcr.priming) { + start_pos = transform_wt_pt(start_pos); + end_pos = transform_wt_pt(end_pos); + } + + Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; + float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1)); + + // For Snapmaker Artision + gcodegen.m_next_wipe_x = 0; + gcodegen.m_next_wipe_y = 0; + auto transformed_pos = Eigen::Rotation2Df(wipe_tower_rotation) * tcr.start_pos + wipe_tower_offset; + gcodegen.m_next_wipe_x = transformed_pos(0); + gcodegen.m_next_wipe_y = transformed_pos(1); + + std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); + + gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't). + + double current_z = gcodegen.writer().get_position().z(); + + if (z == -1.) // in case no specific z was provided, print at current_z pos + z = current_z; + + const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id); + const bool will_go_down = !is_approx(z, current_z); + const bool is_ramming = (gcodegen.config().single_extruder_multi_material) || + (!gcodegen.config().single_extruder_multi_material && + gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool)); + const bool should_travel_to_tower = !tcr.priming && (tcr.force_travel // wipe tower says so + || !needs_toolchange // this is just finishing the tower with no toolchange + || is_ramming); + + if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) { + // FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges, + // then we could simplify the condition and make it more readable. + auto type = ZHopType(gcodegen.m_config.z_hop_types.get_at(gcodegen.m_writer.extruder()->id())); + if (type == ZHopType::zhtAuto) { + type = ZHopType::zhtSpiral; + } + auto lift_type = gcodegen.to_lift_type(type); + + if (gcodegen.m_config.z_hop_when_prime.get_at(gcodegen.m_writer.extruder()->id())) { + gcode += gcodegen.retract(false, false, lift_type); + } + + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, + "Travel to a Wipe Tower"); + gcode += gcodegen.unretract(); + } else { + // When this is multiextruder printer without any ramming, we can just change + // the tool without travelling to the tower. + } + + if (will_go_down) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); + gcode += gcodegen.writer().unretract(); + } + + std::string toolchange_gcode_str; + std::string deretraction_str; + if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { + if (is_ramming) + gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. + toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z + if (gcodegen.config().enable_prime_tower) { + deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true); + Vec3d position{gcodegen.writer().get_position()}; + position.z() = z; + gcodegen.writer().set_position(position); + deretraction_str += gcodegen.unretract(); + } + } + + // Insert the toolchange and deretraction gcode into the generated gcode. + + DynamicConfig config; + config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str)); + config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); + config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); + + std::string tcr_gcode, + tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); + gcode += tcr_gcode; + check_add_eol(toolchange_gcode_str); + + // SoftFever: set new PA for new filament + if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { + gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); + // Orca: Adaptive PA + // Reset Adaptive PA processor last PA value + gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id)); + } + + // A phony move to the end position at the wipe tower. + gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast()); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d)); + if (!is_approx(z, current_z)) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); + gcode += gcodegen.writer().unretract(); + } + + else { + // Prepare a future wipe. + gcodegen.m_wipe.reset_path(); + for (const Vec2f& wipe_pt : tcr.wipe_path) + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); + } + + // Let the planner know we are traveling between objects. + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + return gcode; +} + +// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode +// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) +std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, + const Vec2f& translation, + float angle) const +{ + Vec2f extruder_offset; + if (m_single_extruder_multi_material) + extruder_offset = m_extruder_offsets[0].cast(); + else + extruder_offset = m_extruder_offsets[tcr.initial_tool].cast(); + + std::istringstream gcode_str(tcr.gcode); + std::string gcode_out; + std::string line; + Vec2f pos = tcr.start_pos; + auto trans_pos = [wt_rot = Eigen::Rotation2Df(angle), &translation](const Vec2f& p) -> Vec2f { return wt_rot * p + translation; }; + Vec2f transformed_pos = trans_pos(pos); + Vec2f old_pos(-1000.1f, -1000.1f); + + bool isFirstTransform = true; + + while (gcode_str) { + std::getline(gcode_str, line); // we read the gcode line by line + + // All G1 commands should be translated and rotated. X and Y coords are + // only pushed to the output when they differ from last time. + // WT generator can override this by appending the never_skip_tag + if (line.find("G1 ") == 0 || line.find("G2 ") == 0 || line.find("G3 ") == 0) { + std::string cur_gcode_start = line.find("G1 ") == 0 ? "G1 " : (line.find("G2 ") == 0 ? "G2 " : "G3 "); + bool never_skip = false; + auto it = line.find(WipeTower::never_skip_tag()); + if (it != std::string::npos) { + // remove the tag and remember we saw it + never_skip = true; + line.erase(it, it + WipeTower::never_skip_tag().size()); + } + std::ostringstream line_out; + std::istringstream line_str(line); + line_str >> std::noskipws; // don't skip whitespace + char ch = 0; + while (line_str >> ch) { + if (ch == 'X' || ch == 'Y') + line_str >> (ch == 'X' ? pos.x() : pos.y()); + else + line_out << ch; + } + + transformed_pos = trans_pos(pos); + + if (transformed_pos != old_pos || never_skip) { + line = line_out.str(); + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) << cur_gcode_start; + if (transformed_pos.x() != old_pos.x() || never_skip) + oss << " X" << transformed_pos.x() - extruder_offset.x(); + if (transformed_pos.y() != old_pos.y() || never_skip) + oss << " Y" << transformed_pos.y() - extruder_offset.y(); + oss << " "; + line.replace(line.find(cur_gcode_start), 3, oss.str()); + old_pos = transformed_pos; + } + } + + gcode_out += line + "\n"; + + // If this was a toolchange command, we should change current extruder offset + if (line == "[change_filament_gcode]") { // BBS - { - // BBS: current position and fan_speed is unclear after interting change_filament_gcode - check_add_eol(toolchange_gcode_str); - toolchange_gcode_str += ";_FORCE_RESUME_FAN_SPEED\n"; - gcodegen.writer().set_current_position_clear(false); - // BBS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_tool_change; - if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_str, temp_z_after_tool_change)) { - Vec3d pos = gcodegen.writer().get_position(); - pos(2) = temp_z_after_tool_change; - gcodegen.writer().set_position(pos); - } - } + if (!m_single_extruder_multi_material) { + extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); - // move to start_pos for wiping after toolchange - std::string start_pos_str; - start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, - "Move to start pos"); - check_add_eol(start_pos_str); - toolchange_gcode_str += start_pos_str; - - // unretract before wiping - toolchange_gcode_str += gcodegen.unretract(); - check_add_eol(toolchange_gcode_str); - } - - std::string toolchange_command; - if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) - toolchange_command = gcodegen.writer().toolchange(new_extruder_id); - if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) - toolchange_gcode_str += toolchange_command; - else { - // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. - } - - gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); - gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(new_extruder_id)); - gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(new_extruder_id)); - - // Process the start filament gcode. - std::string start_filament_gcode_str; - const std::string &filament_start_gcode = gcodegen.config().filament_start_gcode.get_at(new_extruder_id); - if (!filament_start_gcode.empty()) { - // Process the filament_start_gcode for the active filament only. - DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); - start_filament_gcode_str = gcodegen.placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_extruder_id, - &config); - check_add_eol(start_filament_gcode_str); - } - - // Insert the end filament, toolchange, and start filament gcode into the generated gcode. - DynamicConfig config; - config.set_key_value("filament_end_gcode", new ConfigOptionString(end_filament_gcode_str)); - config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str)); - config.set_key_value("filament_start_gcode", new ConfigOptionString(start_filament_gcode_str)); - std::string tcr_gcode, - tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); - unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); - gcode += tcr_gcode; - check_add_eol(toolchange_gcode_str); - - // SoftFever: set new PA for new filament - if (gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { - gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); - // Orca: Adaptive PA - // Reset Adaptive PA processor last PA value - gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id)); - } - - // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast()); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d)); - if (!is_approx(z, current_z)) { - gcode += gcodegen.writer().retract(); - gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); - gcode += gcodegen.writer().unretract(); - } - - else { - // Prepare a future wipe. - gcodegen.m_wipe.reset_path(); - for (const Vec2f &wipe_pt : tcr.wipe_path) - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); - } - - // Let the planner know we are traveling between objects. - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); - return gcode; - } - - std::string WipeTowerIntegration::append_tcr2(GCode &gcodegen, - const WipeTower::ToolChangeResult &tcr, - int new_extruder_id, - double z) const - { - if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) - throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); - - std::string gcode; - - // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) - // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position - float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); - - auto transform_wt_pt = [&alpha, this](const Vec2f &pt) -> Vec2f { - Vec2f out = Eigen::Rotation2Df(alpha) * pt; - out += m_wipe_tower_pos; - return out; - }; - - Vec2f start_pos = tcr.start_pos; - Vec2f end_pos = tcr.end_pos; - if (!tcr.priming) { - start_pos = transform_wt_pt(start_pos); - end_pos = transform_wt_pt(end_pos); - } - - Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; - float wipe_tower_rotation = tcr.priming ? 0.f : alpha; - Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1)); - - // For Snapmaker Artision - gcodegen.m_next_wipe_x = 0; - gcodegen.m_next_wipe_y = 0; - auto transformed_pos = Eigen::Rotation2Df(wipe_tower_rotation) * tcr.start_pos + wipe_tower_offset; - gcodegen.m_next_wipe_x = transformed_pos(0); - gcodegen.m_next_wipe_y = transformed_pos(1); - - std::string tcr_rotated_gcode = post_process_wipe_tower_moves(gcodegen, tcr, wipe_tower_offset, wipe_tower_rotation); - - gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't). - - double current_z = gcodegen.writer().get_position().z(); - - if (z == -1.) // in case no specific z was provided, print at current_z pos - z = current_z; - - const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id); - const bool will_go_down = !is_approx(z, current_z); - const bool is_ramming = (gcodegen.config().single_extruder_multi_material) || - (!gcodegen.config().single_extruder_multi_material && - gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool)); - const bool should_travel_to_tower = !tcr.priming && (tcr.force_travel // wipe tower says so - || !needs_toolchange // this is just finishing the tower with no toolchange - || is_ramming); - - if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) { - // FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges, - // then we could simplify the condition and make it more readable. - auto type = ZHopType(gcodegen.m_config.z_hop_types.get_at(gcodegen.m_writer.extruder()->id())); - if (type == ZHopType::zhtAuto) { - type = ZHopType::zhtSpiral; - } - auto lift_type = gcodegen.to_lift_type(type); - - if (gcodegen.m_config.z_hop_when_prime.get_at(gcodegen.m_writer.extruder()->id())) { - gcode += gcodegen.retract(false, false, lift_type); - } - - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); - gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, "Travel to a Wipe Tower"); - gcode += gcodegen.unretract(); - } else { - // When this is multiextruder printer without any ramming, we can just change - // the tool without travelling to the tower. - } - - if (will_go_down) { - gcode += gcodegen.writer().retract(); - gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); - gcode += gcodegen.writer().unretract(); - } - - std::string toolchange_gcode_str; - std::string deretraction_str; - if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { - if (is_ramming) - gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. - toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z - if (gcodegen.config().enable_prime_tower) { - deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true); - Vec3d position{gcodegen.writer().get_position()}; - position.z() = z; - gcodegen.writer().set_position(position); - deretraction_str += gcodegen.unretract(); - } - } - - // Insert the toolchange and deretraction gcode into the generated gcode. - - DynamicConfig config; - config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str)); - config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str)); - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); - config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); - - std::string tcr_gcode, - tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); - unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); - gcode += tcr_gcode; - check_add_eol(toolchange_gcode_str); - - // SoftFever: set new PA for new filament - if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { - gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); - // Orca: Adaptive PA - // Reset Adaptive PA processor last PA value - gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id)); - } - - // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast()); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d)); - if (!is_approx(z, current_z)) { - gcode += gcodegen.writer().retract(); - gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); - gcode += gcodegen.writer().unretract(); - } - - else { - // Prepare a future wipe. - gcodegen.m_wipe.reset_path(); - for (const Vec2f &wipe_pt : tcr.wipe_path) - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); - } - - // Let the planner know we are traveling between objects. - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); - return gcode; - } - - // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode - // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) - std::string WipeTowerIntegration::post_process_wipe_tower_moves(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const - { - Vec2f extruder_offset; - if (m_single_extruder_multi_material) - extruder_offset = m_extruder_offsets[0].cast(); - else { - int physical_extruder = gcodegen.writer().get_physical_extruder(tcr.initial_tool); - extruder_offset = m_extruder_offsets[physical_extruder].cast(); - } - - std::istringstream gcode_str(tcr.gcode); - std::string gcode_out; - std::string line; - Vec2f pos = tcr.start_pos; - auto trans_pos = [wt_rot = Eigen::Rotation2Df(angle), &translation](const Vec2f& p) -> Vec2f { return wt_rot * p + translation; }; - Vec2f transformed_pos = trans_pos(pos); - Vec2f old_pos(-1000.1f, -1000.1f); - - bool isFirstTransform = true; - - while (gcode_str) { - std::getline(gcode_str, line); // we read the gcode line by line - - // All G1 commands should be translated and rotated. X and Y coords are - // only pushed to the output when they differ from last time. - // WT generator can override this by appending the never_skip_tag - if (line.find("G1 ") == 0 || line.find("G2 ") == 0 || line.find("G3 ") == 0) { - std::string cur_gcode_start = line.find("G1 ") == 0 ? "G1 " : (line.find("G2 ") == 0 ? "G2 " : "G3 "); - bool never_skip = false; - auto it = line.find(WipeTower::never_skip_tag()); - if (it != std::string::npos) { - // remove the tag and remember we saw it - never_skip = true; - line.erase(it, it + WipeTower::never_skip_tag().size()); - } - std::ostringstream line_out; - std::istringstream line_str(line); - line_str >> std::noskipws; // don't skip whitespace - char ch = 0; - while (line_str >> ch) { - if (ch == 'X' || ch == 'Y') - line_str >> (ch == 'X' ? pos.x() : pos.y()); - else - line_out << ch; - } - - transformed_pos = trans_pos(pos); - - if (transformed_pos != old_pos || never_skip) { - line = line_out.str(); + // If the extruder offset changed, add an extra move so everything is continuous + if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { std::ostringstream oss; - oss << std::fixed << std::setprecision(3) << cur_gcode_start; - if (transformed_pos.x() != old_pos.x() || never_skip) - oss << " X" << transformed_pos.x() - extruder_offset.x(); - if (transformed_pos.y() != old_pos.y() || never_skip) - oss << " Y" << transformed_pos.y() - extruder_offset.y(); - oss << " "; - line.replace(line.find(cur_gcode_start), 3, oss.str()); - old_pos = transformed_pos; - } - } - - gcode_out += line + "\n"; - - // If this was a toolchange command, we should change current extruder offset - if (line == "[change_filament_gcode]") { - // BBS - if (!m_single_extruder_multi_material) { - int physical_extruder_new = gcodegen.writer().get_physical_extruder(tcr.new_tool); - int physical_extruder_initial = gcodegen.writer().get_physical_extruder(tcr.initial_tool); - Vec2f new_extruder_offset = m_extruder_offsets[physical_extruder_new].cast(); - - // If the extruder offset changed, add an extra move so everything is continuous - if (new_extruder_offset != m_extruder_offsets[physical_extruder_initial].cast()) { - std::ostringstream oss; - oss << std::fixed << std::setprecision(3) - << "G1 X" << transformed_pos.x() - new_extruder_offset.x() - << " Y" << transformed_pos.y() - new_extruder_offset.y() - << "\n"; - gcode_out += oss.str(); - } - extruder_offset = new_extruder_offset; + oss << std::fixed << std::setprecision(3) << "G1 X" << transformed_pos.x() - extruder_offset.x() << " Y" + << transformed_pos.y() - extruder_offset.y() << "\n"; + gcode_out += oss.str(); } } } - return gcode_out; } + return gcode_out; +} - std::string WipeTowerIntegration::prime(GCode &gcodegen) - { - std::string gcode; - if (!gcodegen.is_BBL_Printer()) { - for (const WipeTower::ToolChangeResult &tcr : m_priming) { - if (!tcr.extrusions.empty()) - gcode += append_tcr2(gcodegen, tcr, tcr.new_tool); - } +std::string WipeTowerIntegration::prime(GCode& gcodegen) +{ + std::string gcode; + if (!gcodegen.is_BBL_Printer()) { + for (const WipeTower::ToolChangeResult& tcr : m_priming) { + if (!tcr.extrusions.empty()) + gcode += append_tcr2(gcodegen, tcr, tcr.new_tool); } + } + return gcode; +} + +std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer) +{ + std::string gcode; + + assert(m_layer_idx >= 0); + if (m_layer_idx >= (int) m_tool_changes.size()) return gcode; - } - - std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) - { - std::string gcode; - - assert(m_layer_idx >= 0); - if (m_layer_idx >= (int) m_tool_changes.size()) - return gcode; - if (!gcodegen.is_BBL_Printer()) { - if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { - if (m_layer_idx < (int) m_tool_changes.size()) { - if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) - throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); - - // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, - // resulting in a wipe tower with sparse layers. - double wipe_tower_z = -1; - bool ignore_sparse = false; - if (gcodegen.config().wipe_tower_no_sparse_layers.value) { - wipe_tower_z = m_last_wipe_tower_print_z; - ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && - m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && - m_layer_idx != 0); - if (m_tool_change_idx == 0 && !ignore_sparse) - wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; - } - - if (!ignore_sparse) { - gcode += append_tcr2(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); - m_last_wipe_tower_print_z = wipe_tower_z; - } - } - } - } else { - // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, - // resulting in a wipe tower with sparse layers. - double wipe_tower_z = -1; - bool ignore_sparse = false; - if (gcodegen.config().wipe_tower_no_sparse_layers.value) { - wipe_tower_z = m_last_wipe_tower_print_z; - ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && - m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); - if (m_tool_change_idx == 0 && !ignore_sparse) - wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; - } - - if (m_enable_timelapse_print && m_is_first_print) { - gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][0], m_tool_changes[m_layer_idx][0].new_tool, wipe_tower_z); - m_tool_change_idx++; - m_is_first_print = false; - } - - if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (!gcodegen.is_BBL_Printer()) { + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (m_layer_idx < (int) m_tool_changes.size()) { if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); + // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, + // resulting in a wipe tower with sparse layers. + double wipe_tower_z = -1; + bool ignore_sparse = false; + if (gcodegen.config().wipe_tower_no_sparse_layers.value) { + wipe_tower_z = m_last_wipe_tower_print_z; + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && + m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && + m_layer_idx != 0); + if (m_tool_change_idx == 0 && !ignore_sparse) + wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; + } + if (!ignore_sparse) { - gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); + gcode += append_tcr2(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); m_last_wipe_tower_print_z = wipe_tower_z; } } } - - return gcode; - } - - bool WipeTowerIntegration::is_empty_wipe_tower_gcode(GCode &gcodegen, int extruder_id, bool finish_layer) - { - assert(m_layer_idx >= 0); - if (m_layer_idx >= (int) m_tool_changes.size()) - return true; - + } else { + // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, + // resulting in a wipe tower with sparse layers. + double wipe_tower_z = -1; bool ignore_sparse = false; if (gcodegen.config().wipe_tower_no_sparse_layers.value) { - ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); + wipe_tower_z = m_last_wipe_tower_print_z; + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && + m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); + if (m_tool_change_idx == 0 && !ignore_sparse) + wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; } if (m_enable_timelapse_print && m_is_first_print) { - return false; + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][0], m_tool_changes[m_layer_idx][0].new_tool, wipe_tower_z); + m_tool_change_idx++; + m_is_first_print = false; } if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { @@ -1019,71 +977,100 @@ static std::vector get_path_of_change_filament(const Print& print) throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); if (!ignore_sparse) { - return false; + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); + m_last_wipe_tower_print_z = wipe_tower_z; } } + } + return gcode; +} + +bool WipeTowerIntegration::is_empty_wipe_tower_gcode(GCode& gcodegen, int extruder_id, bool finish_layer) +{ + assert(m_layer_idx >= 0); + if (m_layer_idx >= (int) m_tool_changes.size()) return true; + + bool ignore_sparse = false; + if (gcodegen.config().wipe_tower_no_sparse_layers.value) { + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && + m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); } - // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. - std::string WipeTowerIntegration::finalize(GCode &gcodegen) - { - std::string gcode; - if (!gcodegen.is_BBL_Printer()) { - if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON) - gcode += gcodegen.change_layer(m_final_purge.print_z); - gcode += append_tcr2(gcodegen, m_final_purge, -1); + if (m_enable_timelapse_print && m_is_first_print) { + return false; + } + + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) + throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); + + if (!ignore_sparse) { + return false; } - - return gcode; } - const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; + return true; +} + +// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. +std::string WipeTowerIntegration::finalize(GCode& gcodegen) +{ + std::string gcode; + if (!gcodegen.is_BBL_Printer()) { + if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON) + gcode += gcodegen.change_layer(m_final_purge.print_z); + gcode += append_tcr2(gcodegen, m_final_purge, -1); + } + + return gcode; +} + +const std::vector ColorPrintColors::Colors = {"#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6"}; #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) -#define PHYSICAL_EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.get_physical_extruder(m_writer.extruder()->id())) void GCode::PlaceholderParserIntegration::reset() { this->failed_templates.clear(); this->output_config.clear(); - this->opt_position = nullptr; - this->opt_zhop = nullptr; - this->opt_e_position = nullptr; - this->opt_e_retracted = nullptr; - this->opt_e_restart_extra = nullptr; - this->opt_extruded_volume = nullptr; - this->opt_extruded_weight = nullptr; + this->opt_position = nullptr; + this->opt_zhop = nullptr; + this->opt_e_position = nullptr; + this->opt_e_retracted = nullptr; + this->opt_e_restart_extra = nullptr; + this->opt_extruded_volume = nullptr; + this->opt_extruded_weight = nullptr; this->opt_extruded_volume_total = nullptr; this->opt_extruded_weight_total = nullptr; - this->num_extruders = 0; + this->num_extruders = 0; this->position.clear(); this->e_position.clear(); this->e_retracted.clear(); this->e_restart_extra.clear(); } -void GCode::PlaceholderParserIntegration::init(const GCodeWriter &writer) +void GCode::PlaceholderParserIntegration::init(const GCodeWriter& writer) { this->reset(); - const std::vector &extruders = writer.extruders(); - if (! extruders.empty()) { + const std::vector& extruders = writer.extruders(); + if (!extruders.empty()) { this->num_extruders = extruders.back().id() + 1; this->e_retracted.assign(MAXIMUM_EXTRUDER_NUMBER, 0); this->e_restart_extra.assign(MAXIMUM_EXTRUDER_NUMBER, 0); - this->opt_e_retracted = new ConfigOptionFloats(e_retracted); + this->opt_e_retracted = new ConfigOptionFloats(e_retracted); this->opt_e_restart_extra = new ConfigOptionFloats(e_restart_extra); this->output_config.set_key_value("e_retracted", this->opt_e_retracted); this->output_config.set_key_value("e_restart_extra", this->opt_e_restart_extra); - if (! writer.config.use_relative_e_distances) { + if (!writer.config.use_relative_e_distances) { e_position.assign(MAXIMUM_EXTRUDER_NUMBER, 0); opt_e_position = new ConfigOptionFloats(e_position); this->output_config.set_key_value("e_position", opt_e_position); } } - this->opt_extruded_volume = new ConfigOptionFloats(this->num_extruders, 0.f); - this->opt_extruded_weight = new ConfigOptionFloats(this->num_extruders, 0.f); + this->opt_extruded_volume = new ConfigOptionFloats(this->num_extruders, 0.f); + this->opt_extruded_weight = new ConfigOptionFloats(this->num_extruders, 0.f); this->opt_extruded_volume_total = new ConfigOptionFloat(0.f); this->opt_extruded_weight_total = new ConfigOptionFloat(0.f); this->parser.set("extruded_volume", this->opt_extruded_volume); @@ -1100,26 +1087,26 @@ void GCode::PlaceholderParserIntegration::init(const GCodeWriter &writer) this->parser.set("zhop", this->opt_zhop); } -void GCode::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer) +void GCode::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter& writer) { memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3); this->opt_position->values = this->position; - this->opt_zhop->value = writer.get_zhop(); + this->opt_zhop->value = writer.get_zhop(); if (this->num_extruders > 0) { - const std::vector &extruders = writer.extruders(); - assert(! extruders.empty() && num_extruders == extruders.back().id() + 1); + const std::vector& extruders = writer.extruders(); + assert(!extruders.empty() && num_extruders == extruders.back().id() + 1); this->e_retracted.assign(MAXIMUM_EXTRUDER_NUMBER, 0); this->e_restart_extra.assign(MAXIMUM_EXTRUDER_NUMBER, 0); this->opt_extruded_volume->values.assign(num_extruders, 0); this->opt_extruded_weight->values.assign(num_extruders, 0); double total_volume = 0.; double total_weight = 0.; - for (const Extruder &e : extruders) { - this->e_retracted[e.id()] = e.retracted(); - this->e_restart_extra[e.id()] = e.restart_extra(); - double v = e.extruded_volume(); - double w = v * e.filament_density() * 0.001; + for (const Extruder& e : extruders) { + this->e_retracted[e.id()] = e.retracted(); + this->e_restart_extra[e.id()] = e.restart_extra(); + double v = e.extruded_volume(); + double w = v * e.filament_density() * 0.001; this->opt_extruded_volume->values[e.id()] = v; this->opt_extruded_weight->values[e.id()] = w; total_volume += v; @@ -1127,11 +1114,11 @@ void GCode::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWri } opt_extruded_volume_total->value = total_volume; opt_extruded_weight_total->value = total_weight; - opt_e_retracted->values = this->e_retracted; - opt_e_restart_extra->values = this->e_restart_extra; - if (! writer.config.use_relative_e_distances) { + opt_e_retracted->values = this->e_retracted; + opt_e_restart_extra->values = this->e_restart_extra; + if (!writer.config.use_relative_e_distances) { this->e_position.assign(MAXIMUM_EXTRUDER_NUMBER, 0); - for (const Extruder &e : extruders) + for (const Extruder& e : extruders) this->e_position[e.id()] = e.position(); this->opt_e_position->values = this->e_position; } @@ -1165,10 +1152,10 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // This is the same logic as in support generator. //FIXME should we use the printing extruders instead? double gap_over_supports = object.config().support_top_z_distance; - // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. - assert(!object.has_support() || gap_over_supports != 0. || object.config().support_material_synchronize_layers); - if (gap_over_supports != 0.) { - gap_over_supports = std::max(0., gap_over_supports); + // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object + layers iff soluble supports. assert(!object.has_support() || gap_over_supports != 0. || + object.config().support_material_synchronize_layers); if (gap_over_supports != 0.) { gap_over_supports = std::max(0., + gap_over_supports); // Not a soluble support, double support_layer_height_min = 1000000.; for (auto lh : object.print()->config().min_layer_height.values) @@ -1179,20 +1166,20 @@ std::vector GCode::collect_layers_to_print(const PrintObjec std::vector> warning_ranges; // Pair the object layers with the support layers by z. - size_t idx_object_layer = 0; - size_t idx_support_layer = 0; + size_t idx_object_layer = 0; + size_t idx_support_layer = 0; const LayerToPrint* last_extrusion_layer = nullptr; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { LayerToPrint layer_to_print; - double print_z_min = std::numeric_limits::max(); + double print_z_min = std::numeric_limits::max(); if (idx_object_layer < object.layers().size()) { layer_to_print.object_layer = object.layers()[idx_object_layer++]; - print_z_min = std::min(print_z_min, layer_to_print.object_layer->print_z); + print_z_min = std::min(print_z_min, layer_to_print.object_layer->print_z); } if (idx_support_layer < object.support_layers().size()) { layer_to_print.support_layer = object.support_layers()[idx_support_layer++]; - print_z_min = std::min(print_z_min, layer_to_print.support_layer->print_z); + print_z_min = std::min(print_z_min, layer_to_print.support_layer->print_z); } if (layer_to_print.object_layer && layer_to_print.object_layer->print_z > print_z_min + EPSILON) { @@ -1208,24 +1195,27 @@ std::vector GCode::collect_layers_to_print(const PrintObjec layer_to_print.original_object = &object; layers_to_print.push_back(layer_to_print); - bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); + bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) || + (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); // Check that there are extrusions on the very first layer. The case with empty // first layer may result in skirt/brim in the air and maybe other issues. if (layers_to_print.size() == 1u) { if (!has_extrusions) - throw Slic3r::SlicingError(_(L("One object has empty initial layer and can't be printed. Please Cut the bottom or enable supports.")), object.id().id); + throw Slic3r::SlicingError( + _(L("One object has empty initial layer and can't be printed. Please Cut the bottom or enable supports.")), + object.id().id); } // In case there are extrusions on this layer, check there is a layer to lay it on. if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { - double top_cd = object.config().support_top_z_distance; + double top_cd = object.config().support_top_z_distance; double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance; - //if (!object.print()->config().independent_support_layer_height) - { // the actual support gap may be larger than the configured one due to rounding to layer height for organic support, regardless of independent support layer height + // if (!object.print()->config().independent_support_layer_height) + { // the actual support gap may be larger than the configured one due to rounding to layer height for organic support, + // regardless of independent support layer height top_cd = std::ceil(top_cd / object.config().layer_height) * object.config().layer_height; bottom_cd = std::ceil(bottom_cd / object.config().layer_height) * object.config().layer_height; } @@ -1234,36 +1224,37 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // raft contact distance should not trigger any warning if (last_extrusion_layer && last_extrusion_layer->support_layer) { double raft_gap = object.config().raft_contact_distance.value; - //if (!object.print()->config().independent_support_layer_height) + // if (!object.print()->config().independent_support_layer_height) { raft_gap = std::ceil(raft_gap / object.config().layer_height) * object.config().layer_height; } extra_gap = std::max(extra_gap, object.config().raft_contact_distance.value); } - double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) - + layer_to_print.layer()->height - + std::max(0., extra_gap); + double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_to_print.layer()->height + + std::max(0., extra_gap); // Negative support_contact_z is not taken into account, it can result in false positives in cases if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) - warning_ranges.emplace_back(std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z())); + warning_ranges.emplace_back( + std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z())); } // Remember last layer with extrusions. if (has_extrusions) last_extrusion_layer = &layers_to_print.back(); } - if (! warning_ranges.empty()) { + if (!warning_ranges.empty()) { std::string warning; - size_t i = 0; + size_t i = 0; for (i = 0; i < std::min(warning_ranges.size(), size_t(5)); ++i) - warning += Slic3r::format(_(L("Object can't be printed for empty layer between %1% and %2%.")), - warning_ranges[i].first, warning_ranges[i].second) + "\n"; - warning += Slic3r::format(_(L("Object: %1%")), object.model_object()->name) + "\n" - + _(L("Maybe parts of the object at these height are too thin, or the object has faulty mesh")); + warning += Slic3r::format(_(L("Object can't be printed for empty layer between %1% and %2%.")), warning_ranges[i].first, + warning_ranges[i].second) + + "\n"; + warning += Slic3r::format(_(L("Object: %1%")), object.model_object()->name) + "\n" + + _(L("Maybe parts of the object at these height are too thin, or the object has faulty mesh")); - const_cast(object.print())->active_step_add_warning( - PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingEmptyGcodeLayers); + const_cast(object.print()) + ->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingEmptyGcodeLayers); } return layers_to_print; @@ -1274,21 +1265,22 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Return a list of items. std::vector>> GCode::collect_layers_to_print(const Print& print) { - struct OrderingItem { - coordf_t print_z; - size_t object_idx; - size_t layer_idx; + struct OrderingItem + { + coordf_t print_z; + size_t object_idx; + size_t layer_idx; }; - std::vector> per_object(print.objects().size(), std::vector()); - std::vector ordering; + std::vector> per_object(print.objects().size(), std::vector()); + std::vector ordering; std::vector errors; for (size_t i = 0; i < print.objects().size(); ++i) { try { per_object[i] = collect_layers_to_print(*print.objects()[i]); - } catch (const Slic3r::SlicingError &e) { + } catch (const Slic3r::SlicingError& e) { errors.push_back(e); continue; } @@ -1297,13 +1289,15 @@ std::vector>> GCode::collec ordering.reserve(ordering.size() + per_object[i].size()); const LayerToPrint& front = per_object[i].front(); for (const LayerToPrint& ltp : per_object[i]) { - ordering_item.print_z = ltp.print_z(); + ordering_item.print_z = ltp.print_z(); ordering_item.layer_idx = <p - &front; ordering.emplace_back(ordering_item); } } - if (!errors.empty()) { throw Slic3r::SlicingErrors(errors); } + if (!errors.empty()) { + throw Slic3r::SlicingErrors(errors); + } std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); @@ -1312,9 +1306,10 @@ std::vector>> GCode::collec // Merge numerically very close Z values. for (size_t i = 0; i < ordering.size();) { // Find the last layer with roughly the same print_z. - size_t j = i + 1; + size_t j = i + 1; coordf_t zmax = ordering[i].print_z + EPSILON; - for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j); + for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j) + ; // Merge into layers_to_print. std::pair> merged; // Assign an average print_z to the set of layers with nearly equal print_z. @@ -1336,112 +1331,129 @@ namespace DoExport { // static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) // { // const GCodeProcessorResult& result = processor.get_result(); -// print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); +// print_statistics.estimated_normal_print_time = +// get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); // print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? // get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; // } - static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics, const PrintConfig& config) - { - const GCodeProcessorResult& result = processor.get_result(); - double normal_print_time = result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time; - print_statistics.estimated_normal_print_time = get_time_dhms(normal_print_time); - print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; +static void update_print_estimated_stats(const GCodeProcessor& processor, + const std::vector& extruders, + PrintStatistics& print_statistics, + const PrintConfig& config) +{ + const GCodeProcessorResult& result = processor.get_result(); + double normal_print_time = result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time; + print_statistics.estimated_normal_print_time = get_time_dhms(normal_print_time); + print_statistics.estimated_silent_print_time = + processor.is_stealth_time_estimator_enabled() ? + get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : + "N/A"; - // update filament statictics - double total_extruded_volume = 0.0; - double total_used_filament = 0.0; - double total_weight = 0.0; - double total_cost = 0.0; + // update filament statictics + double total_extruded_volume = 0.0; + double total_used_filament = 0.0; + double total_weight = 0.0; + double total_cost = 0.0; - for (auto volume : result.print_statistics.total_volumes_per_extruder) { - total_extruded_volume += volume.second; + for (auto volume : result.print_statistics.total_volumes_per_extruder) { + total_extruded_volume += volume.second; - size_t extruder_id = volume.first; - auto extruder = std::find_if(extruders.begin(), extruders.end(), [extruder_id](const Extruder& extr) {return extr.id() == extruder_id; }); - if (extruder == extruders.end()) - continue; + size_t extruder_id = volume.first; + auto extruder = std::find_if(extruders.begin(), extruders.end(), + [extruder_id](const Extruder& extr) { return extr.id() == extruder_id; }); + if (extruder == extruders.end()) + continue; - double s = PI * sqr(0.5* extruder->filament_diameter()); - double weight = volume.second * extruder->filament_density() * 0.001; - total_used_filament += volume.second/s; - total_weight += weight; - total_cost += weight * extruder->filament_cost() * 0.001; - } - - total_cost += config.time_cost.getFloat() * (normal_print_time/3600.0); - - print_statistics.total_extruded_volume = total_extruded_volume; - print_statistics.total_used_filament = total_used_filament; - print_statistics.total_weight = total_weight; - print_statistics.total_cost = total_cost; - - print_statistics.filament_stats = result.print_statistics.model_volumes_per_extruder; + double s = PI * sqr(0.5 * extruder->filament_diameter()); + double weight = volume.second * extruder->filament_density() * 0.001; + total_used_filament += volume.second / s; + total_weight += weight; + total_cost += weight * extruder->filament_cost() * 0.001; } - // if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found - // into pairs containing: - // first: source - // second: keyword - // to be shown in the warning notification - // The returned vector is empty if no keyword has been found - static std::vector> validate_custom_gcode(const Print& print) { - static const unsigned int MAX_TAGS_COUNT = 5; - std::vector> ret; + total_cost += config.time_cost.getFloat() * (normal_print_time / 3600.0); - auto check = [&ret](const std::string& source, const std::string& gcode) { - std::vector tags; - if (GCodeProcessor::contains_reserved_tags(gcode, MAX_TAGS_COUNT, tags)) { - if (!tags.empty()) { - size_t i = 0; - while (ret.size() < MAX_TAGS_COUNT && i < tags.size()) { - ret.push_back({ source, tags[i] }); - ++i; - } + print_statistics.total_extruded_volume = total_extruded_volume; + print_statistics.total_used_filament = total_used_filament; + print_statistics.total_weight = total_weight; + print_statistics.total_cost = total_cost; + + print_statistics.filament_stats = result.print_statistics.model_volumes_per_extruder; +} + +// if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found +// into pairs containing: +// first: source +// second: keyword +// to be shown in the warning notification +// The returned vector is empty if no keyword has been found +static std::vector> validate_custom_gcode(const Print& print) +{ + static const unsigned int MAX_TAGS_COUNT = 5; + std::vector> ret; + + auto check = [&ret](const std::string& source, const std::string& gcode) { + std::vector tags; + if (GCodeProcessor::contains_reserved_tags(gcode, MAX_TAGS_COUNT, tags)) { + if (!tags.empty()) { + size_t i = 0; + while (ret.size() < MAX_TAGS_COUNT && i < tags.size()) { + ret.push_back({source, tags[i]}); + ++i; } } - }; - - const GCodeConfig& config = print.config(); - check(_(L("Machine start G-code")), config.machine_start_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Machine end G-code")), config.machine_end_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Before layer change G-code")), config.before_layer_change_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Layer change G-code")), config.layer_change_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Timelapse G-code")), config.time_lapse_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Change filament G-code")), config.change_filament_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Printing by object G-code")), config.printing_by_object_gcode.value); - //if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value); - //Orca - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Change extrusion role G-code")), config.change_extrusion_role_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Pause G-code")), config.machine_pause_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) check(_(L("Template Custom G-code")), config.template_custom_gcode.value); - if (ret.size() < MAX_TAGS_COUNT) { - for (const std::string& value : config.filament_start_gcode.values) { - check(_(L("Filament start G-code")), value); - if (ret.size() == MAX_TAGS_COUNT) - break; - } } - if (ret.size() < MAX_TAGS_COUNT) { - for (const std::string& value : config.filament_end_gcode.values) { - check(_(L("Filament end G-code")), value); - if (ret.size() == MAX_TAGS_COUNT) - break; - } - } - //BBS: no custom_gcode_per_print_z, don't need to check - //if (ret.size() < MAX_TAGS_COUNT) { - // const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; - // for (const auto& gcode : custom_gcode_per_print_z.gcodes) { - // check(_(L("Custom G-code")), gcode.extra); - // if (ret.size() == MAX_TAGS_COUNT) - // break; - // } - //} + }; - return ret; + const GCodeConfig& config = print.config(); + check(_(L("Machine start G-code")), config.machine_start_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Machine end G-code")), config.machine_end_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Before layer change G-code")), config.before_layer_change_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Layer change G-code")), config.layer_change_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Timelapse G-code")), config.time_lapse_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Change filament G-code")), config.change_filament_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Printing by object G-code")), config.printing_by_object_gcode.value); + // if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value); + // Orca + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Change extrusion role G-code")), config.change_extrusion_role_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Pause G-code")), config.machine_pause_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) + check(_(L("Template Custom G-code")), config.template_custom_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) { + for (const std::string& value : config.filament_start_gcode.values) { + check(_(L("Filament start G-code")), value); + if (ret.size() == MAX_TAGS_COUNT) + break; + } } + if (ret.size() < MAX_TAGS_COUNT) { + for (const std::string& value : config.filament_end_gcode.values) { + check(_(L("Filament end G-code")), value); + if (ret.size() == MAX_TAGS_COUNT) + break; + } + } + // BBS: no custom_gcode_per_print_z, don't need to check + // if (ret.size() < MAX_TAGS_COUNT) { + // const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; + // for (const auto& gcode : custom_gcode_per_print_z.gcodes) { + // check(_(L("Custom G-code")), gcode.extra); + // if (ret.size() == MAX_TAGS_COUNT) + // break; + // } + // } + + return ret; +} } // namespace DoExport bool GCode::is_BBL_Printer() @@ -1465,7 +1477,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu if (print->is_step_done(psGCodeExport) && boost::filesystem::exists(boost::filesystem::path(path))) return; - BOOST_LOG_TRIVIAL(info) << boost::format("Will export G-code to %1% soon")%path; + BOOST_LOG_TRIVIAL(info) << boost::format("Will export G-code to %1% soon") % path; GCodeProcessor::s_IsBBLPrinter = print->is_BBL_printer(); print->set_started(psGCodeExport); @@ -1477,11 +1489,12 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu for (const auto& [source, keyword] : validation_res) { reports += source + ": \"" + keyword + "\"\n"; } - //print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, - // _(L("In the custom G-code were found reserved keywords:")) + "\n" + - // reports + - // _(L("This may cause problems in g-code visualization and printing time estimation."))); - std::string temp = "Dangerous keywords in custom Gcode: " + reports + "\nThis may cause problems in g-code visualization and printing time estimation."; + // print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + // _(L("In the custom G-code were found reserved keywords:")) + "\n" + + // reports + + // _(L("This may cause problems in g-code visualization and printing time estimation."))); + std::string temp = "Dangerous keywords in custom Gcode: " + reports + + "\nThis may cause problems in g-code visualization and printing time estimation."; BOOST_LOG_TRIVIAL(warning) << temp; } @@ -1494,7 +1507,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu fs::path folder = file_path.parent_path(); if (!fs::exists(folder)) { fs::create_directory(folder); - BOOST_LOG_TRIVIAL(error) << "[WARNING]: the parent path " + folder.string() +" is not there, create it!" << std::endl; + BOOST_LOG_TRIVIAL(error) << "[WARNING]: the parent path " + folder.string() + " is not there, create it!" << std::endl; } std::string path_tmp(path); @@ -1503,11 +1516,11 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu m_processor.initialize(path_tmp); m_processor.set_print(print); GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); - if (! file.is_open()) { + if (!file.is_open()) { BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n" << std::endl; if (!fs::exists(folder)) { - //fs::create_directory(folder); - BOOST_LOG_TRIVIAL(error) << "the parent path " + folder.string() +" is not there!!!" << std::endl; + // fs::create_directory(folder); + BOOST_LOG_TRIVIAL(error) << "the parent path " + folder.string() + " is not there!!!" << std::endl; } throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); } @@ -1520,7 +1533,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu boost::nowide::remove(path_tmp.c_str()); throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } - } catch (std::exception & /* ex */) { + } catch (std::exception& /* ex */) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. // Close and remove the file. file.close(); @@ -1532,7 +1545,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu check_placeholder_parser_failed(); #if ORCA_CHECK_GCODE_PLACEHOLDERS - if (!m_placeholder_error_messages.empty()){ + if (!m_placeholder_error_messages.empty()) { std::ostringstream message; message << "Some EditGcodeDialog defs were not specified properly. Do so in PrintConfig under SlicingStatesConfigDef:" << std::endl; for (const auto& error : m_placeholder_error_messages) { @@ -1556,24 +1569,22 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu if (m_config.printer_structure.value == PrinterStructure::psI3 && print->config().print_sequence == PrintSequence::ByObject) { m_timelapse_warning_code += (1 << 1); } - m_processor.result().timelapse_warning_code = m_timelapse_warning_code; + m_processor.result().timelapse_warning_code = m_timelapse_warning_code; m_processor.result().support_traditional_timelapse = m_support_traditional_timelapse; bool activate_long_retraction_when_cut = false; for (const auto& extruder : m_writer.extruders()) - activate_long_retraction_when_cut |= ( - m_config.long_retractions_when_cut.get_at(extruder.id()) - && m_config.retraction_distances_when_cut.get_at(extruder.id()) > 0 - ); + activate_long_retraction_when_cut |= (m_config.long_retractions_when_cut.get_at(extruder.id()) && + m_config.retraction_distances_when_cut.get_at(extruder.id()) > 0); m_processor.result().long_retraction_when_cut = activate_long_retraction_when_cut; - - { //BBS:check bed and filament compatible - const ConfigOptionDef *bed_type_def = print_config_def.get("curr_bed_type"); + + { // BBS:check bed and filament compatible + const ConfigOptionDef* bed_type_def = print_config_def.get("curr_bed_type"); assert(bed_type_def != nullptr); - const t_config_enum_values *bed_type_keys_map = bed_type_def->enum_keys_map; - const ConfigOptionInts *bed_temp_opt = m_config.option(get_bed_temp_key(m_config.curr_bed_type)); - for(auto extruder_id : m_initial_layer_extruders){ + const t_config_enum_values* bed_type_keys_map = bed_type_def->enum_keys_map; + const ConfigOptionInts* bed_temp_opt = m_config.option(get_bed_temp_key(m_config.curr_bed_type)); + for (auto extruder_id : m_initial_layer_extruders) { int cur_bed_temp = bed_temp_opt->get_at(extruder_id); if (cur_bed_temp == 0 && bed_type_keys_map != nullptr) { for (auto item : *bed_type_keys_map) { @@ -1589,33 +1600,29 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu } m_processor.finalize(true); -// DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); + // DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics, print->config()); if (result != nullptr) { - *result = std::move(m_processor.extract_result()); - // set the filename to the correct value result->filename = path; } - //BBS: add some log for error output + // BBS: add some log for error output BOOST_LOG_TRIVIAL(debug) << boost::format("Finished processing gcode to %1% ") % path_tmp; std::error_code ret = rename_file(path_tmp, path); if (ret) { - throw Slic3r::RuntimeError( - std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "error code " + ret.message() + '\n' + - "Is " + path_tmp + " locked?" + '\n'); - } - else { - BOOST_LOG_TRIVIAL(info) << boost::format("rename_file from %1% to %2% successfully")% path_tmp % path; + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + + "error code " + ret.message() + '\n' + "Is " + path_tmp + " locked?" + '\n'); + } else { + BOOST_LOG_TRIVIAL(info) << boost::format("rename_file from %1% to %2% successfully") % path_tmp % path; } BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); print->set_done(psGCodeExport); - - if(is_BBL_Printer()) + + if (is_BBL_Printer()) result->label_object_enabled = m_enable_exclude_object; // Write the profiler measurements to file @@ -1625,14 +1632,14 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu // free functions called by GCode::_do_export() namespace DoExport { - static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) - { - silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) - && config.silent_mode; - processor.reset(); - processor.apply_config(config); - processor.enable_stealth_time_estimator(silent_time_estimator_enabled); - } +static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) +{ + silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) && + config.silent_mode; + processor.reset(); + processor.apply_config(config); + processor.enable_stealth_time_estimator(silent_time_estimator_enabled); +} #if 0 static double autospeed_volumetric_limit(const Print &print) @@ -1694,73 +1701,76 @@ namespace DoExport { } #endif - static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) - { - ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material; - } - - // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. - static std::string update_print_stats_and_format_filament_stats( - const bool has_wipe_tower, - const WipeTowerData &wipe_tower_data, - const std::vector &extruders, - PrintStatistics &print_statistics) - { - std::string filament_stats_string_out; - - print_statistics.clear(); - print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); - if (! extruders.empty()) { - std::pair out_filament_used_mm ("; filament used [mm] = ", 0); - std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); - std::pair out_filament_used_g ("; filament used [g] = ", 0); - std::pair out_filament_cost ("; filament cost = ", 0); - for (const Extruder &extruder : extruders) { - double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); - double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter - double filament_weight = extruded_volume * extruder.filament_density() * 0.001; - double filament_cost = filament_weight * extruder.filament_cost() * 0.001; - auto append = [&extruder](std::pair &dst, const char *tmpl, double value) { - assert(is_decimal_separator_point()); - while (dst.second < extruder.id()) { - // Fill in the non-printing extruders with zeros. - dst.first += (dst.second > 0) ? ", 0" : "0"; - ++ dst.second; - } - if (dst.second > 0) - dst.first += ", "; - char buf[64]; - sprintf(buf, tmpl, value); - dst.first += buf; - ++ dst.second; - }; - append(out_filament_used_mm, "%.2lf", used_filament); - append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); - if (filament_weight > 0.) { - print_statistics.total_weight = print_statistics.total_weight + filament_weight; - append(out_filament_used_g, "%.2lf", filament_weight); - if (filament_cost > 0.) { - print_statistics.total_cost = print_statistics.total_cost + filament_cost; - append(out_filament_cost, "%.2lf", filament_cost); - } - } - print_statistics.total_used_filament += used_filament; - print_statistics.total_extruded_volume += extruded_volume; - print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; - print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; - } - filament_stats_string_out += out_filament_used_mm.first; - filament_stats_string_out += "\n" + out_filament_used_cm3.first; - if (out_filament_used_g.second) - filament_stats_string_out += "\n" + out_filament_used_g.first; - if (out_filament_cost.second) - filament_stats_string_out += "\n" + out_filament_cost.first; - filament_stats_string_out += "\n"; - } - return filament_stats_string_out; - } +static void init_ooze_prevention(const Print& print, OozePrevention& ooze_prevention) +{ + ooze_prevention.enable = print.config().ooze_prevention.value && !print.config().single_extruder_multi_material; } +// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. +static std::string update_print_stats_and_format_filament_stats(const bool has_wipe_tower, + const WipeTowerData& wipe_tower_data, + const std::vector& extruders, + PrintStatistics& print_statistics) +{ + std::string filament_stats_string_out; + + print_statistics.clear(); + print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); + if (!extruders.empty()) { + std::pair out_filament_used_mm("; filament used [mm] = ", 0); + std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); + std::pair out_filament_used_g("; filament used [g] = ", 0); + std::pair out_filament_cost("; filament cost = ", 0); + for (const Extruder& extruder : extruders) { + double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); + double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : + 0.f); // assumes 1.75mm filament diameter + double filament_weight = extruded_volume * extruder.filament_density() * 0.001; + double filament_cost = filament_weight * extruder.filament_cost() * 0.001; + auto append = [&extruder](std::pair& dst, const char* tmpl, double value) { + assert(is_decimal_separator_point()); + while (dst.second < extruder.id()) { + // Fill in the non-printing extruders with zeros. + dst.first += (dst.second > 0) ? ", 0" : "0"; + ++dst.second; + } + if (dst.second > 0) + dst.first += ", "; + char buf[64]; + sprintf(buf, tmpl, value); + dst.first += buf; + ++dst.second; + }; + append(out_filament_used_mm, "%.2lf", used_filament); + append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); + if (filament_weight > 0.) { + print_statistics.total_weight = print_statistics.total_weight + filament_weight; + append(out_filament_used_g, "%.2lf", filament_weight); + if (filament_cost > 0.) { + print_statistics.total_cost = print_statistics.total_cost + filament_cost; + append(out_filament_cost, "%.2lf", filament_cost); + } + } + print_statistics.total_used_filament += used_filament; + print_statistics.total_extruded_volume += extruded_volume; + print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; + print_statistics.total_wipe_tower_cost += has_wipe_tower ? + (extruded_volume - extruder.extruded_volume()) * extruder.filament_density() * + 0.001 * extruder.filament_cost() * 0.001 : + 0.; + } + filament_stats_string_out += out_filament_used_mm.first; + filament_stats_string_out += "\n" + out_filament_used_cm3.first; + if (out_filament_used_g.second) + filament_stats_string_out += "\n" + out_filament_used_g.first; + if (out_filament_cost.second) + filament_stats_string_out += "\n" + out_filament_cost.first; + filament_stats_string_out += "\n"; + } + return filament_stats_string_out; +} +} // namespace DoExport + #if 0 // Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints. static inline std::vector sort_object_instances_by_max_z(const Print &print) @@ -1777,12 +1787,11 @@ static inline std::vector sort_object_instances_by_max_z(c #endif // Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model(). -//BBS: add sort logic for seq-print +// BBS: add sort logic for seq-print std::vector sort_object_instances_by_model_order(const Print& print, bool init_order) { auto find_object_index = [](const Model& model, const ModelObject* obj) { - for (int index = 0; index < model.objects.size(); index++) - { + for (int index = 0; index < model.objects.size(); index++) { if (model.objects[index] == obj) return index; } @@ -1792,14 +1801,15 @@ std::vector sort_object_instances_by_model_order(const Pri // Build up map from ModelInstance* to PrintInstance* std::vector> model_instance_to_print_instance; model_instance_to_print_instance.reserve(print.num_object_instances()); - for (const PrintObject *print_object : print.objects()) - for (const PrintInstance &print_instance : print_object->instances()) - { + for (const PrintObject* print_object : print.objects()) + for (const PrintInstance& print_instance : print_object->instances()) { if (init_order) - const_cast(print_instance.model_instance)->arrange_order = find_object_index(print.model(), print_object->model_object()); + const_cast(print_instance.model_instance)->arrange_order = find_object_index(print.model(), + print_object->model_object()); model_instance_to_print_instance.emplace_back(print_instance.model_instance, &print_instance); } - std::sort(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), [](auto &l, auto &r) { return l.first->arrange_order < r.first->arrange_order; }); + std::sort(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), + [](auto& l, auto& r) { return l.first->arrange_order < r.first->arrange_order; }); if (init_order) { // Re-assign the arrange_order so each instance has a unique order number for (int k = 0; k < model_instance_to_print_instance.size(); k++) { @@ -1809,23 +1819,26 @@ std::vector sort_object_instances_by_model_order(const Pri std::vector instances; instances.reserve(model_instance_to_print_instance.size()); - for (const ModelObject *model_object : print.model().objects) - for (const ModelInstance *model_instance : model_object->instances) { - auto it = std::lower_bound(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), std::make_pair(model_instance, nullptr), [](auto &l, auto &r) { return l.first->arrange_order < r.first->arrange_order; }); + for (const ModelObject* model_object : print.model().objects) + for (const ModelInstance* model_instance : model_object->instances) { + auto it = std::lower_bound(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), + std::make_pair(model_instance, nullptr), + [](auto& l, auto& r) { return l.first->arrange_order < r.first->arrange_order; }); if (it != model_instance_to_print_instance.end() && it->first == model_instance) instances.emplace_back(it->second); } - std::sort(instances.begin(), instances.end(), [](auto& l, auto& r) { return l->model_instance->arrange_order < r->model_instance->arrange_order; }); + std::sort(instances.begin(), instances.end(), + [](auto& l, auto& r) { return l->model_instance->arrange_order < r->model_instance->arrange_order; }); return instances; } enum BambuBedType { - bbtUnknown = 0, - bbtCoolPlate = 1, - bbtEngineeringPlate = 2, + bbtUnknown = 0, + bbtCoolPlate = 1, + bbtEngineeringPlate = 2, bbtHighTemperaturePlate = 3, - bbtTexturedPEIPlate = 4, - bbtSuperTackPlate = 5, + bbtTexturedPEIPlate = 4, + bbtSuperTackPlate = 5, }; static BambuBedType to_bambu_bed_type(BedType type) @@ -1847,23 +1860,11 @@ static BambuBedType to_bambu_bed_type(BedType type) return bambu_bed_type; } -void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) +void GCode::_do_export(Print& print, GCodeOutputStream& file, ThumbnailsGeneratorCallback thumbnail_cb) { PROFILE_FUNC(); - const auto& print_mapping = print.get_filament_extruder_map(); - if (!print_mapping.empty()) { - m_writer.set_filament_extruder_map(print_mapping); - for (const auto& pair : print_mapping) { - } - } else { - } - - const auto& writer_mapping = m_writer.get_filament_extruder_map(); - m_processor.set_filament_extruder_map(writer_mapping); - // modifies m_silent_time_estimator_enabled - // 现在 init_gcode_processor 调用 apply_config 时,映射表已经设置好了 DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled); const bool is_bbl_printers = print.is_BBL_printer(); m_calib_config.clear(); @@ -1871,14 +1872,14 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_last_height = 0.f; m_last_layer_z = 0.f; m_max_layer_z = 0.f; - m_last_width = 0.f; + m_last_width = 0.f; m_is_role_based_fan_on.fill(false); #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_last_mm3_per_mm = 0.; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING m_fan_mover.release(); - + m_writer.set_is_bbl_machine(is_bbl_printers); // How many times will be change_layer() called? @@ -1894,14 +1895,14 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato for (auto layer : object->support_layers()) zs.push_back(layer->print_z); std::sort(zs.begin(), zs.end()); - //BBS: merge numerically very close Z values. - auto end_it = std::unique(zs.begin(), zs.end()); - unsigned int temp_layer_count = (unsigned int)(end_it - zs.begin()); + // BBS: merge numerically very close Z values. + auto end_it = std::unique(zs.begin(), zs.end()); + unsigned int temp_layer_count = (unsigned int) (end_it - zs.begin()); for (auto it = zs.begin(); it != end_it - 1; it++) { if (abs(*it - *(it + 1)) < EPSILON) temp_layer_count--; } - m_layer_count += (unsigned int)(object->instances().size() * temp_layer_count); + m_layer_count += (unsigned int) (object->instances().size() * temp_layer_count); } } else { // Print all objects with the same print_z together. @@ -1913,12 +1914,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato for (auto layer : object->support_layers()) zs.push_back(layer->print_z); } - if (!zs.empty()) - { + if (!zs.empty()) { std::sort(zs.begin(), zs.end()); - //BBS: merge numerically very close Z values. - auto end_it = std::unique(zs.begin(), zs.end()); - m_layer_count = (unsigned int)(end_it - zs.begin()); + // BBS: merge numerically very close Z values. + auto end_it = std::unique(zs.begin(), zs.end()); + m_layer_count = (unsigned int) (end_it - zs.begin()); for (auto it = zs.begin(); it != end_it - 1; it++) { if (abs(*it - *(it + 1)) < EPSILON) m_layer_count--; @@ -1930,48 +1930,48 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_enable_cooling_markers = true; this->apply_print_config(print.config()); - //m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); + // m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); print.throw_if_canceled(); if (print.config().spiral_mode.value) m_spiral_vase = make_unique(print.config()); - if (print.config().max_volumetric_extrusion_rate_slope.value > 0){ - m_pressure_equalizer = make_unique(print.config()); - m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; + if (print.config().max_volumetric_extrusion_rate_slope.value > 0) { + m_pressure_equalizer = make_unique(print.config()); + m_enable_extrusion_role_markers = (bool) m_pressure_equalizer; } else - m_enable_extrusion_role_markers = false; + m_enable_extrusion_role_markers = false; if (!print.config().small_area_infill_flow_compensation_model.empty()) m_small_area_infill_flow_compensator = make_unique(print.config()); - + // Orca: Don't output Header block if BTT thumbnail is identified in the list // Get the thumbnails value as a string std::string thumbnails_value = print.config().option("thumbnails")->value; // search string for the BTT_TFT label bool has_BTT_thumbnail = (thumbnails_value.find("BTT_TFT") != std::string::npos); - - if(!has_BTT_thumbnail){ + + if (!has_BTT_thumbnail) { file.write_format("; HEADER_BLOCK_START\n"); // Write information on the generator. file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str()); if (is_bbl_printers) file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); - //BBS: total layer number + // BBS: total layer number file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str()); - //Orca: extra check for bbl printer + // Orca: extra check for bbl printer if (is_bbl_printers) { if (print.calib_params().mode == CalibMode::Calib_None) { // Don't support skipping in cali mode // list all label_object_id with sorted order here m_enable_exclude_object = true; m_label_objects_ids.clear(); m_label_objects_ids.reserve(print.num_object_instances()); - for (const PrintObject *print_object : print.objects()) - for (const PrintInstance &print_instance : print_object->instances()) + for (const PrintObject* print_object : print.objects()) + for (const PrintInstance& print_instance : print_object->instances()) m_label_objects_ids.push_back(print_instance.model_instance->get_labeled_id()); - + std::sort(m_label_objects_ids.begin(), m_label_objects_ids.end()); - + std::string objects_id_list = "; model label id: "; for (auto it = m_label_objects_ids.begin(); it != m_label_objects_ids.end(); it++) objects_id_list += (std::to_string(*it) + (it != m_label_objects_ids.end() - 1 ? "," : "\n")); @@ -1980,35 +1980,35 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_enable_exclude_object = false; m_label_objects_ids.clear(); } + } + + { + std::string filament_density_list = "; filament_density: "; + (filament_density_list += m_config.filament_density.serialize()) += '\n'; + file.writeln(filament_density_list); + + std::string filament_diameter_list = "; filament_diameter: "; + (filament_diameter_list += m_config.filament_diameter.serialize()) += '\n'; + file.writeln(filament_diameter_list); + + coordf_t max_height_z = -1; + for (const auto& object : print.objects()) + max_height_z = std::max(object->layers().back()->print_z, max_height_z); + + std::ostringstream max_height_z_tip; + max_height_z_tip << "; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n'; + file.writeln(max_height_z_tip.str()); + } + + file.write_format("; HEADER_BLOCK_END\n\n"); } - { - std::string filament_density_list = "; filament_density: "; - (filament_density_list+=m_config.filament_density.serialize()) +='\n'; - file.writeln(filament_density_list); - - std::string filament_diameter_list = "; filament_diameter: "; - (filament_diameter_list += m_config.filament_diameter.serialize()) += '\n'; - file.writeln(filament_diameter_list); - - coordf_t max_height_z = -1; - for (const auto& object : print.objects()) - max_height_z = std::max(object->layers().back()->print_z, max_height_z); - - std::ostringstream max_height_z_tip; - max_height_z_tip<<"; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n'; - file.writeln(max_height_z_tip.str()); - } - - file.write_format("; HEADER_BLOCK_END\n\n"); - } - - // BBS: write global config at the beginning of gcode file because printer - // need these config information - // Append full config, delimited by two 'phony' configuration keys - // CONFIG_BLOCK_START and CONFIG_BLOCK_END. The delimiters are structured - // as configuration key / value pairs to be parsable by older versions of - // PrusaSlicer G-code viewer. + // BBS: write global config at the beginning of gcode file because printer + // need these config information + // Append full config, delimited by two 'phony' configuration keys + // CONFIG_BLOCK_START and CONFIG_BLOCK_END. The delimiters are structured + // as configuration key / value pairs to be parsable by older versions of + // PrusaSlicer G-code viewer. { if (is_bbl_printers) { file.write("; CONFIG_BLOCK_START\n"); @@ -2019,11 +2019,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // SoftFever: write compatiple image int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type); - file.write_format("; first_layer_bed_temperature = %d\n", - first_layer_bed_temperature); - file.write_format( - "; first_layer_temperature = %d\n", - print.config().nozzle_temperature_initial_layer.get_at(0)); + file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature); + file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0)); file.write("; CONFIG_BLOCK_END\n\n"); } else if (thumbnail_cb != nullptr) { // generate the thumbnails @@ -2037,32 +2034,35 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (!thumbnails.empty()) GCodeThumbnails::export_thumbnails_to_file( - thumbnail_cb, print.get_plate_index(), thumbnails, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); }); + thumbnail_cb, print.get_plate_index(), thumbnails, [&file](const char* sz) { file.write(sz); }, + [&print]() { print.throw_if_canceled(); }); } } // Write some terse information on the slicing parameters. - const PrintObject *first_object = print.objects().front(); - const double layer_height = first_object->config().layer_height.value; - const double initial_layer_print_height = print.config().initial_layer_print_height.value; - for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) { - const PrintRegion ®ion = print.get_print_region(region_id); - file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width()); - file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); - file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); - file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); - file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); + const PrintObject* first_object = print.objects().front(); + const double layer_height = first_object->config().layer_height.value; + const double initial_layer_print_height = print.config().initial_layer_print_height.value; + for (size_t region_id = 0; region_id < print.num_print_regions(); ++region_id) { + const PrintRegion& region = print.get_print_region(region_id); + file.write_format("; external perimeters extrusion width = %.2fmm\n", + region.flow(*first_object, frExternalPerimeter, layer_height).width()); + file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); + file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); + file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); + file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); if (print.has_support_material()) file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); if (print.config().initial_layer_line_width.value > 0) - file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, initial_layer_print_height, true).width()); + file.write_format("; first layer extrusion width = %.2fmm\n", + region.flow(*first_object, frPerimeter, initial_layer_print_height, true).width()); file.write_format("\n"); } file.write_format("; EXECUTABLE_BLOCK_START\n"); // SoftFever - if( m_enable_exclude_object) + if (m_enable_exclude_object) file.write(set_object_info(&print)); // adds tags for time estimators @@ -2080,23 +2080,23 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Get optimal tool ordering to minimize tool switches of a multi-exruder print. // For a print by objects, find the 1st printing object. ToolOrdering tool_ordering; - unsigned int initial_extruder_id = (unsigned int)-1; - //BBS: first non-support filament extruder - unsigned int initial_non_support_extruder_id; - unsigned int final_extruder_id = (unsigned int)-1; - bool has_wipe_tower = false; - std::vector print_object_instances_ordering; - std::vector::const_iterator print_object_instance_sequential_active; + unsigned int initial_extruder_id = (unsigned int) -1; + // BBS: first non-support filament extruder + unsigned int initial_non_support_extruder_id; + unsigned int final_extruder_id = (unsigned int) -1; + bool has_wipe_tower = false; + std::vector print_object_instances_ordering; + std::vector::const_iterator print_object_instance_sequential_active; if (print.config().print_sequence == PrintSequence::ByObject) { // Order object instances for sequential print. print_object_instances_ordering = sort_object_instances_by_model_order(print); -// print_object_instances_ordering = sort_object_instances_by_max_z(print); + // print_object_instances_ordering = sort_object_instances_by_max_z(print); // Find the 1st printing object, find its tool ordering and the initial extruder ID. print_object_instance_sequential_active = print_object_instances_ordering.begin(); - for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) { + for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast(-1)) { - //BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament + // BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament initial_non_support_extruder_id = initial_extruder_id; if (tool_ordering.all_extruders().size() > 1 && print.config().filament_is_support.get_at(initial_extruder_id)) { bool has_non_support_filament = false; @@ -2106,7 +2106,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato break; } } - //BBS: find the non-support filament extruder of object + // BBS: find the non-support filament extruder of object if (has_non_support_filament) for (LayerTools layer_tools : tool_ordering.layer_tools()) { if (!layer_tools.has_object) @@ -2142,12 +2142,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); // Orca: support all extruder priming initial_extruder_id = (!is_bbl_printers && has_wipe_tower && !print.config().single_extruder_multi_material_priming) ? - // The priming towers will be skipped. - tool_ordering.all_extruders().back() : - // Don't skip the priming towers. - tool_ordering.first_extruder(); + // The priming towers will be skipped. + tool_ordering.all_extruders().back() : + // Don't skip the priming towers. + tool_ordering.first_extruder(); - //BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament + // BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament if (initial_extruder_id != static_cast(-1)) { initial_non_support_extruder_id = initial_extruder_id; if (tool_ordering.all_extruders().size() > 1 && print.config().filament_is_support.get_at(initial_extruder_id)) { @@ -2158,7 +2158,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato break; } } - //BBS: find the non-support filament extruder of object + // BBS: find the non-support filament extruder of object if (has_non_support_filament) for (LayerTools layer_tools : tool_ordering.layer_tools()) { if (!layer_tools.has_object) @@ -2173,29 +2173,30 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } } - // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z. - // Therefore initialize the printing extruders from there. + // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in + // Model::custom_gcode_per_print_z. Therefore initialize the printing extruders from there. this->set_extruders(tool_ordering.all_extruders()); - print_object_instances_ordering = + print_object_instances_ordering = // By default, order object instances using a nearest neighbor search. print.config().print_order == PrintOrder::Default ? chain_print_object_instances(print) - // Otherwise same order as the object list - : sort_object_instances_by_model_order(print); + // Otherwise same order as the object list + : + sort_object_instances_by_model_order(print); } - if (initial_extruder_id == (unsigned int)-1) { + if (initial_extruder_id == (unsigned int) -1) { // Nothing to print! - initial_extruder_id = 0; + initial_extruder_id = 0; initial_non_support_extruder_id = 0; - final_extruder_id = 0; + final_extruder_id = 0; } else { final_extruder_id = tool_ordering.last_extruder(); - assert(final_extruder_id != (unsigned int)-1); + assert(final_extruder_id != (unsigned int) -1); } print.throw_if_canceled(); m_cooling_buffer = make_unique(*this); m_cooling_buffer->set_current_extruder(initial_extruder_id); - + // Orca: Initialise AdaptivePA processor filter m_pa_processor = std::make_unique(*this, tool_ordering.all_extruders()); @@ -2205,7 +2206,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Disable fan. if (m_config.auxiliary_fan.value && print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) { file.write(m_writer.set_fan(0)); - //BBS: disable additional fan + // BBS: disable additional fan file.write(m_writer.set_additional_fan(0)); } @@ -2214,29 +2215,34 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Let the start-up script prime the 1st printing tool. this->placeholder_parser().set("initial_tool", initial_extruder_id); this->placeholder_parser().set("initial_extruder", initial_extruder_id); - //BBS + // BBS this->placeholder_parser().set("initial_no_support_tool", initial_non_support_extruder_id); this->placeholder_parser().set("initial_no_support_extruder", initial_non_support_extruder_id); this->placeholder_parser().set("current_extruder", initial_extruder_id); - //Orca: set the key for compatibilty + // Orca: set the key for compatibilty this->placeholder_parser().set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id)); this->placeholder_parser().set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id)); this->placeholder_parser().set("temperature", new ConfigOptionInts(print.config().nozzle_temperature)); this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(m_config.retraction_distances_when_cut)); - this->placeholder_parser().set("long_retractions_when_cut",new ConfigOptionBools(m_config.long_retractions_when_cut)); - //Set variable for total layer count so it can be used in custom gcode. + this->placeholder_parser().set("long_retractions_when_cut", new ConfigOptionBools(m_config.long_retractions_when_cut)); + // Set variable for total layer count so it can be used in custom gcode. this->placeholder_parser().set("total_layer_count", m_layer_count); // Useful for sequential prints. this->placeholder_parser().set("current_object_idx", 0); // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. this->placeholder_parser().set("has_wipe_tower", has_wipe_tower); - this->placeholder_parser().set("has_single_extruder_multi_material_priming", !is_bbl_printers && has_wipe_tower && print.config().single_extruder_multi_material_priming); - this->placeholder_parser().set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). + this->placeholder_parser().set("has_single_extruder_multi_material_priming", + !is_bbl_printers && has_wipe_tower && print.config().single_extruder_multi_material_priming); + this->placeholder_parser() + .set("total_toolchanges", + std::max(0, + print.wipe_tower_data() + .number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). this->placeholder_parser().set("num_extruders", int(print.config().nozzle_diameter.values.size())); this->placeholder_parser().set("retract_length", new ConfigOptionFloats(print.config().retraction_length)); - //Orca: support max MAXIMUM_EXTRUDER_NUMBER extruders/filaments + // Orca: support max MAXIMUM_EXTRUDER_NUMBER extruders/filaments std::vector is_extruder_used(std::max(size_t(MAXIMUM_EXTRUDER_NUMBER), print.config().filament_diameter.size()), 0); for (unsigned int extruder : tool_ordering.all_extruders()) is_extruder_used[extruder] = true; @@ -2244,13 +2250,13 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato { BoundingBoxf bbox_bed(print.config().printable_area.values); - Vec2f plate_offset = m_writer.get_xy_offset(); - this->placeholder_parser().set("print_bed_min", new ConfigOptionFloats({ bbox_bed.min.x(), bbox_bed.min.y()})); - this->placeholder_parser().set("print_bed_max", new ConfigOptionFloats({ bbox_bed.max.x(), bbox_bed.max.y()})); - this->placeholder_parser().set("print_bed_size", new ConfigOptionFloats({ bbox_bed.size().x(), bbox_bed.size().y() })); + Vec2f plate_offset = m_writer.get_xy_offset(); + this->placeholder_parser().set("print_bed_min", new ConfigOptionFloats({bbox_bed.min.x(), bbox_bed.min.y()})); + this->placeholder_parser().set("print_bed_max", new ConfigOptionFloats({bbox_bed.max.x(), bbox_bed.max.y()})); + this->placeholder_parser().set("print_bed_size", new ConfigOptionFloats({bbox_bed.size().x(), bbox_bed.size().y()})); BoundingBoxf bbox; - auto pts = std::make_unique(); + auto pts = std::make_unique(); if (print.calib_mode() == CalibMode::Calib_PA_Line || print.calib_mode() == CalibMode::Calib_PA_Pattern) { bbox = bbox_bed; bbox.offset(-25.0); @@ -2268,29 +2274,27 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // therefore it does NOT encompass the initial purge line. // It does NOT encompass MMU/MMU2 starting (wipe) areas. pts->values.reserve(print.first_layer_convex_hull().size()); - for (const Point &pt : print.first_layer_convex_hull().points) + for (const Point& pt : print.first_layer_convex_hull().points) pts->values.emplace_back(print.translate_to_print_space(pt)); bbox = BoundingBoxf((pts->values)); } this->placeholder_parser().set("first_layer_print_convex_hull", pts.release()); this->placeholder_parser().set("first_layer_print_min", new ConfigOptionFloats({bbox.min.x(), bbox.min.y()})); this->placeholder_parser().set("first_layer_print_max", new ConfigOptionFloats({bbox.max.x(), bbox.max.y()})); - this->placeholder_parser().set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); + this->placeholder_parser().set("first_layer_print_size", new ConfigOptionFloats({bbox.size().x(), bbox.size().y()})); - { + { // use first layer convex_hull union with each object's bbox to check whether in head detect zone Polygons object_projections; for (auto& obj : print.objects()) { for (auto& instance : obj->instances()) { const auto& bbox = instance.get_bounding_box(); - Point min_p{ coord_t(scale_(bbox.min.x())),coord_t(scale_(bbox.min.y())) }; - Point max_p{ coord_t(scale_(bbox.max.x())),coord_t(scale_(bbox.max.y())) }; - Polygon instance_projection = { - {min_p.x(),min_p.y()}, - {max_p.x(),min_p.y()}, - {max_p.x(),max_p.y()}, - {min_p.x(),max_p.y()} - }; + Point min_p{coord_t(scale_(bbox.min.x())), coord_t(scale_(bbox.min.y()))}; + Point max_p{coord_t(scale_(bbox.max.x())), coord_t(scale_(bbox.max.y()))}; + Polygon instance_projection = {{min_p.x(), min_p.y()}, + {max_p.x(), min_p.y()}, + {max_p.x(), max_p.y()}, + {min_p.x(), max_p.y()}}; object_projections.emplace_back(std::move(instance_projection)); } } @@ -2318,27 +2322,25 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato auto bed_mesh_algo = "bicubic"; if (probe_count_x * probe_count_y <= 6) { // lagrange needs up to a total of 6 mesh points bed_mesh_algo = "lagrange"; + } else if (print.config().gcode_flavor == gcfKlipper) { + // bicubic needs 4 probe points per axis + probe_count_x = std::max(probe_count_x, 4); + probe_count_y = std::max(probe_count_y, 4); } - else - if(print.config().gcode_flavor == gcfKlipper){ - // bicubic needs 4 probe points per axis - probe_count_x = std::max(probe_count_x,4); - probe_count_y = std::max(probe_count_y,4); - } this->placeholder_parser().set("bed_mesh_probe_count", new ConfigOptionInts({probe_count_x, probe_count_y})); this->placeholder_parser().set("bed_mesh_algo", bed_mesh_algo); // get center without wipe tower BoundingBoxf bbox_wo_wt; // bounding box without wipe tower - for (auto &objPtr : print.objects()) { + for (auto& objPtr : print.objects()) { BBoxData data; bbox_wo_wt.merge(unscaled(objPtr->get_first_layer_bbox(data.area, data.layer_height, data.name))); } auto center = bbox_wo_wt.center(); - this->placeholder_parser().set("first_layer_center_no_wipe_tower", new ConfigOptionFloats{ {center.x(),center.y()}}); + this->placeholder_parser().set("first_layer_center_no_wipe_tower", new ConfigOptionFloats{{center.x(), center.y()}}); } bool activate_chamber_temp_control = false; auto max_chamber_temp = 0; - for (const auto &extruder : m_writer.extruders()) { + for (const auto& extruder : m_writer.extruders()) { activate_chamber_temp_control |= m_config.activate_chamber_temp_control.get_at(extruder.id()); max_chamber_temp = std::max(max_chamber_temp, m_config.chamber_temperature.get_at(extruder.id())); } @@ -2346,54 +2348,48 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato { int curr_bed_type = m_config.curr_bed_type.getInt(); - std::string first_layer_bed_temp_str; - const ConfigOptionInts* first_bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key((BedType)curr_bed_type)); - const ConfigOptionInts* bed_temp_opt = m_config.option(get_bed_temp_key((BedType)curr_bed_type)); + std::string first_layer_bed_temp_str; + const ConfigOptionInts* first_bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key((BedType) curr_bed_type)); + const ConfigOptionInts* bed_temp_opt = m_config.option(get_bed_temp_key((BedType) curr_bed_type)); this->placeholder_parser().set("bbl_bed_temperature_gcode", new ConfigOptionBool(false)); this->placeholder_parser().set("bed_temperature_initial_layer", new ConfigOptionInts(*first_bed_temp_opt)); this->placeholder_parser().set("bed_temperature", new ConfigOptionInts(*bed_temp_opt)); - this->placeholder_parser().set("bed_temperature_initial_layer_single", new ConfigOptionInt(first_bed_temp_opt->get_at(initial_extruder_id))); + this->placeholder_parser().set("bed_temperature_initial_layer_single", + new ConfigOptionInt(first_bed_temp_opt->get_at(initial_extruder_id))); this->placeholder_parser().set("bed_temperature_initial_layer_vector", new ConfigOptionString()); - this->placeholder_parser().set("chamber_temperature",new ConfigOptionInts(m_config.chamber_temperature)); + this->placeholder_parser().set("chamber_temperature", new ConfigOptionInts(m_config.chamber_temperature)); this->placeholder_parser().set("overall_chamber_temperature", new ConfigOptionInt(max_chamber_temp)); // SoftFever: support variables `first_layer_temperature` and `first_layer_bed_temperature` this->placeholder_parser().set("first_layer_bed_temperature", new ConfigOptionInts(*first_bed_temp_opt)); this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(m_config.nozzle_temperature_initial_layer)); - this->placeholder_parser().set("max_print_height",new ConfigOptionInt(m_config.printable_height)); + this->placeholder_parser().set("max_print_height", new ConfigOptionInt(m_config.printable_height)); this->placeholder_parser().set("z_offset", new ConfigOptionFloat(m_config.z_offset)); this->placeholder_parser().set("model_name", new ConfigOptionString(print.get_model_name())); this->placeholder_parser().set("plate_number", new ConfigOptionString(print.get_plate_number_formatted())); this->placeholder_parser().set("plate_name", new ConfigOptionString(print.get_plate_name())); this->placeholder_parser().set("first_layer_height", new ConfigOptionFloat(m_config.initial_layer_print_height.value)); - //add during_print_exhaust_fan_speed + // add during_print_exhaust_fan_speed std::vector during_print_exhaust_fan_speed_num; during_print_exhaust_fan_speed_num.reserve(m_config.during_print_exhaust_fan_speed.size()); for (const auto& item : m_config.during_print_exhaust_fan_speed.values) - during_print_exhaust_fan_speed_num.emplace_back((int)(item / 100.0 * 255)); - this->placeholder_parser().set("during_print_exhaust_fan_speed_num",new ConfigOptionInts(during_print_exhaust_fan_speed_num)); + during_print_exhaust_fan_speed_num.emplace_back((int) (item / 100.0 * 255)); + this->placeholder_parser().set("during_print_exhaust_fan_speed_num", new ConfigOptionInts(during_print_exhaust_fan_speed_num)); // calculate the volumetric speed of outer wall. Ignore per-object setting and multi-filament, and just use the default setting { - int physical_extruder_id = m_writer.get_physical_extruder(initial_non_support_extruder_id); - - size_t nozzle_array_size = m_config.nozzle_diameter.values.size(); - BOOST_LOG_TRIVIAL(info) << "GCode::process_layer: volumetric_speed_calc - filament_id=" << initial_non_support_extruder_id - << " physical_extruder_id=" << physical_extruder_id - << " nozzle_diameter array_size=" << nozzle_array_size - << " access_index=" << physical_extruder_id - << (physical_extruder_id >= (int)nozzle_array_size ? " [POTENTIAL_OUT_OF_BOUNDS!]" : " [OK]"); - - float filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed")->get_at(initial_non_support_extruder_id); - const double nozzle_diameter = m_config.nozzle_diameter.get_at(physical_extruder_id); - float outer_wall_line_width = print.default_region_config().get_abs_value("outer_wall_line_width", nozzle_diameter); + float filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed") + ->get_at(initial_non_support_extruder_id); + const double nozzle_diameter = m_config.nozzle_diameter.get_at(initial_non_support_extruder_id); + float outer_wall_line_width = print.default_region_config().get_abs_value("outer_wall_line_width", nozzle_diameter); if (outer_wall_line_width == 0.0) { - float default_line_width = print.default_object_config().get_abs_value("line_width", nozzle_diameter); - outer_wall_line_width = default_line_width == 0.0 ? nozzle_diameter : default_line_width; + float default_line_width = print.default_object_config().get_abs_value("line_width", nozzle_diameter); + outer_wall_line_width = default_line_width == 0.0 ? nozzle_diameter : default_line_width; } - Flow outer_wall_flow = Flow(outer_wall_line_width, m_config.layer_height, m_config.nozzle_diameter.get_at(physical_extruder_id)); - float outer_wall_speed = print.default_region_config().outer_wall_speed.value; + Flow outer_wall_flow = Flow(outer_wall_line_width, m_config.layer_height, + m_config.nozzle_diameter.get_at(initial_non_support_extruder_id)); + float outer_wall_speed = print.default_region_config().outer_wall_speed.value; outer_wall_volumetric_speed = outer_wall_speed * outer_wall_flow.mm3_per_mm(); if (outer_wall_volumetric_speed > filament_max_volumetric_speed) outer_wall_volumetric_speed = filament_max_volumetric_speed; @@ -2404,7 +2400,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato this->placeholder_parser().set("scan_first_layer", new ConfigOptionBool(false)); } } - std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, initial_extruder_id); + std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, + initial_extruder_id); if (print.config().gcode_flavor != gcfKlipper) { // Set bed temperature if the start G-code does not contain any bed temp control G-codes. this->_print_first_layer_bed_temperature(file, print, machine_start_gcode, initial_extruder_id, true); @@ -2413,7 +2410,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } // adds tag for processor - file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), + ExtrusionEntity::role_to_string(erCustom).c_str()); // Orca: set chamber temperature at the beginning of gcode file if (activate_chamber_temp_control && max_chamber_temp > 0) @@ -2422,30 +2420,31 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Write the custom start G-code file.writeln(machine_start_gcode); - //BBS: gcode writer doesn't know where the real position of extruder is after inserting custom gcode + // BBS: gcode writer doesn't know where the real position of extruder is after inserting custom gcode m_writer.set_current_position_clear(false); m_start_gcode_filament = GCodeProcessor::get_gcode_last_filament(machine_start_gcode); - //flush FanMover buffer to avoid modifying the start gcode if it's manual. + // flush FanMover buffer to avoid modifying the start gcode if it's manual. if (!machine_start_gcode.empty() && this->m_fan_mover.get() != nullptr) file.write(this->m_fan_mover.get()->process_gcode("", true)); // Process filament-specific gcode. - /* if (has_wipe_tower) { - // Wipe tower will control the extruder switching, it will call the filament_start_gcode. - } else { - DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - file.writeln(this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.values[initial_extruder_id], initial_extruder_id, &config)); - } -*/ + /* if (has_wipe_tower) { + // Wipe tower will control the extruder switching, it will call the filament_start_gcode. + } else { + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); + file.writeln(this->placeholder_parser_process("filament_start_gcode", + print.config().filament_start_gcode.values[initial_extruder_id], initial_extruder_id, &config)); + } + */ if (is_bbl_printers) { this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, true); } // Orca: when activate_air_filtration is set on any extruder, find and set the highest during_print_exhaust_fan_speed bool activate_air_filtration = false; int during_print_exhaust_fan_speed = 0; - for (const auto &extruder : m_writer.extruders()) { + for (const auto& extruder : m_writer.extruders()) { activate_air_filtration |= m_config.activate_air_filtration.get_at(extruder.id()); if (m_config.activate_air_filtration.get_at(extruder.id())) during_print_exhaust_fan_speed = std::max(during_print_exhaust_fan_speed, @@ -2478,8 +2477,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } // Orca: support extruder priming - if (is_bbl_printers || ! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) - { + if (is_bbl_printers || !(has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. file.write(this->set_extruder(initial_extruder_id, 0.)); @@ -2493,13 +2491,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (!iter->second.empty()) this->m_objSupportsWithBrim.insert(iter->first); } - if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) m_brim_done = true; + if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) + m_brim_done = true; // SoftFever: calib if (print.calib_params().mode == CalibMode::Calib_PA_Line) { std::string gcode; - if ((print.default_object_config().outer_wall_acceleration.value > 0 && print.default_object_config().outer_wall_acceleration.value > 0)) { - gcode += m_writer.set_print_acceleration((unsigned int)floor(print.default_object_config().outer_wall_acceleration.value + 0.5)); + if ((print.default_object_config().outer_wall_acceleration.value > 0 && + print.default_object_config().outer_wall_acceleration.value > 0)) { + gcode += m_writer.set_print_acceleration( + (unsigned int) floor(print.default_object_config().outer_wall_acceleration.value + 0.5)); } if (print.default_object_config().outer_wall_jerk.value > 0) { @@ -2511,7 +2512,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato CalibPressureAdvanceLine pa_test(this); - auto fast_speed = CalibPressureAdvance::find_optimal_PA_speed(print.full_print_config(), pa_test.line_width(), pa_test.height_layer()); + auto fast_speed = CalibPressureAdvance::find_optimal_PA_speed(print.full_print_config(), pa_test.line_width(), + pa_test.height_layer()); auto slow_speed = std::max(10.0, fast_speed / 10.0); if (fast_speed < slow_speed + 5) fast_speed = slow_speed + 5; @@ -2522,7 +2524,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write(gcode); } else { - //BBS: open spaghetti detector + // BBS: open spaghetti detector if (is_bbl_printers) { // if (print.config().spaghetti_detector.value) file.write("M981 S1 P20000 ;open spaghetti detector\n"); @@ -2530,19 +2532,20 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Do all objects for each layer. if (print.config().print_sequence == PrintSequence::ByObject && !has_wipe_tower) { - size_t finished_objects = 0; - const PrintObject *prev_object = (*print_object_instance_sequential_active)->print_object; - for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) { - const PrintObject &object = *(*print_object_instance_sequential_active)->print_object; + size_t finished_objects = 0; + const PrintObject* prev_object = (*print_object_instance_sequential_active)->print_object; + for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); + ++print_object_instance_sequential_active) { + const PrintObject& object = *(*print_object_instance_sequential_active)->print_object; if (&object != prev_object || tool_ordering.first_extruder() != final_extruder_id) { - tool_ordering = ToolOrdering(object, final_extruder_id); + tool_ordering = ToolOrdering(object, final_extruder_id); unsigned int new_extruder_id = tool_ordering.first_extruder(); - if (new_extruder_id == (unsigned int)-1) + if (new_extruder_id == (unsigned int) -1) // Skip this object. continue; initial_extruder_id = new_extruder_id; final_extruder_id = tool_ordering.last_extruder(); - assert(final_extruder_id != (unsigned int)-1); + assert(final_extruder_id != (unsigned int) -1); } print.throw_if_canceled(); this->set_origin(unscale((*print_object_instance_sequential_active)->shift)); @@ -2556,12 +2559,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_avoid_crossing_perimeters.use_external_mp_once(); // BBS. change tool before moving to origin point. if (m_writer.need_toolchange(initial_extruder_id)) { - const PrintObjectConfig& object_config = object.config(); - coordf_t initial_layer_print_height = print.config().initial_layer_print_height.value; + const PrintObjectConfig& object_config = object.config(); + coordf_t initial_layer_print_height = print.config().initial_layer_print_height.value; file.write(this->set_extruder(initial_extruder_id, initial_layer_print_height, true)); prime_extruder = true; - } - else { + } else { file.write(this->retract()); } file.write(m_writer.travel_to_z(m_max_layer_z)); @@ -2573,7 +2575,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // another one, set first layer temperatures. This happens before the Z move // is triggered, so machine has more time to reach such temperatures. this->placeholder_parser().set("current_object_idx", int(finished_objects)); - std::string printing_by_object_gcode = this->placeholder_parser_process("printing_by_object_gcode", print.config().printing_by_object_gcode.value, initial_extruder_id); + std::string printing_by_object_gcode = this->placeholder_parser_process("printing_by_object_gcode", + print.config().printing_by_object_gcode.value, + initial_extruder_id); // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. this->_print_first_layer_bed_temperature(file, print, printing_by_object_gcode, initial_extruder_id, false); this->_print_first_layer_extruder_temperatures(file, print, printing_by_object_gcode, initial_extruder_id, false); @@ -2585,36 +2589,40 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. - this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file, prime_extruder); - //BBS: close powerlost recovery + this->process_layers(print, tool_ordering, collect_layers_to_print(object), + *print_object_instance_sequential_active - object.instances().data(), file, prime_extruder); + // BBS: close powerlost recovery { if (is_bbl_printers && m_second_layer_things_done) { file.write("; close powerlost recovery\n"); file.write("M1003 S0\n"); } } - ++ finished_objects; + ++finished_objects; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. // Reset it when starting another object from 1st layer. m_second_layer_things_done = false; - prev_object = &object; + prev_object = &object; } } else { // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. std::vector>> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. - if (has_wipe_tower && ! layers_to_print.empty()) { - m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), * print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); - //BBS + if (has_wipe_tower && !layers_to_print.empty()) { + m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), + *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, + *print.wipe_tower_data().final_purge.get())); + // BBS file.write(m_writer.travel_to_z(initial_layer_print_height + m_config.z_offset.value, "Move to the first layer height")); if (!is_bbl_printers && print.config().single_extruder_multi_material_priming) { file.write(m_wipe_tower->prime(*this)); // Verify, whether the print overaps the priming extrusions. BoundingBoxf bbox_print(get_print_extrusions_extents(print)); - coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; - for (const PrintObject *print_object : print.objects()) + coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + + EPSILON; + for (const PrintObject* print_object : print.objects()) bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); @@ -2629,18 +2637,17 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write("M1 Remove priming towers and click button.\n"); } else { // Just wait for a bit to let the user check, that the priming succeeded. - //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. + // TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. file.write("M1 S10\n"); } - } - else { + } else { // This is not Marlin, M1 command is probably not supported. if (overlap) { - print.active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - _(L("Your print is very close to the priming regions. " - "Make sure there is no collision."))); + print.active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + _(L("Your print is very close to the priming regions. " + "Make sure there is no collision."))); } else { - // Just continue printing, no action necessary. + // Just continue printing, no action necessary. } } } @@ -2650,7 +2657,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); - //BBS: close powerlost recovery + // BBS: close powerlost recovery { if (is_bbl_printers && m_second_layer_things_done) { file.write("; close powerlost recovery\n"); @@ -2662,8 +2669,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write(m_wipe_tower->finalize(*this)); } } - //BBS: the last retraction - // Write end commands to file. + // BBS: the last retraction + // Write end commands to file. file.write(this->retract(false, true)); // if needed, write the gcode_label_objects_end @@ -2674,51 +2681,55 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } file.write(m_writer.set_fan(0)); - //BBS: make sure the additional fan is closed when end - if(m_config.auxiliary_fan.value) + // BBS: make sure the additional fan is closed when end + if (m_config.auxiliary_fan.value) file.write(m_writer.set_additional_fan(0)); if (is_bbl_printers) { - //BBS: close spaghetti detector - //Note: M981 is also used to tell xcam the last layer is finished, so we need always send it even if spaghetti option is disabled. - //if (print.config().spaghetti_detector.value) + // BBS: close spaghetti detector + // Note: M981 is also used to tell xcam the last layer is finished, so we need always send it even if spaghetti option is disabled. + // if (print.config().spaghetti_detector.value) file.write("M981 S0 P20000 ; close spaghetti detector\n"); } // adds tag for processor - file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), + ExtrusionEntity::role_to_string(erCustom).c_str()); // Process filament-specific gcode in extruder order. { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - //BBS - config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2) - m_config.z_offset.value)); + // BBS + config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2) - m_config.z_offset.value)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); if (print.config().single_extruder_multi_material) { // Process the filament_end_gcode for the active filament only. int extruder_id = m_writer.extruder()->id(); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - file.writeln(this->placeholder_parser_process("filament_end_gcode", print.config().filament_end_gcode.get_at(extruder_id), extruder_id, &config)); + file.writeln(this->placeholder_parser_process("filament_end_gcode", print.config().filament_end_gcode.get_at(extruder_id), + extruder_id, &config)); } else { - for (const std::string &end_gcode : print.config().filament_end_gcode.values) { - int extruder_id = (unsigned int)(&end_gcode - &print.config().filament_end_gcode.values.front()); + for (const std::string& end_gcode : print.config().filament_end_gcode.values) { + int extruder_id = (unsigned int) (&end_gcode - &print.config().filament_end_gcode.values.front()); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); file.writeln(this->placeholder_parser_process("filament_end_gcode", end_gcode, extruder_id, &config)); } } - file.writeln(this->placeholder_parser_process("machine_end_gcode", print.config().machine_end_gcode, m_writer.extruder()->id(), &config)); + file.writeln( + this->placeholder_parser_process("machine_end_gcode", print.config().machine_end_gcode, m_writer.extruder()->id(), &config)); } file.write(m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% file.write(m_writer.postamble()); if (activate_chamber_temp_control && max_chamber_temp > 0) - file.write(m_writer.set_chamber_temperature(0, false)); //close chamber_temperature + file.write(m_writer.set_chamber_temperature(0, false)); // close chamber_temperature if (activate_air_filtration) { int complete_print_exhaust_fan_speed = 0; for (const auto& extruder : m_writer.extruders()) if (m_config.activate_air_filtration.get_at(extruder.id())) - complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id())); + complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, + m_config.complete_print_exhaust_fan_speed.get_at(extruder.id())); file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true)); } // adds tags for time estimators @@ -2729,58 +2740,49 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Get filament stats. file.write(DoExport::update_print_stats_and_format_filament_stats( - // Const inputs - has_wipe_tower, print.wipe_tower_data(), - m_writer.extruders(), + // Const inputs + has_wipe_tower, print.wipe_tower_data(), m_writer.extruders(), // Modifies print.m_print_statistics)); print.m_print_statistics.initial_tool = initial_extruder_id; if (!is_bbl_printers) { - file.write_format("; total filament used [g] = %.2lf\n", - print.m_print_statistics.total_weight); - file.write_format("; total filament cost = %.2lf\n", - print.m_print_statistics.total_cost); + file.write_format("; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight); + file.write_format("; total filament cost = %.2lf\n", print.m_print_statistics.total_cost); if (print.m_print_statistics.total_toolchanges > 0) - file.write_format("; total filament change = %i\n", - print.m_print_statistics.total_toolchanges); + file.write_format("; total filament change = %i\n", print.m_print_statistics.total_toolchanges); file.write_format("; total layers count = %i\n", m_layer_count); - file.write_format( - ";%s\n", - GCodeProcessor::reserved_tag( - GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder) - .c_str()); - file.write("\n"); - file.write("; CONFIG_BLOCK_START\n"); - std::string full_config; - append_full_config(print, full_config); - if (!full_config.empty()) - file.write(full_config); + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); + file.write("\n"); + file.write("; CONFIG_BLOCK_START\n"); + std::string full_config; + append_full_config(print, full_config); + if (!full_config.empty()) + file.write(full_config); - // SoftFever: write compatiple info - int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type); - file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature); - file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str()); - file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0)); - file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value); - - //SF TODO -// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0); - - file.write("; CONFIG_BLOCK_END\n\n"); + // SoftFever: write compatiple info + int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type); + file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature); + file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str()); + file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0)); + file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value); + // SF TODO + // file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0); + + file.write("; CONFIG_BLOCK_END\n\n"); } file.write("\n"); print.throw_if_canceled(); } -//BBS +// BBS void GCode::check_placeholder_parser_failed() { - if (! m_placeholder_parser_integration.failed_templates.empty()) { + if (!m_placeholder_parser_integration.failed_templates.empty()) { // G-code export proceeded, but some of the PlaceholderParser substitutions failed. std::string msg = Slic3r::format(_(L("Failed to generate G-code for invalid custom G-code.\n\n"))); - for (const auto &name_and_error : m_placeholder_parser_integration.failed_templates) + for (const auto& name_and_error : m_placeholder_parser_integration.failed_templates) msg += name_and_error.first + " " + name_and_error.second + "\n"; msg += Slic3r::format(_(L("Please check the custom G-code or use the default custom G-code."))); throw Slic3r::PlaceholderParserError(msg); @@ -2790,17 +2792,18 @@ void GCode::check_placeholder_parser_failed() // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. -void GCode::process_layers( - const Print &print, - const ToolOrdering &tool_ordering, - const std::vector &print_object_instances_ordering, - const std::vector>> &layers_to_print, - GCodeOutputStream &output_stream) +void GCode::process_layers(const Print& print, + const ToolOrdering& tool_ordering, + const std::vector& print_object_instances_ordering, + const std::vector>>& layers_to_print, + GCodeOutputStream& output_stream) { // The pipeline is variable: The vase mode filter is optional. - size_t layer_to_print_idx = 0; - const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> LayerResult { + size_t layer_to_print_idx = 0; + const auto generator = tbb::make_filter( + slic3r_tbb_filtermode::serial_in_order, + [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, + &layer_to_print_idx](tbb::flow_control& fc) -> LayerResult { if (layer_to_print_idx >= layers_to_print.size()) { if (layer_to_print_idx == layers_to_print.size() + (m_pressure_equalizer ? 1 : 0)) { fc.stop(); @@ -2812,180 +2815,189 @@ void GCode::process_layers( return LayerResult::make_nop_layer_result(); } } else { - const std::pair>& layer = layers_to_print[layer_to_print_idx++]; - const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); + const std::pair>& layer = layers_to_print[layer_to_print_idx++]; + const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx))); if (m_wipe_tower && layer_tools.has_wipe_tower) m_wipe_tower->next_layer(); - //BBS + // BBS check_placeholder_parser_failed(); print.throw_if_canceled(); - return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); + return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), + &print_object_instances_ordering, size_t(-1)); } }); if (m_spiral_vase) { - float nozzle_diameter = PHYSICAL_EXTRUDER_CONFIG(nozzle_diameter); + float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter); this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing); } - const auto spiral_mode = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](LayerResult in)->LayerResult { - if (in.nop_layer_result) - return in; - - spiral_mode.enable(in.spiral_vase_enable); - bool last_layer = in.layer_id == layers_to_print.size() - 1; - return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; - }); - const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult { - return pressure_equalizer->process_layer(std::move(in)); - }); - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer.get()](LayerResult in) -> std::string { - if (in.nop_layer_result) - return in.gcode; - return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); - }); - const auto pa_processor_filter = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&pa_processor = *this->m_pa_processor](std::string in) -> std::string { - return pa_processor.process_layer(std::move(in)); - } - ); - - const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&output_stream](std::string s) { output_stream.write(s); } - ); + const auto spiral_mode = tbb::make_filter< + LayerResult, + LayerResult>(slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](LayerResult in) -> LayerResult { + if (in.nop_layer_result) + return in; - const auto fan_mover = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in)->std::string { - - CNumericLocalesSetter locales_setter; - - if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { - if (fan_mover.get() == nullptr) - fan_mover.reset(new Slic3r::FanMover( - writer, - std::abs((float)config.fan_speedup_time.value), - config.fan_speedup_time.value > 0, - config.use_relative_e_distances.value, - config.fan_speedup_overhangs.value, - (float)config.fan_kickstart.value)); - //flush as it's a whole layer - return fan_mover->process_gcode(in, true); - } - return in; + spiral_mode.enable(in.spiral_vase_enable); + bool last_layer = in.layer_id == layers_to_print.size() - 1; + return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; }); + const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [pressure_equalizer = this->m_pressure_equalizer.get()]( + LayerResult in) -> LayerResult { + return pressure_equalizer->process_layer(std::move(in)); + }); + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()]( + LayerResult in) -> std::string { + if (in.nop_layer_result) + return in.gcode; + return cooling_buffer.process_layer(std::move(in.gcode), + in.layer_id, + in.cooling_buffer_flush); + }); + const auto pa_processor_filter = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&pa_processor = *this->m_pa_processor]( + std::string in) -> std::string { + return pa_processor.process_layer(std::move(in)); + }); + + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); }); + + const auto fan_mover = tbb::make_filter( + slic3r_tbb_filtermode::serial_in_order, + [&fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in) -> std::string { + CNumericLocalesSetter locales_setter; + + if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { + if (fan_mover.get() == nullptr) + fan_mover.reset(new Slic3r::FanMover(writer, std::abs((float) config.fan_speedup_time.value), + config.fan_speedup_time.value > 0, config.use_relative_e_distances.value, + config.fan_speedup_overhangs.value, (float) config.fan_kickstart.value)); + // flush as it's a whole layer + return fan_mover->process_gcode(in, true); + } + return in; + }); // The pipeline elements are joined using const references, thus no copying is performed. if (m_spiral_vase && m_pressure_equalizer) tbb::parallel_pipeline(12, generator & spiral_mode & pressure_equalizer & cooling & fan_mover & output); else if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output); - else if (m_pressure_equalizer) + tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output); + else if (m_pressure_equalizer) tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & fan_mover & pa_processor_filter & output); else - tbb::parallel_pipeline(12, generator & cooling & fan_mover & pa_processor_filter & output); + tbb::parallel_pipeline(12, generator & cooling & fan_mover & pa_processor_filter & output); } // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. -void GCode::process_layers( - const Print &print, - const ToolOrdering &tool_ordering, - std::vector layers_to_print, - const size_t single_object_idx, - GCodeOutputStream &output_stream, - // BBS - const bool prime_extruder) +void GCode::process_layers(const Print& print, + const ToolOrdering& tool_ordering, + std::vector layers_to_print, + const size_t single_object_idx, + GCodeOutputStream& output_stream, + // BBS + const bool prime_extruder) { // The pipeline is variable: The vase mode filter is optional. - size_t layer_to_print_idx = 0; - const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, prime_extruder](tbb::flow_control& fc) -> LayerResult { - if (layer_to_print_idx >= layers_to_print.size()) { - if (layer_to_print_idx == layers_to_print.size() + (m_pressure_equalizer ? 1 : 0)) { - fc.stop(); - return {}; - } else { - // Pressure equalizer need insert empty input. Because it returns one layer back. - // Insert NOP (no operation) layer; - ++layer_to_print_idx; - return LayerResult::make_nop_layer_result(); - } - } else { - LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; - print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx))); - //BBS - check_placeholder_parser_failed(); - print.throw_if_canceled(); - return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx, prime_extruder); - } - }); + size_t layer_to_print_idx = 0; + const auto generator = + tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, + prime_extruder](tbb::flow_control& fc) -> LayerResult { + if (layer_to_print_idx >= layers_to_print.size()) { + if (layer_to_print_idx == layers_to_print.size() + (m_pressure_equalizer ? 1 : 0)) { + fc.stop(); + return {}; + } else { + // Pressure equalizer need insert empty input. Because it returns one layer back. + // Insert NOP (no operation) layer; + ++layer_to_print_idx; + return LayerResult::make_nop_layer_result(); + } + } else { + LayerToPrint& layer = layers_to_print[layer_to_print_idx++]; + print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), + std::to_string(layer_to_print_idx))); + // BBS + check_placeholder_parser_failed(); + print.throw_if_canceled(); + return this->process_layer(print, {std::move(layer)}, + tool_ordering.tools_for_layer(layer.print_z()), + &layer == &layers_to_print.back(), nullptr, + single_object_idx, prime_extruder); + } + }); if (m_spiral_vase) { - float nozzle_diameter = PHYSICAL_EXTRUDER_CONFIG(nozzle_diameter); + float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter); this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing); } - const auto spiral_mode = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](LayerResult in)->LayerResult { - if (in.nop_layer_result) - return in; - spiral_mode.enable(in.spiral_vase_enable); - bool last_layer = in.layer_id == layers_to_print.size() - 1; - return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; - }); - const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult { - return pressure_equalizer->process_layer(std::move(in)); - }); - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer.get()](LayerResult in)->std::string { - if (in.nop_layer_result) - return in.gcode; - return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); - }); - const auto pa_processor_filter = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&pa_processor = *this->m_pa_processor](std::string in) -> std::string { - return pa_processor.process_layer(std::move(in)); - } - ); - - const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&output_stream](std::string s) { output_stream.write(s); } - ); - - const auto fan_mover = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in)->std::string { - - if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { - if (fan_mover.get() == nullptr) - fan_mover.reset(new Slic3r::FanMover( - writer, - std::abs((float)config.fan_speedup_time.value), - config.fan_speedup_time.value > 0, - config.use_relative_e_distances.value, - config.fan_speedup_overhangs.value, - (float)config.fan_kickstart.value)); - //flush as it's a whole layer - return fan_mover->process_gcode(in, true); - } - return in; + const auto spiral_mode = tbb::make_filter< + LayerResult, + LayerResult>(slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](LayerResult in) -> LayerResult { + if (in.nop_layer_result) + return in; + spiral_mode.enable(in.spiral_vase_enable); + bool last_layer = in.layer_id == layers_to_print.size() - 1; + return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; }); + const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [pressure_equalizer = this->m_pressure_equalizer.get()]( + LayerResult in) -> LayerResult { + return pressure_equalizer->process_layer(std::move(in)); + }); + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()]( + LayerResult in) -> std::string { + if (in.nop_layer_result) + return in.gcode; + return cooling_buffer.process_layer(std::move(in.gcode), + in.layer_id, + in.cooling_buffer_flush); + }); + const auto pa_processor_filter = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&pa_processor = *this->m_pa_processor]( + std::string in) -> std::string { + return pa_processor.process_layer(std::move(in)); + }); + + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); }); + + const auto fan_mover = tbb::make_filter( + slic3r_tbb_filtermode::serial_in_order, + [&fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in) -> std::string { + if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { + if (fan_mover.get() == nullptr) + fan_mover.reset(new Slic3r::FanMover(writer, std::abs((float) config.fan_speedup_time.value), + config.fan_speedup_time.value > 0, config.use_relative_e_distances.value, + config.fan_speedup_overhangs.value, (float) config.fan_kickstart.value)); + // flush as it's a whole layer + return fan_mover->process_gcode(in, true); + } + return in; + }); // The pipeline elements are joined using const references, thus no copying is performed. if (m_spiral_vase && m_pressure_equalizer) tbb::parallel_pipeline(12, generator & spiral_mode & pressure_equalizer & cooling & fan_mover & output); else if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output); - else if (m_pressure_equalizer) + tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output); + else if (m_pressure_equalizer) tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & fan_mover & pa_processor_filter & output); else - tbb::parallel_pipeline(12, generator & cooling & fan_mover & pa_processor_filter & output); + tbb::parallel_pipeline(12, generator & cooling & fan_mover & pa_processor_filter & output); } -std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) +std::string GCode::placeholder_parser_process(const std::string& name, + const std::string& templ, + unsigned int current_extruder_id, + const DynamicConfig* config_override) { // Orca: Added CMake config option since debug is rarely used in current workflow. // Also changed from throwing error immediately to storing messages till slicing is completed @@ -2995,7 +3007,7 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std const auto& custom_gcode_placeholders = custom_gcode_specific_placeholders(); // 1-st check: custom G-code "name" have to be present in s_CustomGcodeSpecificPlaceholders; - //if (custom_gcode_placeholders.count(name) > 0) { + // if (custom_gcode_placeholders.count(name) > 0) { // const auto& placeholders = custom_gcode_placeholders.at(name); if (auto it = custom_gcode_placeholders.find(name); it != custom_gcode_placeholders.end()) { const auto& placeholders = it->second; @@ -3003,7 +3015,9 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std for (const std::string& key : config_override->keys()) { // 2-nd check: "key" have to be present in s_CustomGcodeSpecificPlaceholders for "name" custom G-code ; if (std::find(placeholders.begin(), placeholders.end(), key) == placeholders.end()) { - auto& vector = m_placeholder_error_messages[name + " - option not specified for custom gcode type (s_CustomGcodeSpecificPlaceholders)"]; + auto& vector = + m_placeholder_error_messages[name + + " - option not specified for custom gcode type (s_CustomGcodeSpecificPlaceholders)"]; if (std::find(vector.begin(), vector.end(), key) == vector.end()) vector.emplace_back(key); } @@ -3014,8 +3028,7 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std vector.emplace_back(key); } } - } - else { + } else { auto& vector = m_placeholder_error_messages[name + " - gcode type not found in s_CustomGcodeSpecificPlaceholders"]; if (vector.empty()) vector.emplace_back(""); @@ -3023,34 +3036,32 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std } #endif -PlaceholderParserIntegration &ppi = m_placeholder_parser_integration; + PlaceholderParserIntegration& ppi = m_placeholder_parser_integration; try { ppi.update_from_gcodewriter(m_writer); std::string output = ppi.parser.process(templ, current_extruder_id, config_override, &ppi.output_config, &ppi.context); ppi.validate_output_vector_variables(); - if (const std::vector &pos = ppi.opt_position->values; ppi.position != pos) { + if (const std::vector& pos = ppi.opt_position->values; ppi.position != pos) { // Update G-code writer. - m_writer.set_position({ pos[0], pos[1], pos[2] }); - this->set_last_pos(this->gcode_to_point({ pos[0], pos[1] })); + m_writer.set_position({pos[0], pos[1], pos[2]}); + this->set_last_pos(this->gcode_to_point({pos[0], pos[1]})); } - for (const Extruder &e : m_writer.extruders()) { + for (const Extruder& e : m_writer.extruders()) { unsigned int eid = e.id(); assert(eid < ppi.num_extruders); - if ( eid < ppi.num_extruders) { - if (! m_writer.config.use_relative_e_distances && ! is_approx(ppi.e_position[eid], ppi.opt_e_position->values[eid])) + if (eid < ppi.num_extruders) { + if (!m_writer.config.use_relative_e_distances && !is_approx(ppi.e_position[eid], ppi.opt_e_position->values[eid])) const_cast(e).set_position(ppi.opt_e_position->values[eid]); - if (! is_approx(ppi.e_retracted[eid], ppi.opt_e_retracted->values[eid]) || - ! is_approx(ppi.e_restart_extra[eid], ppi.opt_e_restart_extra->values[eid])) + if (!is_approx(ppi.e_retracted[eid], ppi.opt_e_retracted->values[eid]) || + !is_approx(ppi.e_restart_extra[eid], ppi.opt_e_restart_extra->values[eid])) const_cast(e).set_retracted(ppi.opt_e_retracted->values[eid], ppi.opt_e_restart_extra->values[eid]); } } return output; - } - catch (std::runtime_error &err) - { + } catch (std::runtime_error& err) { // Collect the names of failed template substitutions for error reporting. auto it = ppi.failed_templates.find(name); if (it == ppi.failed_templates.end()) @@ -3058,56 +3069,57 @@ PlaceholderParserIntegration &ppi = m_placeholder_parser_integration; // We don't want to collect error message for each and every occurence of a single custom G-code section. ppi.failed_templates.insert(it, std::make_pair(name, std::string(err.what()))); // Insert the macro error message into the G-code. - return - std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + - err.what() + - "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; + return std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + err.what() + + "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; } } -// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code. -// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. -static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out) +// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the +// custom G-code. Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. +static bool custom_gcode_sets_temperature( + const std::string& gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int& temp_out) { temp_out = -1; if (gcode.empty()) return false; - const char *ptr = gcode.data(); - bool temp_set_by_gcode = false; + const char* ptr = gcode.data(); + bool temp_set_by_gcode = false; while (*ptr != 0) { // Skip whitespaces. - for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); - if (*ptr == 'M' || // Line starts with 'M'. It is a machine command. + for (; *ptr == ' ' || *ptr == '\t'; ++ptr) + ; + if (*ptr == 'M' || // Line starts with 'M'. It is a machine command. (*ptr == 'G' && include_g10)) { // Only check for G10 if requested bool is_gcode = *ptr == 'G'; - ++ ptr; + ++ptr; // Parse the M or G code value. - char *endptr = nullptr; - int mgcode = int(strtol(ptr, &endptr, 10)); - if (endptr != nullptr && endptr != ptr && - is_gcode ? + char* endptr = nullptr; + int mgcode = int(strtol(ptr, &endptr, 10)); + if (endptr != nullptr && endptr != ptr && is_gcode ? // G10 found mgcode == 10 : // M104/M109 or M140/M190 found. (mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) { ptr = endptr; - if (! is_gcode) + if (!is_gcode) // Let the caller know that the custom M-code sets the temperature. temp_set_by_gcode = true; // Now try to parse the temperature value. // While not at the end of the line: while (strchr(";\r\n\0", *ptr) == nullptr) { // Skip whitespaces. - for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + for (; *ptr == ' ' || *ptr == '\t'; ++ptr) + ; if (*ptr == 'S') { // Skip whitespaces. - for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); + for (++ptr; *ptr == ' ' || *ptr == '\t'; ++ptr) + ; // Parse an int. - endptr = nullptr; + endptr = nullptr; long temp_parsed = strtol(ptr, &endptr, 10); if (endptr > ptr) { - ptr = endptr; + ptr = endptr; temp_out = temp_parsed; // Let the caller know that the custom G-code sets the temperature // Only do this after successfully parsing temperature since G10 @@ -3116,68 +3128,64 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc } } else { // Skip this word. - for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ptr) + ; } } } } // Skip the rest of the line. - for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); + for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ptr) + ; // Skip the end of line indicators. - for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); + for (; *ptr == '\r' || *ptr == '\n'; ++ptr) + ; } return temp_set_by_gcode; } // Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. -void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print) +void GCode::print_machine_envelope(GCodeOutputStream& file, Print& print) { const auto flavor = print.config().gcode_flavor.value; if ((flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware) && print.config().emit_machine_limits_to_gcode.value == true) { int factor = flavor == gcfRepRapFirmware ? 60 : 1; // RRF M203 and M566 are in mm/min - file.write_format("M201 X%d Y%d Z%d E%d\n", - int(print.config().machine_max_acceleration_x.values.front() + 0.5), - int(print.config().machine_max_acceleration_y.values.front() + 0.5), - int(print.config().machine_max_acceleration_z.values.front() + 0.5), - int(print.config().machine_max_acceleration_e.values.front() + 0.5)); - file.write_format("M203 X%d Y%d Z%d E%d\n", - int(print.config().machine_max_speed_x.values.front() * factor + 0.5), - int(print.config().machine_max_speed_y.values.front() * factor + 0.5), - int(print.config().machine_max_speed_z.values.front() * factor + 0.5), - int(print.config().machine_max_speed_e.values.front() * factor + 0.5)); + file.write_format("M201 X%d Y%d Z%d E%d\n", int(print.config().machine_max_acceleration_x.values.front() + 0.5), + int(print.config().machine_max_acceleration_y.values.front() + 0.5), + int(print.config().machine_max_acceleration_z.values.front() + 0.5), + int(print.config().machine_max_acceleration_e.values.front() + 0.5)); + file.write_format("M203 X%d Y%d Z%d E%d\n", int(print.config().machine_max_speed_x.values.front() * factor + 0.5), + int(print.config().machine_max_speed_y.values.front() * factor + 0.5), + int(print.config().machine_max_speed_z.values.front() * factor + 0.5), + int(print.config().machine_max_speed_e.values.front() * factor + 0.5)); // Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about // Legacy Marlin should export travel acceleration the same as printing acceleration. // MarlinFirmware has the two separated. - int travel_acc = flavor == gcfMarlinLegacy - ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) - : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); + int travel_acc = flavor == gcfMarlinLegacy ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) : + int(print.config().machine_max_acceleration_travel.values.front() + 0.5); if (flavor == gcfRepRapFirmware) file.write_format("M204 P%d T%d ; sets acceleration (P, T), mm/sec^2\n", - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), - travel_acc); + int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), travel_acc); else if (flavor == gcfMarlinFirmware) // New Marlin uses M204 P[print] R[retract] T[travel] file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), - int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), - int(print.config().machine_max_acceleration_travel.values.front() + 0.5)); + int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), + int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), + int(print.config().machine_max_acceleration_travel.values.front() + 0.5)); else - file.write_format("M204 P%d R%d T%d\n", - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), - int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), - travel_acc); + file.write_format("M204 P%d R%d T%d\n", int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), + int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), travel_acc); assert(is_decimal_separator_point()); - file.write_format(flavor == gcfRepRapFirmware - ? "M566 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/min\n" - : "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", - print.config().machine_max_jerk_x.values.front() * factor, - print.config().machine_max_jerk_y.values.front() * factor, - print.config().machine_max_jerk_z.values.front() * factor, - print.config().machine_max_jerk_e.values.front() * factor); + file.write_format(flavor == gcfRepRapFirmware ? "M566 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/min\n" : + "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", + print.config().machine_max_jerk_x.values.front() * factor, + print.config().machine_max_jerk_y.values.front() * factor, + print.config().machine_max_jerk_z.values.front() * factor, + print.config().machine_max_jerk_e.values.front() * factor); // New Marlin uses M205 J[mm] for junction deviation (only apply if it is > 0) file.write_format(writer().set_junction_deviation(config().machine_max_junction_deviation.values.front()).c_str()); @@ -3187,7 +3195,7 @@ void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print) // BBS int GCode::get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const { - std::string bed_temp_key = is_first_layer ? get_bed_temp_1st_layer_key(bed_type) : get_bed_temp_key(bed_type); + std::string bed_temp_key = is_first_layer ? get_bed_temp_1st_layer_key(bed_type) : get_bed_temp_key(bed_type); const ConfigOptionInts* bed_temp_opt = m_config.option(bed_temp_key); return bed_temp_opt->get_at(extruder_id); } @@ -3196,12 +3204,13 @@ int GCode::get_bed_temperature(const int extruder_id, const bool is_first_layer, // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // M140 - Set Extruder Temperature // M190 - Set Extruder Temperature and Wait -void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCode::_print_first_layer_bed_temperature( + GCodeOutputStream& file, Print& print, const std::string& gcode, unsigned int first_printing_extruder_id, bool wait) { // Initial bed temperature based on the first extruder. // BBS std::vector temps_per_bed; - int bed_temp = get_bed_temperature(first_printing_extruder_id, true, print.config().curr_bed_type); + int bed_temp = get_bed_temperature(first_printing_extruder_id, true, print.config().curr_bed_type); // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; @@ -3215,7 +3224,7 @@ void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &p // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if // the custom start G-code emited these. std::string set_temp_gcode = m_writer.set_bed_temperature(bed_temp, wait); - if (! temp_set_by_gcode) + if (!temp_set_by_gcode) file.write(set_temp_gcode); } @@ -3224,7 +3233,8 @@ void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &p // M104 - Set Extruder Temperature // M109 - Set Extruder Temperature and Wait // RepRapFirmware: G10 Sxx -void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCode::_print_first_layer_extruder_temperatures( + GCodeOutputStream& file, Print& print, const std::string& gcode, unsigned int first_printing_extruder_id, bool wait) { // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; @@ -3249,7 +3259,7 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr int target_tool = -1; for (unsigned int tool_id : print.extruders()) { is_active = true; - int temp = print.config().nozzle_temperature_initial_layer.get_at(tool_id); + int temp = print.config().nozzle_temperature_initial_layer.get_at(tool_id); if (print.config().ooze_prevention.value && tool_id != first_printing_extruder_id) { is_active = false; if (print.config().idle_temperature.get_at(tool_id) == 0) @@ -3261,9 +3271,9 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr if (is_active) { target_temp = temp; target_tool = tool_id; - }else + } else file.write(m_writer.set_temperature(temp, wait, tool_id)); - } + } } if (target_temp != -1 && target_tool != -1) { file.write(m_writer.set_temperature(target_temp, wait, target_tool)); @@ -3272,116 +3282,115 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr } } -inline GCode::ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects) +inline GCode::ObjectByExtruder& object_by_extruder(std::map>& by_extruder, + unsigned int extruder_id, + size_t object_idx, + size_t num_objects) { - std::vector &objects_by_extruder = by_extruder[extruder_id]; + std::vector& objects_by_extruder = by_extruder[extruder_id]; if (objects_by_extruder.empty()) objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder()); return objects_by_extruder[object_idx]; } inline std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects, - size_t num_islands) + std::map>& by_extruder, + unsigned int extruder_id, + size_t object_idx, + size_t num_objects, + size_t num_islands) { - std::vector &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; + std::vector& islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; if (islands.empty()) islands.assign(num_islands, GCode::ObjectByExtruder::Island()); return islands; } std::vector GCode::sort_print_object_instances( - std::vector &objects_by_extruder, - const std::vector &layers, + std::vector& objects_by_extruder, + const std::vector& layers, // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, + const std::vector* ordering, // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx) + const size_t single_object_instance_idx) { std::vector out; if (ordering == nullptr) { // Sequential print, single object is being printed. - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - //BBS:add the support of shared print object - const PrintObject *print_object = layers[layer_id].original_object; - //const PrintObject *print_object = layers[layer_id].object(); + for (ObjectByExtruder& object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + // BBS:add the support of shared print object + const PrintObject* print_object = layers[layer_id].original_object; + // const PrintObject *print_object = layers[layer_id].object(); if (print_object) - out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx, print_object->instances()[single_object_instance_idx].model_instance->get_labeled_id()); + out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx, + print_object->instances()[single_object_instance_idx].model_instance->get_labeled_id()); } } else { // Create mapping from PrintObject* to ObjectByExtruder*. std::vector> sorted; sorted.reserve(objects_by_extruder.size()); - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - //BBS:add the support of shared print object - const PrintObject *print_object = layers[layer_id].original_object; - //const PrintObject *print_object = layers[layer_id].object(); + for (ObjectByExtruder& object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + // BBS:add the support of shared print object + const PrintObject* print_object = layers[layer_id].original_object; + // const PrintObject *print_object = layers[layer_id].object(); if (print_object) sorted.emplace_back(print_object, &object_by_extruder); } std::sort(sorted.begin(), sorted.end()); - if (! sorted.empty()) { + if (!sorted.empty()) { out.reserve(sorted.size()); - for (const PrintInstance *instance : *ordering) { - const PrintObject &print_object = *instance->print_object; - //BBS:add the support of shared print object - //const PrintObject* print_obj_ptr = &print_object; - //if (print_object.get_shared_object()) - // print_obj_ptr = print_object.get_shared_object(); + for (const PrintInstance* instance : *ordering) { + const PrintObject& print_object = *instance->print_object; + // BBS:add the support of shared print object + // const PrintObject* print_obj_ptr = &print_object; + // if (print_object.get_shared_object()) + // print_obj_ptr = print_object.get_shared_object(); std::pair key(&print_object, nullptr); - auto it = std::lower_bound(sorted.begin(), sorted.end(), key); + auto it = std::lower_bound(sorted.begin(), sorted.end(), key); if (it != sorted.end() && it->first == &print_object) // ObjectByExtruder for this PrintObject was found. - out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data(), instance->model_instance->get_labeled_id()); + out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, + instance - print_object.instances().data(), instance->model_instance->get_labeled_id()); } } } return out; } -namespace ProcessLayer +namespace ProcessLayer { + +static std::string emit_custom_gcode_per_print_z(GCode& gcodegen, + const CustomGCode::Item* custom_gcode, + unsigned int current_extruder_id, + // ID of the first extruder printing this layer. + unsigned int first_extruder_id, + const PrintConfig& config) { + std::string gcode; + // BBS + bool single_filament_print = config.filament_diameter.size() == 1; - static std::string emit_custom_gcode_per_print_z( - GCode &gcodegen, - const CustomGCode::Item *custom_gcode, - unsigned int current_extruder_id, - // ID of the first extruder printing this layer. - unsigned int first_extruder_id, - const PrintConfig &config) - { - std::string gcode; - // BBS - bool single_filament_print = config.filament_diameter.size() == 1; + if (custom_gcode != nullptr) { + // Extruder switches are processed by LayerTools, they should be filtered out. + assert(custom_gcode->type != CustomGCode::ToolChange); - if (custom_gcode != nullptr) { - // Extruder switches are processed by LayerTools, they should be filtered out. - assert(custom_gcode->type != CustomGCode::ToolChange); + CustomGCode::Type gcode_type = custom_gcode->type; + bool color_change = gcode_type == CustomGCode::ColorChange; + bool tool_change = gcode_type == CustomGCode::ToolChange; + // Tool Change is applied as Color Change for a single extruder printer only. + assert(!tool_change || single_filament_print); - CustomGCode::Type gcode_type = custom_gcode->type; - bool color_change = gcode_type == CustomGCode::ColorChange; - bool tool_change = gcode_type == CustomGCode::ToolChange; - // Tool Change is applied as Color Change for a single extruder printer only. - assert(!tool_change || single_filament_print); - - std::string pause_print_msg; - int m600_extruder_before_layer = -1; - if (color_change && custom_gcode->extruder > 0) - m600_extruder_before_layer = custom_gcode->extruder - 1; - else if (gcode_type == CustomGCode::PausePrint) - pause_print_msg = custom_gcode->extra; - //BBS: inserting color gcode is removed + std::string pause_print_msg; + int m600_extruder_before_layer = -1; + if (color_change && custom_gcode->extruder > 0) + m600_extruder_before_layer = custom_gcode->extruder - 1; + else if (gcode_type == CustomGCode::PausePrint) + pause_print_msg = custom_gcode->extra; + // BBS: inserting color gcode is removed #if 0 // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count if (color_change || tool_change) @@ -3412,137 +3421,141 @@ namespace ProcessLayer } else { #endif - if (gcode_type == CustomGCode::PausePrint) // Pause print - { - // add tag for processor - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n"; - //! FIXME_in_fw show message during print pause - //if (!pause_print_msg.empty()) - // gcode += "M117 " + pause_print_msg + "\n"; - gcode += gcodegen.placeholder_parser_process("machine_pause_gcode", config.machine_pause_gcode, current_extruder_id) + "\n"; - } - else { - // add tag for processor - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n"; - if (gcode_type == CustomGCode::Template) // Template Custom Gcode - gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id); - else // custom Gcode - gcode += custom_gcode->extra; - - } - gcode += "\n"; + if (gcode_type == CustomGCode::PausePrint) // Pause print + { + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n"; + //! FIXME_in_fw show message during print pause + // if (!pause_print_msg.empty()) + // gcode += "M117 " + pause_print_msg + "\n"; + gcode += gcodegen.placeholder_parser_process("machine_pause_gcode", config.machine_pause_gcode, current_extruder_id) + "\n"; + } else { + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n"; + if (gcode_type == CustomGCode::Template) // Template Custom Gcode + gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id); + else // custom Gcode + gcode += custom_gcode->extra; + } + gcode += "\n"; #if 0 } #endif - } - - return gcode; } + + return gcode; +} } // namespace ProcessLayer namespace Skirt { - static void skirt_loops_per_extruder_all_printing(const Print &print, const ExtrusionEntityCollection &skirt, const LayerTools &layer_tools, std::map> &skirt_loops_per_extruder_out) - { - // Prime all extruders printing over the 1st layer over the skirt lines. - size_t n_loops = skirt.entities.size(); - size_t n_tools = layer_tools.extruders.size(); - size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools; +static void skirt_loops_per_extruder_all_printing(const Print& print, + const ExtrusionEntityCollection& skirt, + const LayerTools& layer_tools, + std::map>& skirt_loops_per_extruder_out) +{ + // Prime all extruders printing over the 1st layer over the skirt lines. + size_t n_loops = skirt.entities.size(); + size_t n_tools = layer_tools.extruders.size(); + size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools; - // BBS. Extrude skirt with first extruder if min_skirt_length is zero - //ORCA: Always extrude skirt with first extruder, independantly of if the minimum skirt length is zero or not. The code below - // is left as a placeholder for when a multiextruder support is implemented. Then we will need to extrude the skirt loops for each extruder. - //const PrintConfig &config = print.config(); - //if (config.min_skirt_length.value < EPSILON) { - skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, n_loops); - //} else { - // for (size_t i = 0; i < n_loops; i += lines_per_extruder) - // skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair(i, std::min(i + lines_per_extruder, n_loops)); - //} + // BBS. Extrude skirt with first extruder if min_skirt_length is zero + // ORCA: Always extrude skirt with first extruder, independantly of if the minimum skirt length is zero or not. The code below + // is left as a placeholder for when a multiextruder support is implemented. Then we will need to extrude the skirt loops for each extruder. + // const PrintConfig &config = print.config(); + // if (config.min_skirt_length.value < EPSILON) { + skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, n_loops); + //} else { + // for (size_t i = 0; i < n_loops; i += lines_per_extruder) + // skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair(i, std::min(i + + // lines_per_extruder, n_loops)); + //} +} + +static std::map> make_skirt_loops_per_extruder_1st_layer( + const Print& print, + const ExtrusionEntityCollection& skirt, + const LayerTools& layer_tools, + // Heights (print_z) at which the skirt has already been extruded. + std::vector& skirt_done) +{ + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder_out; + // For sequential print, the following test may fail when extruding the 2nd and other objects. + // assert(skirt_done.empty()); + if (skirt_done.empty() && print.has_skirt() && !skirt.entities.empty() && layer_tools.has_skirt) { + skirt_loops_per_extruder_all_printing(print, skirt, layer_tools, skirt_loops_per_extruder_out); + skirt_done.emplace_back(layer_tools.print_z); } + return skirt_loops_per_extruder_out; +} - static std::map> make_skirt_loops_per_extruder_1st_layer( - const Print &print, - const ExtrusionEntityCollection &skirt, - const LayerTools &layer_tools, - // Heights (print_z) at which the skirt has already been extruded. - std::vector &skirt_done) - { - // Extrude skirt at the print_z of the raft layers and normal object layers - // not at the print_z of the interlaced support material layers. - std::map> skirt_loops_per_extruder_out; - //For sequential print, the following test may fail when extruding the 2nd and other objects. - // assert(skirt_done.empty()); - if (skirt_done.empty() && print.has_skirt() && ! skirt.entities.empty() && layer_tools.has_skirt) { - skirt_loops_per_extruder_all_printing(print, skirt, layer_tools, skirt_loops_per_extruder_out); - skirt_done.emplace_back(layer_tools.print_z); - } - return skirt_loops_per_extruder_out; - } - - static std::map> make_skirt_loops_per_extruder_other_layers( - const Print &print, - const ExtrusionEntityCollection &skirt, - const LayerTools &layer_tools, - // Heights (print_z) at which the skirt has already been extruded. - std::vector &skirt_done) - { - // Extrude skirt at the print_z of the raft layers and normal object layers - // not at the print_z of the interlaced support material layers. - std::map> skirt_loops_per_extruder_out; - if (print.has_skirt() && ! skirt.entities.empty() && layer_tools.has_skirt && - // Not enough skirt layers printed yet. - //FIXME infinite or high skirt does not make sense for sequential print! - (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt())) { - bool valid = ! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON; - assert(valid); - // This print_z has not been extruded yet (sequential print) - // FIXME: The skirt_done should not be empty at this point. The check is a workaround - if (valid) { +static std::map> make_skirt_loops_per_extruder_other_layers( + const Print& print, + const ExtrusionEntityCollection& skirt, + const LayerTools& layer_tools, + // Heights (print_z) at which the skirt has already been extruded. + std::vector& skirt_done) +{ + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder_out; + if (print.has_skirt() && !skirt.entities.empty() && layer_tools.has_skirt && + // Not enough skirt layers printed yet. + // FIXME infinite or high skirt does not make sense for sequential print! + (skirt_done.size() < (size_t) print.config().skirt_height.value || print.has_infinite_skirt())) { + bool valid = !skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON; + assert(valid); + // This print_z has not been extruded yet (sequential print) + // FIXME: The skirt_done should not be empty at this point. The check is a workaround + if (valid) { #if 0 // Prime just the first printing extruder. This is original Slic3r's implementation. skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, print.config().skirt_loops.value); #else - // Prime all extruders planned for this layer, see - skirt_loops_per_extruder_all_printing(print, skirt, layer_tools, skirt_loops_per_extruder_out); + // Prime all extruders planned for this layer, see + skirt_loops_per_extruder_all_printing(print, skirt, layer_tools, skirt_loops_per_extruder_out); #endif - assert(!skirt_done.empty()); - skirt_done.emplace_back(layer_tools.print_z); - } + assert(!skirt_done.empty()); + skirt_done.emplace_back(layer_tools.print_z); } - return skirt_loops_per_extruder_out; + } + return skirt_loops_per_extruder_out; +} + +static Point find_start_point(ExtrusionLoop& loop, float start_angle) +{ + coord_t min_x = std::numeric_limits::max(); + coord_t max_x = std::numeric_limits::min(); + coord_t min_y = min_x; + coord_t max_y = max_x; + + Points pts; + loop.collect_points(pts); + for (Point pt : pts) { + if (pt.x() < min_x) + min_x = pt.x(); + else if (pt.x() > max_x) + max_x = pt.x(); + if (pt.y() < min_y) + min_y = pt.y(); + else if (pt.y() > max_y) + max_y = pt.y(); } - static Point find_start_point(ExtrusionLoop& loop, float start_angle) { - coord_t min_x = std::numeric_limits::max(); - coord_t max_x = std::numeric_limits::min(); - coord_t min_y = min_x; - coord_t max_y = max_x; - - Points pts; - loop.collect_points(pts); - for (Point pt: pts) { - if (pt.x() < min_x) - min_x = pt.x(); - else if (pt.x() > max_x) - max_x = pt.x(); - if (pt.y() < min_y) - min_y = pt.y(); - else if (pt.y() > max_y) - max_y = pt.y(); - } - - Point center((min_x + max_x)/2., (min_y + max_y)/2.); - double r = center.distance_to(Point(min_x, min_y)); - double deg = start_angle * PI / 180; - double shift_x = r * std::cos(deg); - double shift_y = r * std::sin(deg); - return Point(center.x()+shift_x, center.y() + shift_y); - } + Point center((min_x + max_x) / 2., (min_y + max_y) / 2.); + double r = center.distance_to(Point(min_x, min_y)); + double deg = start_angle * PI / 180; + double shift_x = r * std::cos(deg); + double shift_y = r * std::sin(deg); + return Point(center.x() + shift_x, center.y() + shift_y); +} } // namespace Skirt // Orca: Klipper can't parse object names with spaces and other spetical characters -std::string sanitize_instance_name(const std::string& name) { +std::string sanitize_instance_name(const std::string& name) +{ // Replace sequences of non-word characters with an underscore std::string result = std::regex_replace(name, std::regex("[ !@#$%^&*()=+\\[\\]{};:\",']+"), "_"); // Remove leading and trailing underscores @@ -3556,68 +3569,63 @@ std::string sanitize_instance_name(const std::string& name) { return result; } -inline std::string get_instance_name(const PrintObject *object, size_t inst_id) { +inline std::string get_instance_name(const PrintObject* object, size_t inst_id) +{ auto obj_name = sanitize_instance_name(object->model_object()->name); - auto name = (boost::format("%1%_id_%2%_copy_%3%") % obj_name % object->get_id() % inst_id).str(); + auto name = (boost::format("%1%_id_%2%_copy_%3%") % obj_name % object->get_id() % inst_id).str(); return sanitize_instance_name(name); } -inline std::string get_instance_name(const PrintObject *object, const PrintInstance &inst) { - return get_instance_name(object, inst.id); -} +inline std::string get_instance_name(const PrintObject* object, const PrintInstance& inst) { return get_instance_name(object, inst.id); } -std::string GCode::generate_skirt(const Print &print, - const ExtrusionEntityCollection &skirt, - const Point& offset, - const float skirt_start_angle, - const LayerTools &layer_tools, - const Layer& layer, - unsigned int extruder_id) +std::string GCode::generate_skirt(const Print& print, + const ExtrusionEntityCollection& skirt, + const Point& offset, + const float skirt_start_angle, + const LayerTools& layer_tools, + const Layer& layer, + unsigned int extruder_id) { - - bool first_layer = (layer.id() == 0 && abs(layer.bottom_z()) < EPSILON); + bool first_layer = (layer.id() == 0 && abs(layer.bottom_z()) < EPSILON); std::string gcode; // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. // Map from extruder ID to index of skirt loops to be extruded with that extruder. std::map> skirt_loops_per_extruder; - skirt_loops_per_extruder = first_layer ? - Skirt::make_skirt_loops_per_extruder_1st_layer(print, skirt, layer_tools, m_skirt_done) : - Skirt::make_skirt_loops_per_extruder_other_layers(print, skirt, layer_tools, m_skirt_done); + skirt_loops_per_extruder = first_layer ? Skirt::make_skirt_loops_per_extruder_1st_layer(print, skirt, layer_tools, m_skirt_done) : + Skirt::make_skirt_loops_per_extruder_other_layers(print, skirt, layer_tools, m_skirt_done); if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { const std::pair loops = loops_it->second; - + set_origin(unscaled(offset)); m_avoid_crossing_perimeters.use_external_mp(); - Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); + Flow layer_skirt_flow = print.skirt_flow().with_height( + float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); // Decide where to start looping: // - If it’s the first layer or if we do NOT want a single-wall skirt/draft shield, // start from loops.first (all loops). // - Otherwise, if single_loop_draft_shield == true (and not the first layer), // start from loops.second - 1 (just one loop). - const size_t start_idx = (first_layer || !print.m_config.single_loop_draft_shield) - ? loops.first - : (loops.second - 1); + const size_t start_idx = (first_layer || !print.m_config.single_loop_draft_shield) ? loops.first : (loops.second - 1); // Loop over the skirt loops and extrude for (size_t i = start_idx; i < loops.second; ++i) { // Adjust flow according to this layer's layer height. ExtrusionLoop loop = *dynamic_cast(skirt.entities[i]); - for (ExtrusionPath &path : loop.paths) { - path.height = layer_skirt_flow.height(); + for (ExtrusionPath& path : loop.paths) { + path.height = layer_skirt_flow.height(); path.mm3_per_mm = mm3_per_mm; } - //FIXME using the support_speed of the 1st object printed. - if (first_layer && i==loops.first) { - //set skirt start point location + // FIXME using the support_speed of the 1st object printed. + if (first_layer && i == loops.first) { + // set skirt start point location const Point desired_start_point = Skirt::find_start_point(loop, skirt_start_angle); gcode += this->extrude_loop(loop, "skirt", m_config.support_speed.value, {}, &desired_start_point); - } - else + } else gcode += this->extrude_loop(loop, "skirt", m_config.support_speed.value); // If we only want a single wall on non-first layers, break now @@ -3638,35 +3646,34 @@ std::string GCode::generate_skirt(const Print &print, // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // and performing the extruder specific extrusions together. -LayerResult GCode::process_layer( - const Print &print, - // Set of object & print layers of the same PrintObject and with the same print_z. - const std::vector &layers, - const LayerTools &layer_tools, - const bool last_layer, - // Pairs of PrintObject index and its instance index. - const std::vector *ordering, - // If set to size_t(-1), then print all copies of all objects. - // Otherwise print a single copy of a single object. - const size_t single_object_instance_idx, - // BBS - const bool prime_extruder) +LayerResult GCode::process_layer(const Print& print, + // Set of object & print layers of the same PrintObject and with the same print_z. + const std::vector& layers, + const LayerTools& layer_tools, + const bool last_layer, + // Pairs of PrintObject index and its instance index. + const std::vector* ordering, + // If set to size_t(-1), then print all copies of all objects. + // Otherwise print a single copy of a single object. + const size_t single_object_instance_idx, + // BBS + const bool prime_extruder) { - assert(! layers.empty()); + assert(!layers.empty()); // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); // First object, support and raft layer, if available. - const Layer *object_layer = nullptr; - const SupportLayer *support_layer = nullptr; - const SupportLayer *raft_layer = nullptr; - for (const LayerToPrint &l : layers) { - if (l.object_layer && ! object_layer) + const Layer* object_layer = nullptr; + const SupportLayer* support_layer = nullptr; + const SupportLayer* raft_layer = nullptr; + for (const LayerToPrint& l : layers) { + if (l.object_layer && !object_layer) object_layer = l.object_layer; if (l.support_layer) { - if (! support_layer) + if (!support_layer) support_layer = l.support_layer; - if (! raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers()) + if (!raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers()) raft_layer = support_layer; } } @@ -3677,19 +3684,19 @@ LayerResult GCode::process_layer( else if (support_layer != nullptr) layer_ptr = support_layer; const Layer& layer = *layer_ptr; - LayerResult result { {}, layer.id(), false, last_layer }; + LayerResult result{{}, layer.id(), false, last_layer}; if (layer_tools.extruders.empty()) // Nothing to extrude. return result; // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. - coordf_t print_z = layer.print_z; - //BBS: using layer id to judge whether the layer is first layer is wrong. Because if the normal - //support is attached above the object, and support layers has independent layer height, then the lowest support - //interface layer id is 0. - bool first_layer = (layer.id() == 0 && abs(layer.bottom_z()) < EPSILON); + coordf_t print_z = layer.print_z; + // BBS: using layer id to judge whether the layer is first layer is wrong. Because if the normal + // support is attached above the object, and support layers has independent layer height, then the lowest support + // interface layer id is 0. + bool first_layer = (layer.id() == 0 && abs(layer.bottom_z()) < EPSILON); m_writer.set_is_first_layer(first_layer); - unsigned int first_extruder_id = layer_tools.extruders.front(); + unsigned int first_extruder_id = layer_tools.extruders.front(); // Initialize config with the 1st object to be printed at this layer. m_config.apply(layer.object()->config(), true); @@ -3698,12 +3705,12 @@ LayerResult GCode::process_layer( // Just a reminder: A spiral vase mode is allowed for a single object, single material print only. m_enable_loop_clipping = true; if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) { - bool enable = (layer.id() > 0 || !print.has_brim()) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt()); + bool enable = (layer.id() > 0 || !print.has_brim()) && + (layer.id() >= (size_t) print.config().skirt_height.value && !print.has_infinite_skirt()); if (enable) { - for (const LayerRegion *layer_region : layer.regions()) + for (const LayerRegion* layer_region : layer.regions()) if (size_t(layer_region->region().config().bottom_shell_layers.value) > layer.id() || - layer_region->perimeters.items_count() > 1u || - layer_region->fills.items_count() > 0) { + layer_region->perimeters.items_count() > 1u || layer_region->fills.items_count() > 0) { enable = false; break; } @@ -3729,24 +3736,22 @@ LayerResult GCode::process_layer( // update caches m_last_layer_z = static_cast(print_z); m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); - m_last_height = height; + m_last_height = height; // Set new layer - this will change Z and force a retraction if retract_when_changing_layer is enabled. - if (! m_config.before_layer_change_gcode.value.empty()) { + if (!m_config.before_layer_change_gcode.value.empty()) { DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - gcode += this->placeholder_parser_process("before_layer_change_gcode", - print.config().before_layer_change_gcode.value, m_writer.extruder()->id(), &config) - + "\n"; + gcode += this->placeholder_parser_process("before_layer_change_gcode", print.config().before_layer_change_gcode.value, + m_writer.extruder()->id(), &config) + + "\n"; } - PrinterStructure printer_structure = m_config.printer_structure.value; - bool need_insert_timelapse_gcode_for_traditional = false; - if (printer_structure == PrinterStructure::psI3 && - !m_spiral_vase && - (!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) && + PrinterStructure printer_structure = m_config.printer_structure.value; + bool need_insert_timelapse_gcode_for_traditional = false; + if (printer_structure == PrinterStructure::psI3 && !m_spiral_vase && (!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) && print.config().print_sequence == PrintSequence::ByLayer) { need_insert_timelapse_gcode_for_traditional = true; } @@ -3760,28 +3765,31 @@ LayerResult GCode::process_layer( config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - gcode_res = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.extruder()->id(), &config) + "\n"; + gcode_res = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, + m_writer.extruder()->id(), &config) + + "\n"; } return gcode_res; }; // BBS: don't use lazy_raise when enable spiral vase - gcode += this->change_layer(print_z); // this will increase m_layer_index - m_layer = &layer; + gcode += this->change_layer(print_z); // this will increase m_layer_index + m_layer = &layer; m_object_layer_over_raft = false; - if(is_BBL_Printer()){ - if (printer_structure == PrinterStructure::psI3 && !need_insert_timelapse_gcode_for_traditional && !m_spiral_vase && print.config().print_sequence == PrintSequence::ByLayer) { + if (is_BBL_Printer()) { + if (printer_structure == PrinterStructure::psI3 && !need_insert_timelapse_gcode_for_traditional && !m_spiral_vase && + print.config().print_sequence == PrintSequence::ByLayer) { std::string timepals_gcode = insert_timelapse_gcode(); - if(!timepals_gcode.empty()){ - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //BBS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); - } + if (!timepals_gcode.empty()) { + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + // BBS: check whether custom gcode changes the z position. Update if changed + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } } } } else { @@ -3795,81 +3803,95 @@ LayerResult GCode::process_layer( "\n"; } } - if (! m_config.layer_change_gcode.value.empty()) { + if (!m_config.layer_change_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - gcode += this->placeholder_parser_process("layer_change_gcode", - print.config().layer_change_gcode.value, m_writer.extruder()->id(), &config) - + "\n"; + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += this->placeholder_parser_process("layer_change_gcode", print.config().layer_change_gcode.value, m_writer.extruder()->id(), + &config) + + "\n"; config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); } - //BBS: set layer time fan speed after layer change gcode + // BBS: set layer time fan speed after layer change gcode gcode += ";_SET_FAN_SPEED_CHANGING_LAYER\n"; - //Calibration Layer-specific GCode + // Calibration Layer-specific GCode switch (print.calib_mode()) { - case CalibMode::Calib_PA_Tower: { - gcode += writer().set_pressure_advance(print.calib_params().start + static_cast(print_z) * print.calib_params().step); - break; - } - case CalibMode::Calib_Temp_Tower: { - auto offset = static_cast(print_z / 10.001) * 5; - gcode += writer().set_temperature(print.calib_params().start - offset); - break; - } - case CalibMode::Calib_VFA_Tower: { - auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step; - m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); - break; - } - case CalibMode::Calib_Vol_speed_Tower: { - auto _speed = print.calib_params().start + print_z * print.calib_params().step; - m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); - break; - } - case CalibMode::Calib_Retraction_tower: { - auto _length = print.calib_params().start + std::floor(std::max(0.0,print_z-0.4)) * print.calib_params().step; - DynamicConfig _cfg; - _cfg.set_key_value("retraction_length", new ConfigOptionFloats{_length}); - writer().config.apply(_cfg); - sprintf(buf, "; Calib_Retraction_tower: Z_HEIGHT: %g, length:%g\n", print_z, _length); - gcode += buf; - break; - } - case CalibMode::Calib_Input_shaping_freq: { - if (m_layer_index == 1){ - gcode += writer().set_input_shaping('A', print.calib_params().start, 0.f); + case CalibMode::Calib_PA_Tower: { + gcode += writer().set_pressure_advance(print.calib_params().start + static_cast(print_z) * print.calib_params().step); + break; + } + case CalibMode::Calib_Temp_Tower: { + auto offset = static_cast(print_z / 10.001) * 5; + gcode += writer().set_temperature(print.calib_params().start - offset); + break; + } + case CalibMode::Calib_VFA_Tower: { + auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step; + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); + break; + } + case CalibMode::Calib_Vol_speed_Tower: { + auto _speed = print.calib_params().start + print_z * print.calib_params().step; + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); + break; + } + case CalibMode::Calib_Retraction_tower: { + auto _length = print.calib_params().start + std::floor(std::max(0.0, print_z - 0.4)) * print.calib_params().step; + DynamicConfig _cfg; + _cfg.set_key_value("retraction_length", new ConfigOptionFloats{_length}); + writer().config.apply(_cfg); + sprintf(buf, "; Calib_Retraction_tower: Z_HEIGHT: %g, length:%g\n", print_z, _length); + gcode += buf; + break; + } + case CalibMode::Calib_Input_shaping_freq: { + if (m_layer_index == 1) { + gcode += writer().set_input_shaping('A', print.calib_params().start, 0.f); + } else { + if (print.calib_params().freqStartX == print.calib_params().freqStartY && + print.calib_params().freqEndX == print.calib_params().freqEndY) { + gcode += writer().set_input_shaping('A', 0.f, + (print.calib_params().freqStartX) + + ((print.calib_params().freqEndX) - (print.calib_params().freqStartX)) * + (m_layer_index - 2) / (m_layer_count - 3)); } else { - if (print.calib_params().freqStartX == print.calib_params().freqStartY && print.calib_params().freqEndX == print.calib_params().freqEndY) { - gcode += writer().set_input_shaping('A', 0.f, (print.calib_params().freqStartX) + ((print.calib_params().freqEndX)-(print.calib_params().freqStartX)) * (m_layer_index - 2) / (m_layer_count - 3)); - } else { - gcode += writer().set_input_shaping('X', 0.f, (print.calib_params().freqStartX) + ((print.calib_params().freqEndX)-(print.calib_params().freqStartX)) * (m_layer_index - 2) / (m_layer_count - 3)); - gcode += writer().set_input_shaping('Y', 0.f, (print.calib_params().freqStartY) + ((print.calib_params().freqEndY)-(print.calib_params().freqStartY)) * (m_layer_index - 2) / (m_layer_count - 3)); - } + gcode += writer().set_input_shaping('X', 0.f, + (print.calib_params().freqStartX) + + ((print.calib_params().freqEndX) - (print.calib_params().freqStartX)) * + (m_layer_index - 2) / (m_layer_count - 3)); + gcode += writer().set_input_shaping('Y', 0.f, + (print.calib_params().freqStartY) + + ((print.calib_params().freqEndY) - (print.calib_params().freqStartY)) * + (m_layer_index - 2) / (m_layer_count - 3)); } - break; } - case CalibMode::Calib_Input_shaping_damp: { - if (m_layer_index == 1){ - gcode += writer().set_input_shaping('X', 0.f, print.calib_params().freqStartX); + break; + } + case CalibMode::Calib_Input_shaping_damp: { + if (m_layer_index == 1) { + gcode += writer().set_input_shaping('X', 0.f, print.calib_params().freqStartX); gcode += writer().set_input_shaping('Y', 0.f, print.calib_params().freqStartY); - } else { - gcode += writer().set_input_shaping('A', print.calib_params().start + ((print.calib_params().end)-(print.calib_params().start)) * (m_layer_index) / (m_layer_count), 0.f); - } - break; - } - case CalibMode::Calib_Junction_Deviation: { - gcode += writer().set_junction_deviation(print.calib_params().start + ((print.calib_params().end)-(print.calib_params().start)) * (m_layer_index) / (m_layer_count)); - break; + } else { + gcode += writer().set_input_shaping('A', + print.calib_params().start + ((print.calib_params().end) - (print.calib_params().start)) * + (m_layer_index) / (m_layer_count), + 0.f); } + break; + } + case CalibMode::Calib_Junction_Deviation: { + gcode += writer().set_junction_deviation(print.calib_params().start + ((print.calib_params().end) - (print.calib_params().start)) * + (m_layer_index) / (m_layer_count)); + break; + } } - //BBS + // BBS if (first_layer) { // Orca: we don't need to optimize the Klipper as only set once if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { - gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.initial_layer_acceleration.value + 0.5)); + gcode += m_writer.set_print_acceleration((unsigned int) floor(m_config.initial_layer_acceleration.value + 0.5)); } if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0) { @@ -3881,35 +3903,35 @@ LayerResult GCode::process_layer( } } - if (! first_layer && ! m_second_layer_things_done) { - if (print.is_BBL_printer()) { - // BBS: open powerlost recovery - { - gcode += "; open powerlost recovery\n"; - gcode += "M1003 S1\n"; + if (!first_layer && !m_second_layer_things_done) { + if (print.is_BBL_printer()) { + // BBS: open powerlost recovery + { + gcode += "; open powerlost recovery\n"; + gcode += "M1003 S1\n"; + } + // BBS: open first layer inspection at second layer + if (print.config().scan_first_layer.value) { + // BBS: retract first to avoid droping when scan model + gcode += this->retract(); + gcode += "M976 S1 P1 ; scan model before printing 2nd layer\n"; + gcode += "M400 P100\n"; + gcode += this->unretract(); + } } - // BBS: open first layer inspection at second layer - if (print.config().scan_first_layer.value) { - // BBS: retract first to avoid droping when scan model - gcode += this->retract(); - gcode += "M976 S1 P1 ; scan model before printing 2nd layer\n"; - gcode += "M400 P100\n"; - gcode += this->unretract(); + // Reset acceleration at sencond layer + // Orca: only set once, don't need to call set_accel_and_jerk + if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { + gcode += m_writer.set_print_acceleration((unsigned int) floor(m_config.default_acceleration.value + 0.5)); } - } - // Reset acceleration at sencond layer - // Orca: only set once, don't need to call set_accel_and_jerk - if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { - gcode += m_writer.set_print_acceleration((unsigned int) floor(m_config.default_acceleration.value + 0.5)); - } - if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0) { - gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); - } + if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0) { + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); + } // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent // nozzle_temperature_initial_layer vs. temperature settings. - for (const Extruder &extruder : m_writer.extruders()) { + for (const Extruder& extruder : m_writer.extruders()) { if ((print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) && extruder.id() != m_writer.extruder()->id()) // In single extruder multi material mode, set the temperature for the current extruder only. @@ -3928,39 +3950,40 @@ LayerResult GCode::process_layer( if (single_object_instance_idx == size_t(-1)) { // Normal (non-sequential) print. - gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); + gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, + print.config()); } // BBS: get next extruder according to flush and soluble - auto get_next_extruder = [&](int current_extruder,const std::vector&extruders) { + auto get_next_extruder = [&](int current_extruder, const std::vector& extruders) { std::vector flush_matrix(cast(m_config.flush_volumes_matrix.values)); - const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); + const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON); // Extract purging volumes for each extruder pair: std::vector> wipe_volumes; for (unsigned int i = 0; i < number_of_extruders; ++i) - wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); + wipe_volumes.push_back( + std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); unsigned int next_extruder = current_extruder; - float min_flush = std::numeric_limits::max(); + float min_flush = std::numeric_limits::max(); for (auto extruder_id : extruders) { if (print.config().filament_soluble.get_at(extruder_id) || extruder_id == current_extruder) continue; if (wipe_volumes[current_extruder][extruder_id] < min_flush) { next_extruder = extruder_id; - min_flush = wipe_volumes[current_extruder][extruder_id]; + min_flush = wipe_volumes[current_extruder][extruder_id]; } } return next_extruder; }; - - for (const auto &layer_to_print : layers) { + + for (const auto& layer_to_print : layers) { if (layer_to_print.object_layer) { - const auto& regions = layer_to_print.object_layer->regions(); + const auto& regions = layer_to_print.object_layer->regions(); const bool enable_overhang_speed = std::any_of(regions.begin(), regions.end(), [](const LayerRegion* r) { return r->has_extrusions() && r->region().config().enable_overhang_speed; }); if (enable_overhang_speed) { - m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.original_object, - layer_to_print.object_layer); + m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.original_object, layer_to_print.object_layer); } } } @@ -3968,22 +3991,22 @@ LayerResult GCode::process_layer( // Group extrusions by an extruder, then by an object, an island and a region. std::map> by_extruder; bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); - for (const LayerToPrint &layer_to_print : layers) { + for (const LayerToPrint& layer_to_print : layers) { if (layer_to_print.support_layer != nullptr) { - const SupportLayer &support_layer = *layer_to_print.support_layer; - const PrintObject& object = *layer_to_print.original_object; - if (! support_layer.support_fills.entities.empty()) { - ExtrusionRole role = support_layer.support_fills.role(); - bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition; - bool has_interface = role == erMixed || role == erSupportMaterialInterface; + const SupportLayer& support_layer = *layer_to_print.support_layer; + const PrintObject& object = *layer_to_print.original_object; + if (!support_layer.support_fills.entities.empty()) { + ExtrusionRole role = support_layer.support_fills.role(); + bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition; + bool has_interface = role == erMixed || role == erSupportMaterialInterface; // Extruder ID of the support base. -1 if "don't care". - unsigned int support_extruder = object.config().support_filament.value - 1; + unsigned int support_extruder = object.config().support_filament.value - 1; // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool support_dontcare = object.config().support_filament.value == 0; + bool support_dontcare = object.config().support_filament.value == 0; // Extruder ID of the support interface. -1 if "don't care". - unsigned int interface_extruder = object.config().support_interface_filament.value - 1; + unsigned int interface_extruder = object.config().support_interface_filament.value - 1; // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool interface_dontcare = object.config().support_interface_filament.value == 0; + bool interface_dontcare = object.config().support_interface_filament.value == 0; // BBS: apply wiping overridden extruders WipingExtrusions& wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); @@ -4010,26 +4033,25 @@ LayerResult GCode::process_layer( if (print.config().filament_soluble.get_at(extruder_id)) continue; - //BBS: now we don't consider interface filament used in other object + // BBS: now we don't consider interface filament used in other object if (extruder_id == interface_extruder) continue; dontcare_extruder = extruder_id; break; } - #if 0 +#if 0 //BBS: not found a suitable extruder in current layer ,dontcare_extruider==first_extruder_id==interface_extruder if (dontcare_extruder == interface_extruder && (object.config().support_interface_not_for_body && object.config().support_interface_filament.value!=0)) { // BBS : get a suitable extruder from other layer auto all_extruders = print.extruders(); dontcare_extruder = get_next_extruder(dontcare_extruder, all_extruders); } - #endif +#endif if (support_dontcare) support_extruder = dontcare_extruder; - } - else if (support_dontcare || interface_dontcare) { + } else if (support_dontcare || interface_dontcare) { // Some support will be printed with "don't care" material, preferably non-soluble. // Is the current extruder assigned a soluble filament? unsigned int dontcare_extruder = first_extruder_id; @@ -4037,7 +4059,7 @@ LayerResult GCode::process_layer( // The last extruder printed on the previous layer extrudes soluble filament. // Try to find a non-soluble extruder on the same layer. for (unsigned int extruder_id : layer_tools.extruders) - if (! print.config().filament_soluble.get_at(extruder_id)) { + if (!print.config().filament_soluble.get_at(extruder_id)) { dontcare_extruder = extruder_id; break; } @@ -4049,21 +4071,23 @@ LayerResult GCode::process_layer( } // Both the support and the support interface are printed with the same extruder, therefore // the interface may be interleaved with the support base. - bool single_extruder = ! has_support || support_extruder == interface_extruder; + bool single_extruder = !has_support || support_extruder == interface_extruder; // Assign an extruder to the base. - ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj.support = &support_layer.support_fills; + ObjectByExtruder& obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, + &layer_to_print - layers.data(), layers.size()); + obj.support = &support_layer.support_fills; obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial; - if (! single_extruder && has_interface) { - ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj_interface.support = &support_layer.support_fills; + if (!single_extruder && has_interface) { + ObjectByExtruder& obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), + layers.size()); + obj_interface.support = &support_layer.support_fills; obj_interface.support_extrusion_role = erSupportMaterialInterface; } } } if (layer_to_print.object_layer != nullptr) { - const Layer &layer = *layer_to_print.object_layer; + const Layer& layer = *layer_to_print.object_layer; // We now define a strategy for building perimeters and fills. The separation // between regions doesn't matter in terms of printing order, as we follow // another logic instead: @@ -4073,43 +4097,44 @@ LayerResult GCode::process_layer( // - for each island, we extrude perimeters first, unless user set the infill_first // option // (Still, we have to keep track of regions because we need to apply their config) - size_t n_slices = layer.lslices.size(); - const std::vector &layer_surface_bboxes = layer.lslices_bboxes; + size_t n_slices = layer.lslices.size(); + const std::vector& layer_surface_bboxes = layer.lslices_bboxes; // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. std::vector slices_test_order; slices_test_order.reserve(n_slices); - for (size_t i = 0; i < n_slices; ++ i) + for (size_t i = 0; i < n_slices; ++i) slices_test_order.emplace_back(i); std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) { const Vec2d s1 = layer_surface_bboxes[i].size().cast(); const Vec2d s2 = layer_surface_bboxes[j].size().cast(); return s1.x() * s1.y() < s2.x() * s2.y(); }); - auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { - const BoundingBox &bbox = layer_surface_bboxes[i]; - return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && - point(1) >= bbox.min(1) && point(1) < bbox.max(1) && + auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point& point) { + const BoundingBox& bbox = layer_surface_bboxes[i]; + return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && point(1) >= bbox.min(1) && point(1) < bbox.max(1) && layer.lslices[i].contour.contains(point); }; - for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { - const LayerRegion *layerm = layer.regions()[region_id]; + for (size_t region_id = 0; region_id < layer.regions().size(); ++region_id) { + const LayerRegion* layerm = layer.regions()[region_id]; if (layerm == nullptr) continue; // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id()); + const PrintRegion& region = print.get_print_region(layerm->region().print_region_id()); // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. // It is also necessary to save which extrusions are part of MM wiping and which are not. // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: std::vector printing_extruders; - for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { - for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) { + for (const ObjectByExtruder::Island::Region::Type entity_type : + {ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS}) { + for (const ExtrusionEntity* ee : + (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) { // extrusions represents infill or perimeter extrusions of a single island. assert(dynamic_cast(ee) != nullptr); - const auto *extrusions = static_cast(ee); + const auto* extrusions = static_cast(ee); if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail. continue; @@ -4117,48 +4142,51 @@ LayerResult GCode::process_layer( int correct_extruder_id = layer_tools.extruder(*extrusions, region); // Let's recover vector of extruder overrides: - const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; - if (! layer_tools.has_extruder(correct_extruder_id)) { + const WipingExtrusions::ExtruderPerCopy* entity_overrides = nullptr; + if (!layer_tools.has_extruder(correct_extruder_id)) { // this entity is not overridden, but its extruder is not in layer_tools - we'll print it - // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) + // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare + // extruders are eradicated from layer_tools) correct_extruder_id = layer_tools.extruders.back(); } printing_extruders.clear(); if (is_anything_overridden) { - entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size()); + entity_overrides = const_cast(layer_tools) + .wiping_extrusions() + .get_extruder_overrides(extrusions, layer_to_print.original_object, correct_extruder_id, + layer_to_print.object()->instances().size()); if (entity_overrides == nullptr) { printing_extruders.emplace_back(correct_extruder_id); } else { printing_extruders.reserve(entity_overrides->size()); for (int extruder : *entity_overrides) printing_extruders.emplace_back(extruder >= 0 ? - // at least one copy is overridden to use this extruder - extruder : - // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) - static_cast(- extruder - 1)); + // at least one copy is overridden to use this extruder + extruder : + // at least one copy would normally be printed with this extruder + // (see get_extruder_overrides function for explanation) + static_cast(-extruder - 1)); Slic3r::sort_remove_duplicates(printing_extruders); } } else printing_extruders.emplace_back(correct_extruder_id); // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: - for (unsigned int extruder : printing_extruders) - { - std::vector &islands = object_islands_by_extruder( - by_extruder, - extruder, - &layer_to_print - layers.data(), - layers.size(), n_slices+1); - for (size_t i = 0; i <= n_slices; ++ i) { - bool last = i == n_slices; + for (unsigned int extruder : printing_extruders) { + std::vector& islands = object_islands_by_extruder(by_extruder, extruder, + &layer_to_print - layers.data(), + layers.size(), n_slices + 1); + for (size_t i = 0; i <= n_slices; ++i) { + bool last = i == n_slices; size_t island_idx = last ? n_slices : slices_test_order[i]; - if (// extrusions->first_point does not fit inside any slice + if ( // extrusions->first_point does not fit inside any slice last || // extrusions->first_point fits inside ith slice point_inside_surface(island_idx, extrusions->first_point())) { if (islands[island_idx].by_region.empty()) islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, + entity_overrides); break; } } @@ -4173,8 +4201,7 @@ LayerResult GCode::process_layer( m_wipe_tower->set_is_first_print(true); // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. - for (unsigned int extruder_id : layer_tools.extruders) - { + for (unsigned int extruder_id : layer_tools.extruders) { if (print.config().skirt_type == stCombined && !print.skirt().empty()) gcode += generate_skirt(print, print.skirt(), Point(0, 0), layer.object()->config().skirt_start_angle, layer_tools, layer, extruder_id); @@ -4187,16 +4214,16 @@ LayerResult GCode::process_layer( m_writer.add_object_change_labels(gcode); std::string timepals_gcode = insert_timelapse_gcode(); - if(!timepals_gcode.empty()){ - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //BBS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); - } + if (!timepals_gcode.empty()) { + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + // BBS: check whether custom gcode changes the z position. Update if changed + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } } has_insert_timelapse_gcode = true; } @@ -4221,16 +4248,16 @@ LayerResult GCode::process_layer( // BBS: ordering instances by extruder std::vector instances_to_print; - bool has_prime_tower = print.config().enable_prime_tower - && print.extruders().size() > 1 - && ((print.config().print_sequence == PrintSequence::ByLayer && print.config().print_order == PrintOrder::Default) - || (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() == 1)); + bool has_prime_tower = print.config().enable_prime_tower && print.extruders().size() > 1 && + ((print.config().print_sequence == PrintSequence::ByLayer && + print.config().print_order == PrintOrder::Default) || + (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() == 1)); if (has_prime_tower) { - int plate_idx = print.get_plate_index(); + int plate_idx = print.get_plate_index(); Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx)); std::vector& objects_by_extruder = objects_by_extruder_it->second; - std::vector print_objects; + std::vector print_objects; for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) { auto& object_by_extruder = objects_by_extruder[obj_idx]; if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) @@ -4241,24 +4268,20 @@ LayerResult GCode::process_layer( std::vector new_ordering = chain_print_object_instances(print_objects, &wt_pos); std::reverse(new_ordering.begin(), new_ordering.end()); - instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, &new_ordering, single_object_instance_idx); - } - else { + instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, &new_ordering, + single_object_instance_idx); + } else { instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); } // BBS - if (print.config().skirt_type == stPerObject && - print.config().print_sequence == PrintSequence::ByObject && + if (print.config().skirt_type == stPerObject && print.config().print_sequence == PrintSequence::ByObject && !layer.object()->object_skirt().empty() && - ((layer.id() < print.config().skirt_height || print.config().draft_shield == DraftShield::dsEnabled)) - ) - { + ((layer.id() < print.config().skirt_height || print.config().draft_shield == DraftShield::dsEnabled))) { for (InstanceToPrint& instance_to_print : instances_to_print) { - if (instance_to_print.print_object.object_skirt().empty()) continue; - + if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities.size() > 0) continue; @@ -4270,64 +4293,62 @@ LayerResult GCode::process_layer( m_skirt_done.clear(); if (layer.id() == 1 && m_skirt_done.size() > 1) - m_skirt_done.erase(m_skirt_done.begin()+1,m_skirt_done.end()); + m_skirt_done.erase(m_skirt_done.begin() + 1, m_skirt_done.end()); const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; - gcode += generate_skirt(print, instance_to_print.print_object.object_skirt(), offset, instance_to_print.print_object.config().skirt_start_angle, layer_tools, layer, extruder_id); + gcode += generate_skirt(print, instance_to_print.print_object.object_skirt(), offset, + instance_to_print.print_object.config().skirt_start_angle, layer_tools, layer, extruder_id); } } - // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): + // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first + // (infill/perimeter wiping feature): std::vector by_region_per_copy_cache; - for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { + for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions >= 0; --print_wipe_extrusions) { if (is_anything_overridden && print_wipe_extrusions == 0) - gcode+="; PURGING FINISHED\n"; + gcode += "; PURGING FINISHED\n"; - for (InstanceToPrint &instance_to_print : instances_to_print) { - if (print.config().skirt_type == stPerObject && - !instance_to_print.print_object.object_skirt().empty() && - print.config().print_sequence == PrintSequence::ByLayer - && - (layer.id() < print.config().skirt_height || print.config().draft_shield == DraftShield::dsEnabled)) - { + for (InstanceToPrint& instance_to_print : instances_to_print) { + if (print.config().skirt_type == stPerObject && !instance_to_print.print_object.object_skirt().empty() && + print.config().print_sequence == PrintSequence::ByLayer && + (layer.id() < print.config().skirt_height || print.config().draft_shield == DraftShield::dsEnabled)) { if (first_layer) m_skirt_done.clear(); const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; - gcode += generate_skirt(print, instance_to_print.print_object.object_skirt(), offset, instance_to_print.print_object.config().skirt_start_angle, layer_tools, layer, extruder_id); + gcode += generate_skirt(print, instance_to_print.print_object.object_skirt(), offset, + instance_to_print.print_object.config().skirt_start_angle, layer_tools, layer, extruder_id); if (instances_to_print.size() > 1 && &instance_to_print != &*(instances_to_print.end() - 1)) m_skirt_done.pop_back(); } - - const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id]; - const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; + + const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id]; + const LayerToPrint& layer_to_print = layers[instance_to_print.layer_id]; // To control print speed of the 1st object layer printed over raft interface. bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && - instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); + instance_to_print.print_object.slicing_parameters().raft_layers() == + layer_to_print.object_layer->id(); m_config.apply(instance_to_print.print_object.config(), true); - m_layer = layer_to_print.layer(); + m_layer = layer_to_print.layer(); m_object_layer_over_raft = object_layer_over_raft; if (m_config.reduce_crossing_wall) m_avoid_crossing_perimeters.init_layer(*m_layer); if (this->config().gcode_label_objects) { gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + - " id:" + std::to_string(instance_to_print.print_object.get_id()) + " copy " + - std::to_string(inst.id) + "\n"; + " id:" + std::to_string(instance_to_print.print_object.get_id()) + " copy " + std::to_string(inst.id) + "\n"; } // exclude objects if (m_enable_exclude_object) { if (is_BBL_Printer()) { - m_writer.set_object_start_str( - std::string("; start printing object, unique label id: ") + - std::to_string(instance_to_print.label_object_id) + "\n" + "M624 " + - _encode_label_ids_to_base64({instance_to_print.label_object_id}) + "\n"); + m_writer.set_object_start_str(std::string("; start printing object, unique label id: ") + + std::to_string(instance_to_print.label_object_id) + "\n" + "M624 " + + _encode_label_ids_to_base64({instance_to_print.label_object_id}) + "\n"); } else { const auto gflavor = print.config().gcode_flavor.value; if (gflavor == gcfKlipper) { m_writer.set_object_start_str(std::string("EXCLUDE_OBJECT_START NAME=") + get_instance_name(&instance_to_print.print_object, inst.id) + "\n"); - } - else if (gflavor == gcfMarlinLegacy || gflavor == gcfMarlinFirmware || gflavor == gcfRepRapFirmware) { + } else if (gflavor == gcfMarlinLegacy || gflavor == gcfMarlinFirmware || gflavor == gcfRepRapFirmware) { std::string str = std::string("M486 S") + std::to_string(inst.unique_id) + "\n"; m_writer.set_object_start_str(str); } @@ -4341,18 +4362,19 @@ LayerResult GCode::process_layer( m_extrusion_quality_estimator.set_current_object(&instance_to_print.print_object); // When starting a new object, use the external motion planner for the first travel move. - const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; + const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; std::pair this_object_copy(&instance_to_print.print_object, offset); if (m_last_obj_copy != this_object_copy) m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); if (instance_to_print.object_by_extruder.support != nullptr) { - m_layer = layers[instance_to_print.layer_id].support_layer; + m_layer = layers[instance_to_print.layer_id].support_layer; m_object_layer_over_raft = false; - //BBS: print supports' brims first - if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && !print_wipe_extrusions) { + // BBS: print supports' brims first + if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && + !print_wipe_extrusions) { this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); for (const ExtrusionEntity* ee : print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities) { @@ -4373,15 +4395,17 @@ LayerResult GCode::process_layer( ExtrusionEntityCollection support_eec; // BBS - WipingExtrusions& wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); - bool support_overridden = wiping_extrusions.is_support_overridden(layer_to_print.original_object); + WipingExtrusions& wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); + bool support_overridden = wiping_extrusions.is_support_overridden(layer_to_print.original_object); bool support_intf_overridden = wiping_extrusions.is_support_interface_overridden(layer_to_print.original_object); ExtrusionRole support_extrusion_role = instance_to_print.object_by_extruder.support_extrusion_role; - bool is_overridden = support_extrusion_role == erSupportMaterialInterface ? support_intf_overridden : support_overridden; + bool is_overridden = support_extrusion_role == erSupportMaterialInterface ? support_intf_overridden : + support_overridden; if (is_overridden == (print_wipe_extrusions != 0)) { gcode += this->extrude_support( - // support_extrusion_role is erSupportMaterial, erSupportTransition, erSupportMaterialInterface or erMixed for all extrusion paths. + // support_extrusion_role is erSupportMaterial, erSupportTransition, erSupportMaterialInterface or erMixed for + // all extrusion paths. *instance_to_print.object_by_extruder.support, support_extrusion_role); // Make sure ironing is the last @@ -4390,16 +4414,21 @@ LayerResult GCode::process_layer( } } - m_layer = layer_to_print.layer(); + m_layer = layer_to_print.layer(); m_object_layer_over_raft = object_layer_over_raft; } - //FIXME order islands? - // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) - for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { - const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; - //BBS: add brim by obj by extruder + // FIXME order islands? + // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) + for (ObjectByExtruder::Island& island : instance_to_print.object_by_extruder.islands) { + const auto& by_region_specific = is_anything_overridden ? + island.by_region_per_copy(by_region_per_copy_cache, + static_cast(instance_to_print.instance_id), + extruder_id, print_wipe_extrusions != 0) : + island.by_region; + // BBS: add brim by obj by extruder if (first_layer) { - if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && !print_wipe_extrusions) { + if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && + !print_wipe_extrusions) { this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); for (const ExtrusionEntity* ee : print.m_brimMap.at(instance_to_print.print_object.id()).entities) { @@ -4418,9 +4447,9 @@ LayerResult GCode::process_layer( m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); - //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. + // FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. - auto has_infill = [](const std::vector &by_region) { + auto has_infill = [](const std::vector& by_region) { for (auto region : by_region) { if (!region.infills.empty()) return true; @@ -4430,20 +4459,21 @@ LayerResult GCode::process_layer( { // Print perimeters of regions that has is_infill_first == false gcode += this->extrude_perimeters(print, by_region_specific, first_layer, false); - if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { + if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode && + has_infill(by_region_specific)) { gcode += this->retract(false, false, LiftType::NormalLift); std::string timepals_gcode = insert_timelapse_gcode(); - if(!timepals_gcode.empty()){ - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //BBS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); - } + if (!timepals_gcode.empty()) { + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + // BBS: check whether custom gcode changes the z position. Update if changed + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } } has_insert_timelapse_gcode = true; } @@ -4453,14 +4483,12 @@ LayerResult GCode::process_layer( gcode += this->extrude_perimeters(print, by_region_specific, first_layer, true); } // ironing - gcode += this->extrude_infill(print,by_region_specific, true); + gcode += this->extrude_infill(print, by_region_specific, true); } if (this->config().gcode_label_objects) { - gcode += std::string("; stop printing object ") + - instance_to_print.print_object.model_object()->name + - " id:" + std::to_string(instance_to_print.print_object.get_id()) + " copy " + - std::to_string(inst.id) + "\n"; + gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + + " id:" + std::to_string(instance_to_print.print_object.get_id()) + " copy " + std::to_string(inst.id) + "\n"; } // exclude objects // Don't set m_gcode_label_objects_end if you don't had to write the m_gcode_label_objects_start. @@ -4469,8 +4497,7 @@ LayerResult GCode::process_layer( } else if (m_enable_exclude_object) { if (is_BBL_Printer()) { m_writer.set_object_end_str(std::string("; stop printing object, unique label id: ") + - std::to_string(instance_to_print.label_object_id) + "\n" + - "M625\n"); + std::to_string(instance_to_print.label_object_id) + "\n" + "M625\n"); } else { const auto gflavor = print.config().gcode_flavor.value; if (gflavor == gcfKlipper) { @@ -4509,8 +4536,7 @@ LayerResult GCode::process_layer( file.write(gcode); #endif - BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << - log_memory_info(); + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { if (m_support_traditional_timelapse) @@ -4520,29 +4546,29 @@ LayerResult GCode::process_layer( m_writer.add_object_change_labels(gcode); std::string timepals_gcode = insert_timelapse_gcode(); - if(!timepals_gcode.empty()){ - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //BBS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); - } + if (!timepals_gcode.empty()) { + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + // BBS: check whether custom gcode changes the z position. Update if changed + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } } } - result.gcode = std::move(gcode); + result.gcode = std::move(gcode); result.cooling_buffer_flush = object_layer || raft_layer || last_layer; return result; } -void GCode::apply_print_config(const PrintConfig &print_config) +void GCode::apply_print_config(const PrintConfig& print_config) { m_writer.apply_print_config(print_config); m_config.apply(print_config); - m_scaled_resolution = scaled(print_config.resolution.value); + m_scaled_resolution = scaled(print_config.resolution.value); m_enable_exclude_object = m_config.exclude_object; #if ORCA_CHECK_GCODE_PLACEHOLDERS @@ -4562,10 +4588,7 @@ void GCode::apply_print_config(const PrintConfig &print_config) if (opt->empty()) opt->set(new ConfigOptionString(";VALUE FOR TESTING")); } - for (auto opt : std::initializer_list{ - &m_config.filament_start_gcode, - &m_config.filament_end_gcode - }) { + for (auto opt : std::initializer_list{&m_config.filament_start_gcode, &m_config.filament_end_gcode}) { if (opt->empty()) for (int i = 0; i < opt->size(); ++i) opt->set_at(new ConfigOptionString(";VALUE FOR TESTING"), i, 0); @@ -4573,31 +4596,22 @@ void GCode::apply_print_config(const PrintConfig &print_config) #endif } -void GCode::append_full_config(const Print &print, std::string &str) +void GCode::append_full_config(const Print& print, std::string& str) { - const DynamicPrintConfig &cfg = print.full_print_config(); + const DynamicPrintConfig& cfg = print.full_print_config(); // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. - static const std::set banned_keys( { - "compatible_printers"sv, - "compatible_prints"sv, - "print_host"sv, - "print_host_webui"sv, - "printhost_apikey"sv, - "printhost_cafile"sv, - "printhost_user"sv, - "printhost_password"sv, - "printhost_port"sv - }); - auto is_banned = [](const std::string &key) { - return banned_keys.find(key) != banned_keys.end(); - }; - std::ostringstream ss; + static const std::set banned_keys({"compatible_printers"sv, "compatible_prints"sv, "print_host"sv, + "print_host_webui"sv, "printhost_apikey"sv, "printhost_cafile"sv, + "printhost_user"sv, "printhost_password"sv, "printhost_port"sv}); + auto is_banned = [](const std::string& key) { return banned_keys.find(key) != banned_keys.end(); }; + std::ostringstream ss; for (const std::string& key : cfg.keys()) { if (!is_banned(key) && !cfg.option(key)->is_nil()) { if (key == "wipe_tower_x" || key == "wipe_tower_y") { - ss << std::fixed << std::setprecision(3) << "; " << key << " = " << dynamic_cast(cfg.option(key))->get_at(print.get_plate_index()) << "\n"; + ss << std::fixed << std::setprecision(3) << "; " << key << " = " + << dynamic_cast(cfg.option(key))->get_at(print.get_plate_index()) << "\n"; } - if(key == "extruder_colour") + if (key == "extruder_colour") ss << "; " << key << " = " << cfg.opt_serialize("filament_colour") << "\n"; else ss << "; " << key << " = " << cfg.opt_serialize(key) << "\n"; @@ -4606,7 +4620,7 @@ void GCode::append_full_config(const Print &print, std::string &str) str += ss.str(); } -void GCode::set_extruders(const std::vector &extruder_ids) +void GCode::set_extruders(const std::vector& extruder_ids) { m_writer.set_extruders(extruder_ids); @@ -4619,13 +4633,10 @@ void GCode::set_extruders(const std::vector &extruder_ids) } } -void GCode::set_origin(const Vec2d &pointf) +void GCode::set_origin(const Vec2d& pointf) { // if origin increases (goes towards right), last_pos decreases because it goes towards left - const Point translate( - scale_(m_origin(0) - pointf(0)), - scale_(m_origin(1) - pointf(1)) - ); + const Point translate(scale_(m_origin(0) - pointf(0)), scale_(m_origin(1) - pointf(1))); m_last_pos += translate; m_wipe.path.translate(translate); m_origin = pointf; @@ -4650,19 +4661,19 @@ std::string GCode::change_layer(coordf_t print_z) std::string gcode; if (m_layer_count > 0) // Increment a progress bar indicator. - gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); - //BBS - coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates + gcode += m_writer.update_progress(++m_layer_index, m_layer_count); + // BBS + coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates if (EXTRUDER_CONFIG(retract_when_changing_layer) && m_writer.will_move_z(z)) { LiftType lift_type = this->to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); - //BBS: force to use SpiralLift when change layer if lift type is auto + // BBS: force to use SpiralLift when change layer if lift type is auto gcode += this->retract(false, false, ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto ? LiftType::SpiralLift : lift_type); } m_writer.add_object_change_labels(gcode); if (m_spiral_vase) { - //BBS: force to normal lift immediately in spiral vase mode + // BBS: force to normal lift immediately in spiral vase mode std::ostringstream comment; comment << "move to next layer (" << m_layer_index << ")"; gcode += m_writer.travel_to_z(z, comment.str()); @@ -4670,12 +4681,12 @@ std::string GCode::change_layer(coordf_t print_z) m_need_change_layer_lift_z = true; - m_nominal_z = z; + m_nominal_z = z; m_writer.get_position().z() = z; // forget last wiping path as wiping after raising Z is pointless // BBS. Dont forget wiping path to reduce stringing. - //m_wipe.reset_path(); + // m_wipe.reset_path(); return gcode; } @@ -4702,9 +4713,9 @@ static std::unique_ptr calculate_layer_edge_grid(const Layer& la return out; } -std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters, const Point* start_point) +std::string GCode::extrude_loop( + ExtrusionLoop loop, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters, const Point* start_point) { - // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation @@ -4714,13 +4725,13 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // if spiral vase, we have to ensure that all contour are in the same orientation. loop.make_counter_clockwise(); } - //if (loop.loop_role() == elrSkirt && (this->m_layer->id() % 2 == 1)) - // loop.reverse(); + // if (loop.loop_role() == elrSkirt && (this->m_layer->id() % 2 == 1)) + // loop.reverse(); // find the point of the loop that is closest to the current extruder position // or randomize if requested; // or, if `start_point` is specified, start the loop at point closest to it - Point last_pos = start_point ? *start_point : this->last_pos(); + Point last_pos = start_point ? *start_point : this->last_pos(); float seam_overhang = std::numeric_limits::lowest(); if (!m_config.spiral_mode && description == "perimeter") { assert(m_layer != nullptr); @@ -4728,12 +4739,12 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou } else loop.split_at(last_pos, false); - const auto seam_scarf_type = m_config.seam_slope_type.value; - bool enable_seam_slope = ((seam_scarf_type == SeamScarfType::External && !is_hole) || seam_scarf_type == SeamScarfType::All) && - !m_config.spiral_mode && - (loop.role() == erExternalPerimeter || (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && - layer_id() > 0; - const auto nozzle_diameter = PHYSICAL_EXTRUDER_CONFIG(nozzle_diameter); + const auto seam_scarf_type = m_config.seam_slope_type.value; + bool enable_seam_slope = ((seam_scarf_type == SeamScarfType::External && !is_hole) || seam_scarf_type == SeamScarfType::All) && + !m_config.spiral_mode && + (loop.role() == erExternalPerimeter || (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && + layer_id() > 0; + const auto nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); if (enable_seam_slope && m_config.seam_slope_conditional.value) { enable_seam_slope = loop.is_smooth(m_config.scarf_angle_threshold.value * M_PI / 180., nozzle_diameter); } @@ -4747,18 +4758,19 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so // we discard it in that case - const double seam_gap = scale_(m_config.seam_gap.get_abs_value(nozzle_diameter)); + const double seam_gap = scale_(m_config.seam_gap.get_abs_value(nozzle_diameter)); const double clip_length = m_enable_loop_clipping && !enable_seam_slope ? seam_gap : 0; // get paths ExtrusionPaths paths; loop.clip_end(clip_length, &paths); - if (paths.empty()) return ""; + if (paths.empty()) + return ""; - // SoftFever: check loop lenght for small perimeter. + // SoftFever: check loop lenght for small perimeter. double small_peri_speed = -1; if (speed == -1 && loop.length() <= SMALL_PERIMETER_LENGTH(m_config.small_perimeter_threshold.value)) { - if(m_config.small_perimeter_speed == 0) + if (m_config.small_perimeter_speed == 0) small_peri_speed = m_config.outer_wall_speed * 0.5; else small_peri_speed = m_config.small_perimeter_speed.get_abs_value(m_config.outer_wall_speed); @@ -4766,29 +4778,30 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // extrude along the path std::string gcode; - + // Orca: // Port of "wipe inside before extruding an external perimeter" feature from super slicer // If region perimeters size not greater than or equal to 2, then skip the wipe inside move as we will extrude in mid air // as no neighbouring perimeter exists. If an internal perimeter exists, we should find 2 perimeters touching the de-retraction point // 1 - the currently printed external perimeter and 2 - the neighbouring internal perimeter. - if (m_config.wipe_before_external_loop.value && !paths.empty() && paths.front().size() > 1 && paths.back().size() > 1 && paths.front().role() == erExternalPerimeter && region_perimeters.size() > 1) { - const bool is_full_loop_ccw = loop.polygon().is_counter_clockwise(); - bool is_hole_loop = (loop.loop_role() & ExtrusionLoopRole::elrHole) != 0; // loop.make_counter_clockwise(); - const double nozzle_diam = nozzle_diameter; + if (m_config.wipe_before_external_loop.value && !paths.empty() && paths.front().size() > 1 && paths.back().size() > 1 && + paths.front().role() == erExternalPerimeter && region_perimeters.size() > 1) { + const bool is_full_loop_ccw = loop.polygon().is_counter_clockwise(); + bool is_hole_loop = (loop.loop_role() & ExtrusionLoopRole::elrHole) != 0; // loop.make_counter_clockwise(); + const double nozzle_diam = nozzle_diameter; // note: previous & next are inverted to extrude "in the opposite direction, and we are "rewinding" Point previous_point = paths.front().polyline.points[1]; - Point current_point = paths.front().polyline.points.front(); - Point next_point = paths.back().polyline.points.back(); + Point current_point = paths.front().polyline.points.front(); + Point next_point = paths.back().polyline.points.back(); // can happen if seam_gap is null if (next_point == current_point) { next_point = paths.back().polyline.points[paths.back().polyline.points.size() - 2]; } - Point a = next_point; // second point - Point b = previous_point; // second to last point + Point a = next_point; // second point + Point b = previous_point; // second to last point if ((is_hole_loop ? !is_full_loop_ccw : is_full_loop_ccw)) { // swap points std::swap(a, b); @@ -4797,12 +4810,13 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou double angle = current_point.ccw_angle(a, b) / 3; // turn outwards if contour, turn inwwards if hole - if (is_hole_loop ? !is_full_loop_ccw : is_full_loop_ccw) angle *= -1; + if (is_hole_loop ? !is_full_loop_ccw : is_full_loop_ccw) + angle *= -1; - Vec2d current_pos = current_point.cast(); - Vec2d next_pos = next_point.cast(); - Vec2d vec_dist = next_pos - current_pos; - double vec_norm = vec_dist.norm(); + Vec2d current_pos = current_point.cast(); + Vec2d next_pos = next_point.cast(); + Vec2d vec_dist = next_pos - current_pos; + double vec_norm = vec_dist.norm(); // Offset distance is the minimum between half the nozzle diameter or half the line width for the upcomming perimeter // This is to mimimize potential instances where the de-retraction is performed on top of a neighbouring // thin perimeter due to arachne reducing line width. @@ -4813,28 +4827,29 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou pt.rotate(angle, current_point); pt = (current_pos + vec_dist * (2 * dist / vec_norm)).cast(); pt.rotate(angle, current_point); - + // Search region perimeters for lines that are touching the de-retraction point. // If an internal perimeter exists, we should find 2 perimeters touching the de-retraction point // 1: the currently printed external perimeter and 2: the neighbouring internal perimeter. int discoveredTouchingLines = 0; - for (const ExtrusionEntity* ee : region_perimeters){ - auto potential_touching_line = ee->as_polyline(); + for (const ExtrusionEntity* ee : region_perimeters) { + auto potential_touching_line = ee->as_polyline(); AABBTreeLines::LinesDistancer potential_touching_line_distancer{potential_touching_line.lines()}; auto touching_line = potential_touching_line_distancer.all_lines_in_radius(pt, scale_(nozzle_diam)); - if(touching_line.size()){ - discoveredTouchingLines ++; - if(discoveredTouchingLines > 1) break; // found 2 touching lines. End the search early. + if (touching_line.size()) { + discoveredTouchingLines++; + if (discoveredTouchingLines > 1) + break; // found 2 touching lines. End the search early. } } // found 2 perimeters touching the de-retraction point. Its safe to deretract as the point will be // inside the model - if(discoveredTouchingLines > 1){ + if (discoveredTouchingLines > 1) { // use extrude instead of travel_to_xy to trigger the unretract ExtrusionPath fake_path_wipe(Polyline{pt, current_point}, paths.front()); fake_path_wipe.set_force_no_extrusion(true); fake_path_wipe.mm3_per_mm = 0; - //fake_path_wipe.set_extrusion_role(erExternalPerimeter); + // fake_path_wipe.set_extrusion_role(erExternalPerimeter); gcode += extrude_path(fake_path_wipe, "move inwards before retraction/seam", speed); } } @@ -4845,16 +4860,15 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou return is_small_peri ? small_peri_speed : speed; }; - - //Orca: Adaptive PA: calculate average mm3_per_mm value over the length of the loop. - //This is used for adaptive PA - m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the loop + // Orca: Adaptive PA: calculate average mm3_per_mm value over the length of the loop. + // This is used for adaptive PA + m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the loop m_multi_flow_segment_path_average_mm3_per_mm = 0; - double weighted_sum_mm3_per_mm = 0.0; - double total_multipath_length = 0.0; + double weighted_sum_mm3_per_mm = 0.0; + double total_multipath_length = 0.0; for (const ExtrusionPath& path : paths) { - if(!path.is_force_no_extrusion()){ - double path_length = unscale(path.length()); //path length in mm + if (!path.is_force_no_extrusion()) { + double path_length = unscale(path.length()); // path length in mm weighted_sum_mm3_per_mm += path.mm3_per_mm * path_length; total_multipath_length += path_length; } @@ -4862,7 +4876,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (total_multipath_length > 0.0) m_multi_flow_segment_path_average_mm3_per_mm = weighted_sum_mm3_per_mm / total_multipath_length; // Orca: end of multipath average mm3_per_mm value calculation - + if (!enable_seam_slope) { for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { gcode += this->_extrude(*path, description, speed_for_path(*path)); @@ -4879,20 +4893,20 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou start_slope_ratio = m_config.seam_slope_start_height.value / 100.; } else { // Get the ratio against current layer height - double h = paths.front().height; + double h = paths.front().height; start_slope_ratio = m_config.seam_slope_start_height.value / h; } if (start_slope_ratio >= 1) start_slope_ratio = 0.99; double loop_length = 0.; - for (const auto & path : paths) { + for (const auto& path : paths) { loop_length += unscale_(path.length()); } - const bool slope_entire_loop = m_config.seam_slope_entire_loop; - const double slope_min_length = slope_entire_loop ? loop_length : std::min(m_config.seam_slope_min_length.value, loop_length); - const int slope_steps = m_config.seam_slope_steps; + const bool slope_entire_loop = m_config.seam_slope_entire_loop; + const double slope_min_length = slope_entire_loop ? loop_length : std::min(m_config.seam_slope_min_length.value, loop_length); + const int slope_steps = m_config.seam_slope_steps; const double slope_max_segment_length = scale_(slope_min_length / slope_steps); // Calculate the sloped loop @@ -4921,33 +4935,36 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // BBS if (m_wipe.enable) { m_wipe.path = Polyline(); - for (ExtrusionPath &path : paths) { - //BBS: Don't need to save duplicated point into wipe path - if (!m_wipe.path.empty() && !path.empty() && - m_wipe.path.last_point() == path.first_point()) + for (ExtrusionPath& path : paths) { + // BBS: Don't need to save duplicated point into wipe path + if (!m_wipe.path.empty() && !path.empty() && m_wipe.path.last_point() == path.first_point()) m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); else - m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path + m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path } } // make a little move inwards before leaving loop - if (m_config.wipe_on_loops.value && paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.wall_loops.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { + if (m_config.wipe_on_loops.value && paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.wall_loops.value > 1 && + paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { // detect angle between last and first segment // the side depends on the original winding order of the polygon (inwards for contours, outwards for holes) - //FIXME improve the algorithm in case the loop is tiny. - //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). - Point a = paths.front().polyline.points[1]; // second point - Point b = *(paths.back().polyline.points.end()-3); // second to last point + // FIXME improve the algorithm in case the loop is tiny. + // FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). + Point a = paths.front().polyline.points[1]; // second point + Point b = *(paths.back().polyline.points.end() - 3); // second to last point if (is_hole == loop.is_counter_clockwise()) { // swap points - Point c = a; a = b; b = c; + Point c = a; + a = b; + b = c; } double angle = paths.front().first_point().ccw_angle(a, b) / 3; // turn inwards if contour, turn outwards if hole - if (is_hole == loop.is_counter_clockwise()) angle *= -1; + if (is_hole == loop.is_counter_clockwise()) + angle *= -1; // create the destination point along the first segment and rotate it // we make sure we don't exceed the segment length because we don't know @@ -4955,19 +4972,19 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou Vec2d p1 = paths.front().polyline.points.front().cast(); Vec2d p2 = paths.front().polyline.points[1].cast(); Vec2d v = p2 - p1; - double nd = scale_(PHYSICAL_EXTRUDER_CONFIG(nozzle_diameter)); + double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter)); double l2 = v.squaredNorm(); // Shift by no more than a nozzle diameter. - //FIXME Hiding the seams will not work nicely for very densely discretized contours! - //BBS. shorten the travel distant before the wipe path + // FIXME Hiding the seams will not work nicely for very densely discretized contours! + // BBS. shorten the travel distant before the wipe path double threshold = 0.2; - Point pt = (p1 + v * threshold).cast(); + Point pt = (p1 + v * threshold).cast(); if (nd * nd < l2) pt = (p1 + threshold * v * (nd / sqrt(l2))).cast(); - //Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast(); + // Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast(); pt.rotate(angle, paths.front().polyline.points.front()); // generate the travel move - gcode += m_writer.extrude_to_xy(this->point_to_gcode(pt), 0,"move inwards before travel",true); + gcode += m_writer.extrude_to_xy(this->point_to_gcode(pt), 0, "move inwards before travel", true); } return gcode; @@ -4977,16 +4994,16 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string { // extrude along the path std::string gcode; - - //Orca: calculate multipath average mm3_per_mm value over the length of the path. - //This is used for adaptive PA - m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the multi-path + + // Orca: calculate multipath average mm3_per_mm value over the length of the path. + // This is used for adaptive PA + m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the multi-path m_multi_flow_segment_path_average_mm3_per_mm = 0; - double weighted_sum_mm3_per_mm = 0.0; - double total_multipath_length = 0.0; + double weighted_sum_mm3_per_mm = 0.0; + double total_multipath_length = 0.0; for (const ExtrusionPath& path : multipath.paths) { - if(!path.is_force_no_extrusion()){ - double path_length = unscale(path.length()); //path length in mm + if (!path.is_force_no_extrusion()) { + double path_length = unscale(path.length()); // path length in mm weighted_sum_mm3_per_mm += path.mm3_per_mm * path_length; total_multipath_length += path_length; } @@ -4994,8 +5011,8 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string if (total_multipath_length > 0.0) m_multi_flow_segment_path_average_mm3_per_mm = weighted_sum_mm3_per_mm / total_multipath_length; // Orca: end of multipath average mm3_per_mm value calculation - - for (ExtrusionPath path : multipath.paths){ + + for (ExtrusionPath path : multipath.paths) { gcode += this->_extrude(path, description, speed); // Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed // as we have already set the PA value to the average flow over the totality of the path @@ -5006,10 +5023,9 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string // BBS if (m_wipe.enable) { m_wipe.path = Polyline(); - for (ExtrusionPath &path : multipath.paths) { - //BBS: Don't need to save duplicated point into wipe path - if (!m_wipe.path.empty() && !path.empty() && - m_wipe.path.last_point() == path.first_point()) + for (ExtrusionPath& path : multipath.paths) { + // BBS: Don't need to save duplicated point into wipe path + if (!m_wipe.path.empty() && !path.empty() && m_wipe.path.last_point() == path.first_point()) m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); else m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path @@ -5020,7 +5036,10 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string return gcode; } -std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters) +std::string GCode::extrude_entity(const ExtrusionEntity& entity, + std::string description, + double speed, + const ExtrusionEntitiesPtr& region_perimeters) { if (const ExtrusionPath* path = dynamic_cast(&entity)) return this->extrude_path(*path, description, speed); @@ -5036,7 +5055,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) { // Orca: Reset average multipath flow as this is a single line, single extrude volumetric speed path - m_multi_flow_segment_path_pa_set = false; + m_multi_flow_segment_path_pa_set = false; m_multi_flow_segment_path_average_mm3_per_mm = 0; // description += ExtrusionEntity::role_to_string(path.role()); std::string gcode = this->_extrude(path, description, speed); @@ -5049,17 +5068,20 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string description, dou } // Extrude perimeters: Decide where to put seams (hide or align seams). -std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region, bool is_first_layer, bool is_infill_first) +std::string GCode::extrude_perimeters(const Print& print, + const std::vector& by_region, + bool is_first_layer, + bool is_infill_first) { std::string gcode; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.perimeters.empty()) { + for (const ObjectByExtruder::Island::Region& region : by_region) + if (!region.perimeters.empty()) { m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); // BBS: for first layer, we always print wall firstly to get better bed adhesive force // This behaviour is same with cura - const bool should_print = is_first_layer ? !is_infill_first - : (m_config.is_infill_first == is_infill_first); - if (!should_print) continue; + const bool should_print = is_first_layer ? !is_infill_first : (m_config.is_infill_first == is_infill_first); + if (!should_print) + continue; for (const ExtrusionEntity* ee : region.perimeters) gcode += this->extrude_entity(*ee, "perimeter", -1., region.perimeters); @@ -5068,25 +5090,25 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region, bool ironing) +std::string GCode::extrude_infill(const Print& print, const std::vector& by_region, bool ironing) { - std::string gcode; + std::string gcode; ExtrusionEntitiesPtr extrusions; const char* extrusion_name = ironing ? "ironing" : "infill"; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.infills.empty()) { + for (const ObjectByExtruder::Island::Region& region : by_region) + if (!region.infills.empty()) { extrusions.clear(); extrusions.reserve(region.infills.size()); - for (ExtrusionEntity *ee : region.infills) + for (ExtrusionEntity* ee : region.infills) if ((ee->role() == erIroning) == ironing) extrusions.emplace_back(ee); - if (! extrusions.empty()) { + if (!extrusions.empty()) { m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); - for (const ExtrusionEntity *fill : extrusions) { - auto *eec = dynamic_cast(fill); + for (const ExtrusionEntity* fill : extrusions) { + auto* eec = dynamic_cast(fill); if (eec) { - for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + for (ExtrusionEntity* ee : eec->chained_path_from(m_last_pos).entities) gcode += this->extrude_entity(*ee, extrusion_name); } else gcode += this->extrude_entity(*fill, extrusion_name); @@ -5096,16 +5118,15 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorrole(); assert(role == erSupportMaterial || role == erSupportMaterialInterface || role == erSupportTransition || role == erIroning); - const char* label = (role == erSupportMaterial) ? support_label : - ((role == erSupportMaterialInterface) ? support_interface_label : - ((role == erIroning) ? support_ironing_label : support_transition_label)); + const char* label = (role == erSupportMaterial) ? + support_label : + ((role == erSupportMaterialInterface) ? + support_interface_label : + ((role == erIroning) ? support_ironing_label : support_transition_label)); // BBS - //const double speed = (role == erSupportMaterial) ? support_speed : support_interface_speed; - const double speed = -1.0; - const ExtrusionPath* path = dynamic_cast(ee); - const ExtrusionMultiPath* multipath = dynamic_cast(ee); - const ExtrusionLoop* loop = dynamic_cast(ee); + // const double speed = (role == erSupportMaterial) ? support_speed : support_interface_speed; + const double speed = -1.0; + const ExtrusionPath* path = dynamic_cast(ee); + const ExtrusionMultiPath* multipath = dynamic_cast(ee); + const ExtrusionLoop* loop = dynamic_cast(ee); const ExtrusionEntityCollection* collection = dynamic_cast(ee); if (path) gcode += this->extrude_path(*path, label, speed); else if (multipath) { gcode += this->extrude_multi_path(*multipath, label, speed); - } - else if (loop) { + } else if (loop) { gcode += this->extrude_loop(*loop, label, speed); - } - else if (collection) { + } else if (collection) { gcode += extrude_support(*collection, support_extrusion_role); - } - else { + } else { throw Slic3r::InvalidArgument("Unknown extrusion type"); } } @@ -5153,15 +5173,9 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill return gcode; } -bool GCode::GCodeOutputStream::is_error() const -{ - return ::ferror(this->f); -} +bool GCode::GCodeOutputStream::is_error() const { return ::ferror(this->f); } -void GCode::GCodeOutputStream::flush() -{ - ::fflush(this->f); -} +void GCode::GCodeOutputStream::flush() { ::fflush(this->f); } void GCode::GCodeOutputStream::close() { @@ -5171,20 +5185,20 @@ void GCode::GCodeOutputStream::close() } } -void GCode::GCodeOutputStream::write(const char *what) +void GCode::GCodeOutputStream::write(const char* what) { if (what != nullptr) { const char* gcode = what; // writes string to file fwrite(gcode, 1, ::strlen(gcode), this->f); - //FIXME don't allocate a string, maybe process a batch of lines? + // FIXME don't allocate a string, maybe process a batch of lines? m_processor.process_buffer(std::string(gcode)); } } -void GCode::GCodeOutputStream::writeln(const std::string &what) +void GCode::GCodeOutputStream::writeln(const std::string& what) { - if (! what.empty()) + if (!what.empty()) this->write(what.back() == '\n' ? what : what + '\n'); } @@ -5198,19 +5212,19 @@ void GCode::GCodeOutputStream::write_format(const char* format, ...) va_list args2; va_copy(args2, args); buflen = - #ifdef _MSC_VER +#ifdef _MSC_VER ::_vscprintf(format, args2) - #else +#else ::vsnprintf(nullptr, 0, format, args2) - #endif +#endif + 1; va_end(args2); } - char buffer[1024]; - bool buffer_dynamic = buflen > 1024; - char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer; - int res = ::vsnprintf(bufptr, buflen, format, args); + char buffer[1024]; + bool buffer_dynamic = buflen > 1024; + char* bufptr = buffer_dynamic ? (char*) malloc(buflen) : buffer; + int res = ::vsnprintf(bufptr, buflen, format, args); if (res > 0) this->write(bufptr); @@ -5220,7 +5234,7 @@ void GCode::GCodeOutputStream::write_format(const char* format, ...) va_end(args); } -bool GCode::_needSAFC(const ExtrusionPath &path) +bool GCode::_needSAFC(const ExtrusionPath& path) { if (!m_small_area_infill_flow_compensator || !m_config.small_area_infill_flow_compensation.value) return false; @@ -5239,7 +5253,7 @@ bool GCode::_needSAFC(const ExtrusionPath &path) }); } -std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed) +std::string GCode::_extrude(const ExtrusionPath& path, std::string description, double speed) { std::string gcode; @@ -5255,7 +5269,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, bool slope_need_z_travel = false; if (sloped != nullptr && !sloped->is_flat()) { - auto target_z = get_sloped_z(sloped->slope_begin.z_ratio); + auto target_z = get_sloped_z(sloped->slope_begin.z_ratio); slope_need_z_travel = m_writer.will_move_z(target_z); } // Move to first point of extrusion path @@ -5263,12 +5277,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || slope_need_z_travel) { const bool _last_pos_undefined = !m_last_pos_defined; - gcode += this->travel_to( - path.first_point(), - path.role(), - "move to first " + description + " point", - sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio) - ); + gcode += this->travel_to(path.first_point(), path.role(), "move to first " + description + " point", + sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio)); m_need_change_layer_lift_z = false; // Orca: force restore Z after unknown last pos if (_last_pos_undefined && !slope_need_z_travel) { @@ -5286,7 +5296,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // Orca: optimize for Klipper, set acceleration and jerk in one command unsigned int acceleration_i = 0; - double jerk = 0; + double jerk = 0; // adjust acceleration if (m_config.default_acceleration.value > 0) { double acceleration; @@ -5311,7 +5321,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } else { acceleration = m_config.default_acceleration.value; } - acceleration_i = (unsigned int)floor(acceleration + 0.5); + acceleration_i = (unsigned int) floor(acceleration + 0.5); } // adjust X Y jerk @@ -5319,15 +5329,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (this->on_first_layer() && m_config.initial_layer_jerk.value > 0) { jerk = m_config.initial_layer_jerk.value; } else if (m_config.outer_wall_jerk.value > 0 && is_external_perimeter(path.role())) { - jerk = m_config.outer_wall_jerk.value; + jerk = m_config.outer_wall_jerk.value; } else if (m_config.inner_wall_jerk.value > 0 && is_internal_perimeter(path.role())) { jerk = m_config.inner_wall_jerk.value; } else if (m_config.top_surface_jerk.value > 0 && is_top_surface(path.role())) { jerk = m_config.top_surface_jerk.value; } else if (m_config.infill_jerk.value > 0 && is_infill(path.role())) { jerk = m_config.infill_jerk.value; - } - else { + } else { jerk = m_config.default_jerk.value; } } @@ -5351,7 +5360,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, _mm3_per_mm *= m_config.bottom_solid_infill_flow_ratio; else if (path.role() == erInternalBridgeInfill) _mm3_per_mm *= m_config.internal_bridge_flow; - else if(sloped) + else if (sloped) _mm3_per_mm *= m_config.scarf_joint_flow_ratio; // Effective extrusion length per distance unit = (filament_flow_ratio/cross_section) * mm3_per_mm / print flow ratio // m_writer.extruder()->e_per_mm3() below is (filament flow ratio / cross-sectional area) @@ -5370,8 +5379,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (sloped) { speed = std::min(speed, m_config.scarf_joint_speed.get_abs_value(m_config.get_abs_value("outer_wall_speed"))); } - } - else if(path.role() == erInternalBridgeInfill) { + } else if (path.role() == erInternalBridgeInfill) { speed = m_config.get_abs_value("internal_bridge_speed"); } else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) { speed = m_config.get_abs_value("bridge_speed"); @@ -5387,37 +5395,29 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, speed = m_config.get_abs_value("initial_layer_infill_speed"); } else if (path.role() == erGapFill) { speed = m_config.get_abs_value("gap_infill_speed"); - } - else if (path.role() == erSupportMaterial || - path.role() == erSupportMaterialInterface) { - const double support_speed = m_config.support_speed.value; - const double support_interface_speed = m_config.get_abs_value("support_interface_speed"); - speed = (path.role() == erSupportMaterial) ? support_speed : support_interface_speed; + } else if (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface) { + const double support_speed = m_config.support_speed.value; + const double support_interface_speed = m_config.get_abs_value("support_interface_speed"); + speed = (path.role() == erSupportMaterial) ? support_speed : support_interface_speed; } else { throw Slic3r::InvalidArgument("Invalid speed"); } } - //BBS: if not set the speed, then use the filament_max_volumetric_speed directly + // BBS: if not set the speed, then use the filament_max_volumetric_speed directly if (speed == 0) speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; if (this->on_first_layer()) { - //BBS: for solid infill of initial layer, speed can be higher as long as - //wall lines have be attached + // BBS: for solid infill of initial layer, speed can be higher as long as + // wall lines have be attached if (path.role() != erBottomSurface) speed = m_config.get_abs_value("initial_layer_speed"); - } - else if(m_config.slow_down_layers > 1){ + } else if (m_config.slow_down_layers > 1) { const auto _layer = layer_id(); if (_layer > 0 && _layer < m_config.slow_down_layers) { - const auto first_layer_speed = - is_perimeter(path.role()) - ? m_config.get_abs_value("initial_layer_speed") - : m_config.get_abs_value("initial_layer_infill_speed"); + const auto first_layer_speed = is_perimeter(path.role()) ? m_config.get_abs_value("initial_layer_speed") : + m_config.get_abs_value("initial_layer_infill_speed"); if (first_layer_speed < speed) { - speed = std::min( - speed, - Slic3r::lerp(first_layer_speed, speed, - (double)_layer / m_config.slow_down_layers)); + speed = std::min(speed, Slic3r::lerp(first_layer_speed, speed, (double) _layer / m_config.slow_down_layers)); } } } @@ -5425,126 +5425,121 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (path.role() == erSkirt) { const double skirt_speed = m_config.get_abs_value("skirt_speed"); if (skirt_speed > 0.0) - speed = skirt_speed; + speed = skirt_speed; } - //BBS: remove this config - //else if (this->object_layer_over_raft()) - // speed = m_config.get_abs_value("first_layer_speed_over_raft", speed); - //if (m_config.max_volumetric_speed.value > 0) { - // // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - // speed = std::min( - // speed, - // m_config.max_volumetric_speed.value / _mm3_per_mm - // ); - //} + // BBS: remove this config + // else if (this->object_layer_over_raft()) + // speed = m_config.get_abs_value("first_layer_speed_over_raft", speed); + // if (m_config.max_volumetric_speed.value > 0) { + // // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + // speed = std::min( + // speed, + // m_config.max_volumetric_speed.value / _mm3_per_mm + // ); + // } if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min(speed, EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm); } // ORCA: resonance‑avoidance on short external perimeters -{ - double ref_speed = speed; // stash the pre‑cap speed - if (path.role() == erExternalPerimeter - && m_config.resonance_avoidance.value) { - - // if our original speed was above “max”, disable RA for this loop - if (ref_speed > m_config.max_resonance_avoidance_speed.value) { - m_resonance_avoidance = false; - } - - // re‑apply volumetric cap - if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { - speed = std::min( - speed, - EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm - ); - } - - // if still in avoidance mode and under “max”, clamp to “min” - if (m_resonance_avoidance - && speed <= m_config.max_resonance_avoidance_speed.value) { - speed = std::min(speed, m_config.min_resonance_avoidance_speed.value); - } - - // reset flag for next segment - m_resonance_avoidance = true; - } -} - - bool variable_speed = false; - std::vector new_points {}; - - if (m_config.enable_overhang_speed && !this->on_first_layer() && - (is_bridge(path.role()) || is_perimeter(path.role()))) { - bool is_external = is_external_perimeter(path.role()); - double ref_speed = is_external ? m_config.get_abs_value("outer_wall_speed") : m_config.get_abs_value("inner_wall_speed"); - if (ref_speed == 0) - ref_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + { + double ref_speed = speed; // stash the pre‑cap speed + if (path.role() == erExternalPerimeter && m_config.resonance_avoidance.value) { + // if our original speed was above “max”, disable RA for this loop + if (ref_speed > m_config.max_resonance_avoidance_speed.value) { + m_resonance_avoidance = false; + } + // re‑apply volumetric cap if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { - ref_speed = std::min(ref_speed, EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm); + speed = std::min(speed, EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm); } - if (sloped) { - ref_speed = std::min(ref_speed, m_config.scarf_joint_speed.get_abs_value(ref_speed)); + + // if still in avoidance mode and under “max”, clamp to “min” + if (m_resonance_avoidance && speed <= m_config.max_resonance_avoidance_speed.value) { + speed = std::min(speed, m_config.min_resonance_avoidance_speed.value); } - - ConfigOptionPercents overhang_overlap_levels({90, 75, 50, 25, 13, 0}); - if (m_config.slowdown_for_curled_perimeters){ - ConfigOptionFloatsOrPercents dynamic_overhang_speeds( - {FloatOrPercent{100, true}, - (m_config.get_abs_value("overhang_1_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_1_4_speed", ref_speed) * 100 / ref_speed, true}, - (m_config.get_abs_value("overhang_2_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_2_4_speed", ref_speed) * 100 / ref_speed, true}, - (m_config.get_abs_value("overhang_3_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_3_4_speed", ref_speed) * 100 / ref_speed, true}, - (m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true}, - (m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true}}); - - new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhang_overlap_levels, dynamic_overhang_speeds, - ref_speed, speed, m_config.slowdown_for_curled_perimeters); - }else{ - ConfigOptionFloatsOrPercents dynamic_overhang_speeds( - {FloatOrPercent{100, true}, - (m_config.get_abs_value("overhang_1_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_1_4_speed", ref_speed) * 100 / ref_speed, true}, - (m_config.get_abs_value("overhang_2_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_2_4_speed", ref_speed) * 100 / ref_speed, true}, - (m_config.get_abs_value("overhang_3_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_3_4_speed", ref_speed) * 100 / ref_speed, true}, - (m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ? - FloatOrPercent{100, true} : - FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true}, - FloatOrPercent{m_config.get_abs_value("bridge_speed") * 100 / ref_speed, true}}); - - new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhang_overlap_levels, dynamic_overhang_speeds, - ref_speed, speed, m_config.slowdown_for_curled_perimeters); - } - variable_speed = std::any_of(new_points.begin(), new_points.end(), - [speed](const ProcessedPoint &p) { return fabs(double(p.speed) - speed) > 1; }); // Ignore small speed variations (under 1mm/sec) + // reset flag for next segment + m_resonance_avoidance = true; + } } - double F = speed * 60; // convert mm/sec to mm/min - + bool variable_speed = false; + std::vector new_points{}; + + if (m_config.enable_overhang_speed && !this->on_first_layer() && (is_bridge(path.role()) || is_perimeter(path.role()))) { + bool is_external = is_external_perimeter(path.role()); + double ref_speed = is_external ? m_config.get_abs_value("outer_wall_speed") : m_config.get_abs_value("inner_wall_speed"); + if (ref_speed == 0) + ref_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + + if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { + ref_speed = std::min(ref_speed, EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm); + } + if (sloped) { + ref_speed = std::min(ref_speed, m_config.scarf_joint_speed.get_abs_value(ref_speed)); + } + + ConfigOptionPercents overhang_overlap_levels({90, 75, 50, 25, 13, 0}); + + if (m_config.slowdown_for_curled_perimeters) { + ConfigOptionFloatsOrPercents dynamic_overhang_speeds( + {FloatOrPercent{100, true}, + (m_config.get_abs_value("overhang_1_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_1_4_speed", ref_speed) * 100 / ref_speed, true}, + (m_config.get_abs_value("overhang_2_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_2_4_speed", ref_speed) * 100 / ref_speed, true}, + (m_config.get_abs_value("overhang_3_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_3_4_speed", ref_speed) * 100 / ref_speed, true}, + (m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true}, + (m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true}}); + + new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhang_overlap_levels, dynamic_overhang_speeds, + ref_speed, speed, + m_config.slowdown_for_curled_perimeters); + } else { + ConfigOptionFloatsOrPercents dynamic_overhang_speeds( + {FloatOrPercent{100, true}, + (m_config.get_abs_value("overhang_1_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_1_4_speed", ref_speed) * 100 / ref_speed, true}, + (m_config.get_abs_value("overhang_2_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_2_4_speed", ref_speed) * 100 / ref_speed, true}, + (m_config.get_abs_value("overhang_3_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_3_4_speed", ref_speed) * 100 / ref_speed, true}, + (m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ? + FloatOrPercent{100, true} : + FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true}, + FloatOrPercent{m_config.get_abs_value("bridge_speed") * 100 / ref_speed, true}}); + + new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhang_overlap_levels, dynamic_overhang_speeds, + ref_speed, speed, + m_config.slowdown_for_curled_perimeters); + } + variable_speed = std::any_of(new_points.begin(), new_points.end(), [speed](const ProcessedPoint& p) { + return fabs(double(p.speed) - speed) > 1; + }); // Ignore small speed variations (under 1mm/sec) + } + + double F = speed * 60; // convert mm/sec to mm/min + // Orca: Dynamic PA // If adaptive PA is enabled, by default evaluate PA on all extrusion moves bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line || - m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern || - m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower; + m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern || m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower; bool evaluate_adaptive_pa = false; - bool role_change = (m_last_extrusion_role != path.role()); - if (!is_pa_calib && PHYSICAL_EXTRUDER_CONFIG(adaptive_pressure_advance) && PHYSICAL_EXTRUDER_CONFIG(enable_pressure_advance)) { + bool role_change = (m_last_extrusion_role != path.role()); + if (!is_pa_calib && EXTRUDER_CONFIG(adaptive_pressure_advance) && EXTRUDER_CONFIG(enable_pressure_advance)) { evaluate_adaptive_pa = true; // If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set // skip re-issuing the PA change tag. @@ -5556,21 +5551,21 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // TODO: is issued before the overhang perimeter role change is triggered // TODO: because for some reason (maybe path segmentation upstream?) there is a short path extruded // TODO: with the overhang speed and flow before the role change is flagged in the path.role() function. - if(role_change) + if (role_change) evaluate_adaptive_pa = true; } // Orca: End of dynamic PA trigger flag segment - - //Orca: process custom gcode for extrusion role change + + // Orca: process custom gcode for extrusion role change if (path.role() != m_last_extrusion_role && !m_config.change_extrusion_role_gcode.value.empty()) { - DynamicConfig config; - config.set_key_value("extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(path.role()))); - config.set_key_value("last_extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(m_last_extrusion_role))); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); - config.set_key_value("layer_z", new ConfigOptionFloat(m_layer == nullptr ? m_last_height : m_layer->print_z)); - gcode += this->placeholder_parser_process("change_extrusion_role_gcode", - m_config.change_extrusion_role_gcode.value, m_writer.extruder()->id(), &config) - + "\n"; + DynamicConfig config; + config.set_key_value("extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(path.role()))); + config.set_key_value("last_extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(m_last_extrusion_role))); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); + config.set_key_value("layer_z", new ConfigOptionFloat(m_layer == nullptr ? m_last_height : m_layer->print_z)); + gcode += this->placeholder_parser_process("change_extrusion_role_gcode", m_config.change_extrusion_role_gcode.value, + m_writer.extruder()->id(), &config) + + "\n"; } // extrude arc or line @@ -5579,7 +5574,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, char buf[32]; sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(path.role())); gcode += buf; - } + } } m_last_extrusion_role = path.role(); @@ -5593,7 +5588,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (path.role() != m_last_processor_extrusion_role) { m_last_processor_extrusion_role = path.role(); - sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str()); + sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), + ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str()); gcode += buf; } @@ -5616,7 +5612,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), m_last_height); gcode += buf; } - + // Orca: Dynamic PA // Post processor flag generation code segment when option to emit only at role changes is enabled // Variables published to the post processor: @@ -5631,32 +5627,24 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, bool isOverhangPerimeter = (path.role() == erOverhangPerimeter); if (m_multi_flow_segment_path_average_mm3_per_mm > 0) { sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", - GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), - m_writer.extruder()->id(), - m_multi_flow_segment_path_average_mm3_per_mm, - acceleration_i, - ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), - role_change, - isOverhangPerimeter); + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), m_writer.extruder()->id(), + m_multi_flow_segment_path_average_mm3_per_mm, acceleration_i, + ((path.role() == erBridgeInfill) || (path.role() == erOverhangPerimeter)), role_change, isOverhangPerimeter); gcode += buf; - } else if(_mm3_per_mm >0 ){ // Triggered when extruding a single segment path (like a line). - // Check if mm3_mm value is greater than zero as the wipe before external perimeter - // is a zero mm3_mm path to force de-retraction to happen and we dont want - // to issue a zero flow PA change command for this + } else if (_mm3_per_mm > 0) { // Triggered when extruding a single segment path (like a line). + // Check if mm3_mm value is greater than zero as the wipe before external perimeter + // is a zero mm3_mm path to force de-retraction to happen and we dont want + // to issue a zero flow PA change command for this sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", - GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), - m_writer.extruder()->id(), - _mm3_per_mm, - acceleration_i, - ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), - role_change, + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), m_writer.extruder()->id(), _mm3_per_mm, + acceleration_i, ((path.role() == erBridgeInfill) || (path.role() == erOverhangPerimeter)), role_change, isOverhangPerimeter); gcode += buf; } } - auto overhang_fan_threshold = PHYSICAL_EXTRUDER_CONFIG(overhang_fan_threshold); - auto enable_overhang_bridge_fan = PHYSICAL_EXTRUDER_CONFIG(enable_overhang_bridge_fan); + auto overhang_fan_threshold = EXTRUDER_CONFIG(overhang_fan_threshold); + auto enable_overhang_bridge_fan = EXTRUDER_CONFIG(enable_overhang_bridge_fan); // { "0%", Overhang_threshold_none }, // { "10%", Overhang_threshold_1_4 }, @@ -5665,31 +5653,19 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // { "75%", Overhang_threshold_4_4 }, // { "95%", Overhang_threshold_bridge } auto check_overhang_fan = [&overhang_fan_threshold](float overlap, ExtrusionRole role) { - if (role == erBridgeInfill || role == erOverhangPerimeter) { // ORCA: Split out bridge infill to internal and external to apply separate fan settings - return true; - } - switch (overhang_fan_threshold) { - case (int)Overhang_threshold_1_4: - return overlap <= 0.9f; - break; - case (int)Overhang_threshold_2_4: - return overlap <= 0.75f; - break; - case (int)Overhang_threshold_3_4: - return overlap <= 0.5f; - break; - case (int)Overhang_threshold_4_4: - return overlap <= 0.25f; - break; - case (int)Overhang_threshold_bridge: - return overlap <= 0.05f; - break; - case (int)Overhang_threshold_none: - return is_external_perimeter(role); - break; - default: - return false; - } + if (role == erBridgeInfill || + role == erOverhangPerimeter) { // ORCA: Split out bridge infill to internal and external to apply separate fan settings + return true; + } + switch (overhang_fan_threshold) { + case (int) Overhang_threshold_1_4: return overlap <= 0.9f; break; + case (int) Overhang_threshold_2_4: return overlap <= 0.75f; break; + case (int) Overhang_threshold_3_4: return overlap <= 0.5f; break; + case (int) Overhang_threshold_4_4: return overlap <= 0.25f; break; + case (int) Overhang_threshold_bridge: return overlap <= 0.05f; break; + case (int) Overhang_threshold_none: return is_external_perimeter(role); break; + default: return false; + } }; std::string comment; @@ -5719,57 +5695,48 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } } }; - auto apply_role_based_fan_speed = [ - &path, &append_role_based_fan_marker, - supp_interface_fan_speed = PHYSICAL_EXTRUDER_CONFIG(support_material_interface_fan_speed), - ironing_fan_speed = PHYSICAL_EXTRUDER_CONFIG(ironing_fan_speed) - ] { + auto apply_role_based_fan_speed = [&path, &append_role_based_fan_marker, + supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed), + ironing_fan_speed = EXTRUDER_CONFIG(ironing_fan_speed)] { append_role_based_fan_marker(erSupportMaterialInterface, "_SUPP_INTERFACE"sv, supp_interface_fan_speed >= 0 && path.role() == erSupportMaterialInterface); - append_role_based_fan_marker(erIroning, "_IRONING"sv, - ironing_fan_speed >= 0 && path.role() == erIroning); + append_role_based_fan_marker(erIroning, "_IRONING"sv, ironing_fan_speed >= 0 && path.role() == erIroning); }; if (!variable_speed) { // F is mm per minute. - if( (std::abs(writer().get_current_speed() - F) > EPSILON) || (std::abs(_mm3_per_mm - m_last_mm3_mm) > EPSILON) ){ + if ((std::abs(writer().get_current_speed() - F) > EPSILON) || (std::abs(_mm3_per_mm - m_last_mm3_mm) > EPSILON)) { // ORCA: Adaptive PA code segment when adjusting PA within the same feature // There is a speed change coming out of an overhang region // or a flow change, so emit the flag to evaluate PA for the upcomming extrusion // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. // Dont emit tag if it has just already been emitted from a role change above - if(_mm3_per_mm >0 && - PHYSICAL_EXTRUDER_CONFIG(adaptive_pressure_advance) && - PHYSICAL_EXTRUDER_CONFIG(enable_pressure_advance) && - PHYSICAL_EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) && - !evaluate_adaptive_pa){ - if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion - if(m_config.gcode_comments){ + if (_mm3_per_mm > 0 && EXTRUDER_CONFIG(adaptive_pressure_advance) && EXTRUDER_CONFIG(enable_pressure_advance) && + EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) && !evaluate_adaptive_pa) { + if (writer().get_current_speed() > + F) { // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion + if (m_config.gcode_comments) { sprintf(buf, "; Ramp down-non-variable\n"); gcode += buf; } sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", - GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), - m_writer.extruder()->id(), - _mm3_per_mm, - acceleration_i, - ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), - 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not a role change, + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), m_writer.extruder()->id(), _mm3_per_mm, + acceleration_i, ((path.role() == erBridgeInfill) || (path.role() == erOverhangPerimeter)), + 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not + // a role change, // the properties of the extrusion in the overhang are different so it behaves similarly to a role // change for the Adaptive PA post processor. 1); - }else{ // Ramping up speed - use baseline logic where max speed is used between current and upcoming extrusion - if(m_config.gcode_comments){ + } else { // Ramping up speed - use baseline logic where max speed is used between current and upcoming extrusion + if (m_config.gcode_comments) { sprintf(buf, "; Ramp up-non-variable\n"); gcode += buf; } sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", - GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), - m_writer.extruder()->id(), - _mm3_per_mm, - acceleration_i, - ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), - 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not a role change, + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), m_writer.extruder()->id(), _mm3_per_mm, + acceleration_i, ((path.role() == erBridgeInfill) || (path.role() == erOverhangPerimeter)), + 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not + // a role change, // the properties of the extrusion in the overhang are different so it is technically similar to a role // change for the Adaptive PA post processor. 0); @@ -5779,16 +5746,18 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } // ORCA: End of adaptive PA code segment } - + gcode += m_writer.set_speed(F, "", comment); { if (m_enable_cooling_markers) { if (enable_overhang_bridge_fan) { // BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external // perimeter - append_role_based_fan_marker(erOverhangPerimeter, "_OVERHANG"sv, - (overhang_fan_threshold == Overhang_threshold_none && is_external_perimeter(path.role())) || - (path.role() == erBridgeInfill || path.role() == erOverhangPerimeter)); // ORCA: Add support for separate internal bridge fan speed control + append_role_based_fan_marker( + erOverhangPerimeter, "_OVERHANG"sv, + (overhang_fan_threshold == Overhang_threshold_none && is_external_perimeter(path.role())) || + (path.role() == erBridgeInfill || + path.role() == erOverhangPerimeter)); // ORCA: Add support for separate internal bridge fan speed control // ORCA: Add support for separate internal bridge fan speed control append_role_based_fan_marker(erInternalBridgeInfill, "_INTERNAL_BRIDGE"sv, path.role() == erInternalBridgeInfill); @@ -5799,38 +5768,35 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode or we are doing sloped extrusion // Attention: G2 and G3 is not supported in spiral_mode mode if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr) { - double path_length = 0.; + double path_length = 0.; double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR; for (const Line& line : path.polyline.lines()) { - std::string tempDescription = description; - const double line_length = line.length() * SCALING_FACTOR; + std::string tempDescription = description; + const double line_length = line.length() * SCALING_FACTOR; if (line_length < EPSILON) continue; path_length += line_length; auto dE = e_per_mm * line_length; if (_needSAFC(path)) { auto oldE = dE; - dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role()); + dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role()); if (m_config.gcode_comments && oldE > 0 && oldE != dE) { - tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length); + tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f", oldE, line_length); } } if (sloped == nullptr) { // Normal extrusion - gcode += m_writer.extrude_to_xy( - this->point_to_gcode(line.b), - dE, - GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion()); + gcode += m_writer.extrude_to_xy(this->point_to_gcode(line.b), dE, + GCodeWriter::full_gcode_comment ? tempDescription : "", + path.is_force_no_extrusion()); } else { // Sloped extrusion const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length); - Vec2d dest2d = this->point_to_gcode(line.b); + Vec2d dest2d = this->point_to_gcode(line.b); Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio)); - gcode += m_writer.extrude_to_xyz( - dest3d, - dE * e_ratio, - GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion()); + gcode += m_writer.extrude_to_xyz(dest3d, dE * e_ratio, GCodeWriter::full_gcode_comment ? tempDescription : "", + path.is_force_no_extrusion()); } } } else { @@ -5841,51 +5807,48 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, switch (fitting_result[fitting_index].path_type) { case EMovePathType::Linear_move: { size_t start_index = fitting_result[fitting_index].start_point_index; - size_t end_index = fitting_result[fitting_index].end_point_index; + size_t end_index = fitting_result[fitting_index].end_point_index; for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) { - tempDescription = description; - const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); + tempDescription = description; + const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); const double line_length = line.length() * SCALING_FACTOR; if (line_length < EPSILON) continue; auto dE = e_per_mm * line_length; if (_needSAFC(path)) { auto oldE = dE; - dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role()); + dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role()); if (m_config.gcode_comments && oldE > 0 && oldE != dE) { - tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length); + tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f", oldE, line_length); } } - gcode += m_writer.extrude_to_xy( - this->point_to_gcode(line.b), - dE, - GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion()); + gcode += m_writer.extrude_to_xy(this->point_to_gcode(line.b), dE, + GCodeWriter::full_gcode_comment ? tempDescription : "", + path.is_force_no_extrusion()); } break; } case EMovePathType::Arc_move_cw: case EMovePathType::Arc_move_ccw: { - const ArcSegment& arc = fitting_result[fitting_index].arc_data; - const double arc_length = fitting_result[fitting_index].arc_data.length * SCALING_FACTOR; + const ArcSegment& arc = fitting_result[fitting_index].arc_data; + const double arc_length = fitting_result[fitting_index].arc_data.length * SCALING_FACTOR; if (arc_length < EPSILON) continue; const Vec2d center_offset = this->point_to_gcode(arc.center) - this->point_to_gcode(arc.start_point); - auto dE = e_per_mm * arc_length; + auto dE = e_per_mm * arc_length; if (_needSAFC(path)) { auto oldE = dE; - dE = m_small_area_infill_flow_compensator->modify_flow(arc_length, dE, path.role()); + dE = m_small_area_infill_flow_compensator->modify_flow(arc_length, dE, path.role()); if (m_config.gcode_comments && oldE > 0 && oldE != dE) { - tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, arc_length); + tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f", oldE, arc_length); } } - gcode += m_writer.extrude_arc_to_xy( - this->point_to_gcode(arc.end_point), - center_offset, - dE, - arc.direction == ArcDirection::Arc_Dir_CCW, - GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion()); + gcode += m_writer.extrude_arc_to_xy(this->point_to_gcode(arc.end_point), center_offset, dE, + arc.direction == ArcDirection::Arc_Dir_CCW, + GCodeWriter::full_gcode_comment ? tempDescription : "", + path.is_force_no_extrusion()); break; } default: @@ -5909,21 +5872,21 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, total_length = l.length() * SCALING_FACTOR; } gcode += m_writer.set_speed(last_set_speed, "", comment); - Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); - bool pre_fan_enabled = false; - bool cur_fan_enabled = false; - if( m_enable_cooling_markers && enable_overhang_bridge_fan) + Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); + bool pre_fan_enabled = false; + bool cur_fan_enabled = false; + if (m_enable_cooling_markers && enable_overhang_bridge_fan) pre_fan_enabled = check_overhang_fan(new_points[0].overlap, path.role()); - - if(path.role() == erInternalBridgeInfill) // ORCA: Add support for separate internal bridge fan speed control + + if (path.role() == erInternalBridgeInfill) // ORCA: Add support for separate internal bridge fan speed control pre_fan_enabled = true; double path_length = 0.; for (size_t i = 1; i < new_points.size(); i++) { - std::string tempDescription = description; - const ProcessedPoint &processed_point = new_points[i]; - const ProcessedPoint &pre_processed_point = new_points[i-1]; - Vec2d p = this->point_to_gcode_quantized(processed_point.p); + std::string tempDescription = description; + const ProcessedPoint& processed_point = new_points[i]; + const ProcessedPoint& pre_processed_point = new_points[i - 1]; + Vec2d p = this->point_to_gcode_quantized(processed_point.p); if (m_enable_cooling_markers) { if (enable_overhang_bridge_fan) { cur_fan_enabled = check_overhang_fan(processed_point.overlap, path.role()); @@ -5938,46 +5901,41 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } const double line_length = (p - prev).norm(); - if(line_length < EPSILON) + if (line_length < EPSILON) continue; path_length += line_length; double new_speed = pre_processed_point.speed * 60.0; - + if ((std::abs(last_set_speed - new_speed) > EPSILON) || (std::abs(_mm3_per_mm - m_last_mm3_mm) > EPSILON)) { // ORCA: Adaptive PA code segment when adjusting PA within the same feature // There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. - if(_mm3_per_mm >0 && - PHYSICAL_EXTRUDER_CONFIG(adaptive_pressure_advance) && - PHYSICAL_EXTRUDER_CONFIG(enable_pressure_advance) && - PHYSICAL_EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){ - if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion - if(m_config.gcode_comments) { + if (_mm3_per_mm > 0 && EXTRUDER_CONFIG(adaptive_pressure_advance) && EXTRUDER_CONFIG(enable_pressure_advance) && + EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs)) { + if (last_set_speed > new_speed) { // Ramping down speed - use overhang logic where the minimum speed is used between + // current and upcoming extrusion + if (m_config.gcode_comments) { sprintf(buf, "; Ramp up-variable\n"); gcode += buf; } sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", - GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), - m_writer.extruder()->id(), - _mm3_per_mm, - acceleration_i, - ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), - 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not a role change, + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), m_writer.extruder()->id(), + _mm3_per_mm, acceleration_i, ((path.role() == erBridgeInfill) || (path.role() == erOverhangPerimeter)), + 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is + // not a role change, // the properties of the extrusion in the overhang are different so it is technically similar to a role // change for the Adaptive PA post processor. 1); - }else{ // Ramping up speed - use baseline logic where max speed is used between current and upcoming extrusion - if(m_config.gcode_comments) { + } else { // Ramping up speed - use baseline logic where max speed is used between current and upcoming extrusion + if (m_config.gcode_comments) { sprintf(buf, "; Ramp down-variable\n"); gcode += buf; } sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", - GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), - m_writer.extruder()->id(), - _mm3_per_mm, - acceleration_i, - ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), - 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not a role change, + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), m_writer.extruder()->id(), + _mm3_per_mm, acceleration_i, ((path.role() == erBridgeInfill) || (path.role() == erOverhangPerimeter)), + 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is + // not a role change, // the properties of the extrusion in the overhang are different so it is technically similar to a role // change for the Adaptive PA post processor. 0); @@ -5985,8 +5943,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; m_last_mm3_mm = _mm3_per_mm; } - }// ORCA: End of adaptive PA code segment - + } // ORCA: End of adaptive PA code segment + // Ignore small speed variations - emit speed change if the delta between current and new is greater than 60mm/min / 1mm/sec // Reset speed to F if delta to F is less than 1mm/sec if ((std::abs(last_set_speed - new_speed) > 60)) { @@ -5999,10 +5957,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, auto dE = e_per_mm * line_length; if (_needSAFC(path)) { auto oldE = dE; - dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role()); + dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role()); if (m_config.gcode_comments && oldE > 0 && oldE != dE) { - tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length); + tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f", oldE, line_length); } } if (sloped == nullptr) { @@ -6016,53 +5974,52 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } prev = p; - } } if (m_enable_cooling_markers) { - gcode += ";_EXTRUDE_END\n"; + gcode += ";_EXTRUDE_END\n"; } if (path.role() != ExtrusionRole::erGapFill) { - m_last_notgapfill_extrusion_role = path.role(); + m_last_notgapfill_extrusion_role = path.role(); } this->set_last_pos(path.last_point()); return gcode; } -//Orca: get string name of extrusion role. used for change_extruder_role_gcode -std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole & role) +// Orca: get string name of extrusion role. used for change_extruder_role_gcode +std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole& role) { switch (role) { - case erPerimeter: return "Perimeter"; - case erExternalPerimeter: return "ExternalPerimeter"; - case erOverhangPerimeter: return "OverhangPerimeter"; - case erInternalInfill: return "InternalInfill"; - case erSolidInfill: return "SolidInfill"; - case erTopSolidInfill: return "TopSolidInfill"; - case erBottomSurface: return "BottomSurface"; - case erBridgeInfill: - case erInternalBridgeInfill: return "BridgeInfill"; - case erGapFill: return "GapFill"; - case erIroning: return "Ironing"; - case erSkirt: return "Skirt"; - case erBrim: return "Brim"; - case erSupportMaterial: return "SupportMaterial"; - case erSupportMaterialInterface: return "SupportMaterialInterface"; - case erSupportTransition: return "SupportTransition"; - case erWipeTower: return "WipeTower"; - case erCustom: - case erMixed: - case erCount: - case erNone: - default: return "Mixed"; + case erPerimeter: return "Perimeter"; + case erExternalPerimeter: return "ExternalPerimeter"; + case erOverhangPerimeter: return "OverhangPerimeter"; + case erInternalInfill: return "InternalInfill"; + case erSolidInfill: return "SolidInfill"; + case erTopSolidInfill: return "TopSolidInfill"; + case erBottomSurface: return "BottomSurface"; + case erBridgeInfill: + case erInternalBridgeInfill: return "BridgeInfill"; + case erGapFill: return "GapFill"; + case erIroning: return "Ironing"; + case erSkirt: return "Skirt"; + case erBrim: return "Brim"; + case erSupportMaterial: return "SupportMaterial"; + case erSupportMaterialInterface: return "SupportMaterialInterface"; + case erSupportTransition: return "SupportTransition"; + case erWipeTower: return "WipeTower"; + case erCustom: + case erMixed: + case erCount: + case erNone: + default: return "Mixed"; } } std::string encodeBase64(uint64_t value) { - //Always use big endian mode + // Always use big endian mode uint8_t src[8]; for (size_t i = 0; i < 8; i++) src[i] = (value >> (8 * i)) & 0xff; @@ -6092,24 +6049,24 @@ std::string GCode::_encode_label_ids_to_base64(std::vector ids) } // This method accepts &point in print coordinates. -std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string comment, double z/* = DBL_MAX*/) +std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string comment, double z /* = DBL_MAX*/) { /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by this->origin in order to get G-code coordinates. */ - Polyline travel { this->last_pos(), point }; + Polyline travel{this->last_pos(), point}; // check whether a straight travel move would need retraction - LiftType lift_type = LiftType::SpiralLift; - bool needs_retraction = this->needs_retraction(travel, role, lift_type); + LiftType lift_type = LiftType::SpiralLift; + bool needs_retraction = this->needs_retraction(travel, role, lift_type); // check whether wipe could be disabled without causing visible stringing - bool could_be_wipe_disabled = false; + bool could_be_wipe_disabled = false; // Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to. - const bool used_external_mp_once = m_avoid_crossing_perimeters.used_external_mp_once(); + const bool used_external_mp_once = m_avoid_crossing_perimeters.used_external_mp_once(); std::string gcode; // Orca: we don't need to optimize the Klipper as only set once - double jerk_to_set = 0.0; + double jerk_to_set = 0.0; unsigned int acceleration_to_set = 0; if (this->on_first_layer()) { if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { @@ -6135,15 +6092,13 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string // if a retraction would be needed, try to use reduce_crossing_wall to plan a // multi-hop travel path inside the configuration space - if (m_config.reduce_crossing_wall - && !m_avoid_crossing_perimeters.disabled_once() - && m_writer.is_current_position_clear()) - //BBS: don't generate detour travel paths when current position is unclea + if (m_config.reduce_crossing_wall && !m_avoid_crossing_perimeters.disabled_once() && m_writer.is_current_position_clear()) + // BBS: don't generate detour travel paths when current position is unclea { travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role, lift_type); - //if (needs_retraction && m_layer_index > 1) exit(0); + // if (needs_retraction && m_layer_index > 1) exit(0); } // Re-allow reduce_crossing_wall for the next travel moves @@ -6162,7 +6117,8 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string // Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction() // FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations. if (last_post_before_retract != this->last_pos() && m_config.reduce_crossing_wall) { - // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. + // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for + // next call. if (used_external_mp_once) m_avoid_crossing_perimeters.use_external_mp_once(); travel = m_avoid_crossing_perimeters.travel_to(*this, point); @@ -6174,14 +6130,13 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); // if (m_config.reduce_crossing_wall) { - // // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. - // if (used_external_mp_once) m_avoid_crossing_perimeters.use_external_mp_once(); - // travel = m_avoid_crossing_perimeters.travel_to(*this, point); + // // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value + // for next call. if (used_external_mp_once) m_avoid_crossing_perimeters.use_external_mp_once(); travel = + // m_avoid_crossing_perimeters.travel_to(*this, point); // // If state of use_external_mp_once was changed reset it to right value. // if (used_external_mp_once) m_avoid_crossing_perimeters.reset_once_modifiers(); // } } - // if needed, write the gcode_label_objects_end then gcode_label_objects_start m_writer.add_object_change_labels(gcode); @@ -6189,7 +6144,7 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string // use G1 because we rely on paths being straight (G0 may make round paths) if (travel.size() >= 2) { // Orca: use `travel_to_xyz` to ensure we start at the correct z, in case we moved z in custom/filament change gcode - if (false/*m_spiral_vase*/) { + if (false /*m_spiral_vase*/) { // No lazy z lift for spiral vase mode for (size_t i = 1; i < travel.size(); ++i) { gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); @@ -6198,7 +6153,7 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string if (travel.size() == 2) { // No extra movements emitted by avoid_crossing_perimeters, simply move to the end point with z change const auto& dest2d = this->point_to_gcode(travel.points.back()); - Vec3d dest3d(dest2d(0), dest2d(1), z == DBL_MAX ? m_nominal_z : z); + Vec3d dest3d(dest2d(0), dest2d(1), z == DBL_MAX ? m_nominal_z : z); gcode += m_writer.travel_to_xyz(dest3d, comment, m_need_change_layer_lift_z); m_need_change_layer_lift_z = false; } else { @@ -6229,51 +6184,49 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string return gcode; } -//BBS -LiftType GCode::to_lift_type(ZHopType z_hop_types) { - switch (z_hop_types) - { - case ZHopType::zhtNormal: - return LiftType::NormalLift; - case ZHopType::zhtSlope: - return LiftType::LazyLift; - case ZHopType::zhtSpiral: - return LiftType::SpiralLift; +// BBS +LiftType GCode::to_lift_type(ZHopType z_hop_types) +{ + switch (z_hop_types) { + case ZHopType::zhtNormal: return LiftType::NormalLift; + case ZHopType::zhtSlope: return LiftType::LazyLift; + case ZHopType::zhtSpiral: return LiftType::SpiralLift; default: // if no corresponding lift type, use normal lift return LiftType::NormalLift; } }; -bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftType& lift_type) +bool GCode::needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type) { if (travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel))) { // skip retraction if the move is shorter than the configured threshold return false; } - //BBS: input travel polyline must be in current plate coordinate system + // BBS: input travel polyline must be in current plate coordinate system auto is_through_overhang = [this](const Polyline& travel) { BoundingBox travel_bbox = get_extents(travel); travel_bbox.inflated(1); travel_bbox.defined = true; // do not scale for z - const float protect_z = 0.4; + const float protect_z = 0.4; std::pair z_range; z_range.second = m_layer ? m_layer->print_z : 0.f; - z_range.first = std::max(0.f, z_range.second - protect_z); - std::vector layers_of_objects; + z_range.first = std::max(0.f, z_range.second - protect_z); + std::vector layers_of_objects; std::vector boundingBox_for_objects; - VecOfPoints objects_instances_shift; - std::vector idx_of_object_sorted = m_curr_print->layers_sorted_for_object(z_range.first, z_range.second, layers_of_objects, boundingBox_for_objects, objects_instances_shift); + VecOfPoints objects_instances_shift; + std::vector idx_of_object_sorted = m_curr_print->layers_sorted_for_object(z_range.first, z_range.second, layers_of_objects, + boundingBox_for_objects, objects_instances_shift); std::vector is_layers_of_objects_sorted(layers_of_objects.size(), false); for (size_t idx : idx_of_object_sorted) { - for (const Point & instance_shift : objects_instances_shift[idx]) { + for (const Point& instance_shift : objects_instances_shift[idx]) { BoundingBox instance_bbox = boundingBox_for_objects[idx]; - if (!instance_bbox.defined) //BBS: Don't need to check when bounding box of overhang area is empty(undefined) + if (!instance_bbox.defined) // BBS: Don't need to check when bounding box of overhang area is empty(undefined) continue; instance_bbox.offset(scale_(EPSILON)); @@ -6287,7 +6240,8 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp continue; if (!is_layers_of_objects_sorted[idx]) { - std::sort(layers_of_objects[idx].begin(), layers_of_objects[idx].end(), [](auto left, auto right) { return left->loverhangs_bbox.area() > right->loverhangs_bbox.area();}); + std::sort(layers_of_objects[idx].begin(), layers_of_objects[idx].end(), + [](auto left, auto right) { return left->loverhangs_bbox.area() > right->loverhangs_bbox.area(); }); is_layers_of_objects_sorted[idx] = true; } @@ -6312,24 +6266,25 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp float max_z_hop = 0.f; for (int i = 0; i < m_config.z_hop.size(); i++) - max_z_hop = std::max(max_z_hop, (float)m_config.z_hop.get_at(i)); - float travel_len_thresh = scale_(max_z_hop / tan(this->writer().extruder()->travel_slope())); - float accum_len = 0.f; + max_z_hop = std::max(max_z_hop, (float) m_config.z_hop.get_at(i)); + float travel_len_thresh = scale_(max_z_hop / tan(this->writer().extruder()->travel_slope())); + float accum_len = 0.f; Polyline clipped_travel; clipped_travel.append(Polyline(travel.points[0], travel.points[1])); if (clipped_travel.length() > travel_len_thresh) - clipped_travel.points.back() = clipped_travel.points.front()+(clipped_travel.points.back() - clipped_travel.points.front()) * (travel_len_thresh / clipped_travel.length()); - //BBS: translate to current plate coordinate system - clipped_travel.translate(Point::new_scale(double(m_origin.x() - m_writer.get_xy_offset().x()), double(m_origin.y() - m_writer.get_xy_offset().y()))); + clipped_travel.points.back() = clipped_travel.points.front() + (clipped_travel.points.back() - clipped_travel.points.front()) * + (travel_len_thresh / clipped_travel.length()); + // BBS: translate to current plate coordinate system + clipped_travel.translate( + Point::new_scale(double(m_origin.x() - m_writer.get_xy_offset().x()), double(m_origin.y() - m_writer.get_xy_offset().y()))); - //BBS: force to retract when leave from external perimeter for a long travel - //Better way is judging whether the travel move direction is same with last extrusion move. + // BBS: force to retract when leave from external perimeter for a long travel + // Better way is judging whether the travel move direction is same with last extrusion move. if (is_perimeter(m_last_processor_extrusion_role) && m_last_processor_extrusion_role != erPerimeter) { if (ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::LazyLift; - } - else { + } else { lift_type = to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); } return true; @@ -6337,33 +6292,32 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp if (role == erSupportMaterial || role == erSupportTransition) { const SupportLayer* support_layer = dynamic_cast(m_layer); - //FIXME support_layer->support_islands.contains should use some search structure! + // FIXME support_layer->support_islands.contains should use some search structure! if (support_layer != NULL) // skip retraction if this is a travel move inside a support material island - //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material + // FIXME not retracting over a long path may cause oozing, which in turn may result in missing material // at the end of the extrusion path! for (const ExPolygon& support_island : support_layer->support_islands) if (support_island.contains(travel)) return false; - //reduce the retractions in lightning infills for tree support - if (support_layer != NULL && support_layer->support_type==stInnerTree) - for (auto &area : support_layer->base_areas) + // reduce the retractions in lightning infills for tree support + if (support_layer != NULL && support_layer->support_type == stInnerTree) + for (auto& area : support_layer->base_areas) if (area.contains(travel)) return false; } - //BBS: need retract when long moving to print perimeter to avoid dropping of material - if (!is_perimeter(role) && m_config.reduce_infill_retraction && m_layer != nullptr && - m_config.sparse_infill_density.value > 0 && m_retract_when_crossing_perimeters.travel_inside_internal_regions(*m_layer, travel)) + // BBS: need retract when long moving to print perimeter to avoid dropping of material + if (!is_perimeter(role) && m_config.reduce_infill_retraction && m_layer != nullptr && m_config.sparse_infill_density.value > 0 && + m_retract_when_crossing_perimeters.travel_inside_internal_regions(*m_layer, travel)) // Skip retraction if travel is contained in an internal slice *and* // internal infill is enabled (so that stringing is entirely not visible). - //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. + // FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. return false; // retract if reduce_infill_retraction is disabled or doesn't apply when role is perimeter if (ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::LazyLift; - } - else { + } else { lift_type = to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); } return true; @@ -6379,8 +6333,9 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li // wipe (if it's enabled for this extruder and we have a stored wipe path and no-zero wipe distance) if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path() && scale_(EXTRUDER_CONFIG(wipe_distance)) > SCALED_EPSILON) { Wipe::RetractionValues wipeRetractions = m_wipe.calculateWipeRetractionLengths(*this, toolchange); - gcode += toolchange ? m_writer.retract_for_toolchange(true,wipeRetractions.retractLengthBeforeWipe) : m_writer.retract(true, wipeRetractions.retractLengthBeforeWipe); - gcode += m_wipe.wipe(*this,wipeRetractions.retractLengthDuringWipe, toolchange, is_last_retraction); + gcode += toolchange ? m_writer.retract_for_toolchange(true, wipeRetractions.retractLengthBeforeWipe) : + m_writer.retract(true, wipeRetractions.retractLengthBeforeWipe); + gcode += m_wipe.wipe(*this, wipeRetractions.retractLengthDuringWipe, toolchange, is_last_retraction); } /* The parent class will decide whether we need to perform an actual retraction @@ -6393,34 +6348,31 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li // if (printer_model == "Snapmaker U1" && toolchange) { // gcode += "M400\n"; // } - if ((!this->on_first_layer() || this->config().bottom_surface_pattern != InfillPattern::ipHilbertCurve) && - (role != erTopSolidInfill || this->config().top_surface_pattern != InfillPattern::ipHilbertCurve)){ - gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); - } + if ((!this->on_first_layer() || this->config().bottom_surface_pattern != InfillPattern::ipHilbertCurve) && + (role != erTopSolidInfill || this->config().top_surface_pattern != InfillPattern::ipHilbertCurve)) { + gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); + } gcode += m_writer.reset_e(); // Orca: check if should + can lift (roughly from SuperSlicer) RetractLiftEnforceType retract_lift_type = RetractLiftEnforceType(EXTRUDER_CONFIG(retract_lift_enforce)); - bool needs_lift = toolchange - || m_writer.extruder()->retraction_length() > 0 - || m_config.use_firmware_retraction; + bool needs_lift = toolchange || m_writer.extruder()->retraction_length() > 0 || m_config.use_firmware_retraction; - bool last_fill_extrusion_role_top_infill = (this->m_last_notgapfill_extrusion_role == ExtrusionRole::erTopSolidInfill || this->m_last_notgapfill_extrusion_role == ExtrusionRole::erIroning); + bool last_fill_extrusion_role_top_infill = (this->m_last_notgapfill_extrusion_role == ExtrusionRole::erTopSolidInfill || + this->m_last_notgapfill_extrusion_role == ExtrusionRole::erIroning); - // assume we can lift on retraction; conditions left explicit + // assume we can lift on retraction; conditions left explicit bool can_lift = true; if (retract_lift_type == RetractLiftEnforceType::rletAllSurfaces) { can_lift = true; - } - else if (this->m_layer_index == 0 && (retract_lift_type == RetractLiftEnforceType::rletBottomOnly || retract_lift_type == RetractLiftEnforceType::rletTopAndBottom)) { + } else if (this->m_layer_index == 0 && (retract_lift_type == RetractLiftEnforceType::rletBottomOnly || + retract_lift_type == RetractLiftEnforceType::rletTopAndBottom)) { can_lift = true; - } - else if (retract_lift_type == RetractLiftEnforceType::rletTopOnly || retract_lift_type == RetractLiftEnforceType::rletTopAndBottom) { + } else if (retract_lift_type == RetractLiftEnforceType::rletTopOnly || retract_lift_type == RetractLiftEnforceType::rletTopAndBottom) { can_lift = last_fill_extrusion_role_top_infill; - } - else { + } else { can_lift = false; } @@ -6442,8 +6394,8 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b std::string gcode; // Append the filament start G-code. - const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); - if (! filament_start_gcode.empty()) { + const std::string& filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); + if (!filament_start_gcode.empty()) { // Process the filament_start_gcode for the filament. DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); @@ -6457,12 +6409,11 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, extruder_id, &config); check_add_eol(gcode); } - int physical_extruder_id = m_writer.get_physical_extruder(extruder_id); - if (m_config.enable_pressure_advance.get_at(physical_extruder_id)) { - gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(physical_extruder_id)); + if (m_config.enable_pressure_advance.get_at(extruder_id)) { + gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(extruder_id)); // Orca: Adaptive PA // Reset Adaptive PA processor last PA value - m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(physical_extruder_id)); + m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(extruder_id)); } gcode += m_writer.toolchange(extruder_id); @@ -6485,12 +6436,12 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b if (m_writer.extruder() != nullptr) { // Process the custom filament_end_gcode. set_extruder() is only called if there is no wipe tower // so it should not be injected twice. - unsigned int old_extruder_id = m_writer.extruder()->id(); - const std::string &filament_end_gcode = m_config.filament_end_gcode.get_at(old_extruder_id); - if (! filament_end_gcode.empty()) { + unsigned int old_extruder_id = m_writer.extruder()->id(); + const std::string& filament_end_gcode = m_config.filament_end_gcode.get_at(old_extruder_id); + if (!filament_end_gcode.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position().z() - m_config.z_offset.value)); + config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position().z() - m_config.z_offset.value)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(old_extruder_id))); gcode += placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_extruder_id, &config); @@ -6503,92 +6454,66 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); - // 需要在参数查询之前计算,因为挤出机属性(温度、回抽等)应该从物理挤出机获取 - int next_physical_extruder = m_writer.get_physical_extruder(extruder_id); - - BOOST_LOG_TRIVIAL(info) << "GCode::set_extruder: Tool change START - filament_id=" << extruder_id - << " -> physical_extruder_id=" << next_physical_extruder - << " print_z=" << print_z - << " layer_index=" << m_layer_index; - // BBS - // 回抽和温度是挤出机属性,使用 physical_extruder - - size_t retract_array_size = m_config.retraction_length.values.size(); - size_t temp_array_size = m_config.nozzle_temperature.values.size(); - BOOST_LOG_TRIVIAL(info) << "GCode::set_extruder: Config access check -" - << " retraction_length array_size=" << retract_array_size - << " nozzle_temperature array_size=" << temp_array_size - << " extruder_id=" << extruder_id - << (extruder_id >= (int)retract_array_size ? " [RETRACT_OUT_OF_BOUNDS!]" : "") - << (extruder_id >= (int)temp_array_size ? " [TEMP_OUT_OF_BOUNDS!]" : ""); - - float new_retract_length = m_config.retraction_length.get_at(extruder_id); + float new_retract_length = m_config.retraction_length.get_at(extruder_id); float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(extruder_id); - int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(extruder_id): m_config.nozzle_temperature.get_at(extruder_id); + int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(extruder_id) : + m_config.nozzle_temperature.get_at(extruder_id); // BBS: if print_z == 0 use first layer temperature if (abs(print_z) < EPSILON) new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(extruder_id); Vec3d nozzle_pos = m_writer.get_position(); float old_retract_length, old_retract_length_toolchange, wipe_volume; - int old_filament_temp, old_filament_e_feedrate; - int previous_physical_extruder = -1; + int old_filament_temp, old_filament_e_feedrate; float filament_area = float((M_PI / 4.f) * pow(m_config.filament_diameter.get_at(extruder_id), 2)); - //BBS: add handling for filament change in start gcode + // BBS: add handling for filament change in start gcode int previous_extruder_id = -1; if (m_writer.extruder() != nullptr || m_start_gcode_filament != -1) { std::vector flush_matrix(cast(m_config.flush_volumes_matrix.values)); - const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); + const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON); if (m_writer.extruder() != nullptr) assert(m_writer.extruder()->id() < number_of_extruders); else assert(m_start_gcode_filament < number_of_extruders); - previous_extruder_id = m_writer.extruder() != nullptr ? m_writer.extruder()->id() : m_start_gcode_filament; - - BOOST_LOG_TRIVIAL(info) << "GCode::set_extruder: Previous extruder config - filament_id=" << previous_extruder_id - << " (array_size=" << m_config.retraction_length.values.size() << ")" - << (previous_extruder_id >= (int)m_config.retraction_length.values.size() ? " [PREV_OUT_OF_BOUNDS!]" : " [OK]"); - - old_retract_length = m_config.retraction_length.get_at(previous_extruder_id); + previous_extruder_id = m_writer.extruder() != nullptr ? m_writer.extruder()->id() : m_start_gcode_filament; + old_retract_length = m_config.retraction_length.get_at(previous_extruder_id); old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(previous_extruder_id); - previous_physical_extruder = m_writer.get_physical_extruder(previous_extruder_id); - - old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : m_config.nozzle_temperature.get_at(previous_extruder_id); - //Orca: always calculate wipe volume and hence provide correct flush_length, so that MMU devices with cutter and purge bin (e.g. ERCF_v2 with a filament cutter or Filametrix can take advantage of it) + old_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : + m_config.nozzle_temperature.get_at(previous_extruder_id); + // Orca: always calculate wipe volume and hence provide correct flush_length, so that MMU devices with cutter and purge bin (e.g. + // ERCF_v2 with a filament cutter or Filametrix can take advantage of it) wipe_volume = flush_matrix[previous_extruder_id * number_of_extruders + extruder_id]; wipe_volume *= m_config.flush_multiplier; - old_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / filament_area); + old_filament_e_feedrate = (int) (60.0 * m_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / filament_area); old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; - //BBS: must clean m_start_gcode_filament + // BBS: must clean m_start_gcode_filament m_start_gcode_filament = -1; } else { - old_retract_length = 0.f; + old_retract_length = 0.f; old_retract_length_toolchange = 0.f; - old_filament_temp = 0; - wipe_volume = 0.f; - old_filament_e_feedrate = 200; + old_filament_temp = 0; + wipe_volume = 0.f; + old_filament_e_feedrate = 200; } - float wipe_length = wipe_volume / filament_area; - int new_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(extruder_id) / filament_area); - new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; + float wipe_length = wipe_volume / filament_area; + int new_filament_e_feedrate = (int) (60.0 * m_config.filament_max_volumetric_speed.get_at(extruder_id) / filament_area); + new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; DynamicConfig dyn_config; dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); - dyn_config.set_key_value("next_physical_extruder", new ConfigOptionInt(next_physical_extruder)); - dyn_config.set_key_value("previous_physical_extruder", new ConfigOptionInt(previous_physical_extruder)); + dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int) extruder_id)); dyn_config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); dyn_config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); dyn_config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); dyn_config.set_key_value("relative_e_axis", new ConfigOptionBool(m_config.use_relative_e_distances)); - dyn_config.set_key_value("toolchange_count", new ConfigOptionInt((int)m_toolchange_count)); - //BBS: fan speed is useless placeholer now, but we don't remove it to avoid - //slicing error in old change_filament_gcode in old 3MF - dyn_config.set_key_value("fan_speed", new ConfigOptionInt((int)0)); + dyn_config.set_key_value("toolchange_count", new ConfigOptionInt((int) m_toolchange_count)); + // BBS: fan speed is useless placeholer now, but we don't remove it to avoid + // slicing error in old change_filament_gcode in old 3MF + dyn_config.set_key_value("fan_speed", new ConfigOptionInt((int) 0)); dyn_config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); dyn_config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); dyn_config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); @@ -6611,17 +6536,17 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length)); - int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time)); - float flush_unit = wipe_length / flush_count; - int flush_idx = 0; + int flush_count = std::min(g_max_flush_count, (int) std::round(wipe_volume / g_purge_volume_one_time)); + float flush_unit = wipe_length / flush_count; + int flush_idx = 0; for (; flush_idx < flush_count; flush_idx++) { - char key_value[64] = { 0 }; + char key_value[64] = {0}; snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); dyn_config.set_key_value(key_value, new ConfigOptionFloat(flush_unit)); } for (; flush_idx < g_max_flush_count; flush_idx++) { - char key_value[64] = { 0 }; + char key_value[64] = {0}; snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); dyn_config.set_key_value(key_value, new ConfigOptionFloat(0.f)); } @@ -6632,8 +6557,8 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b // Process the custom change_filament_gcode. const std::string& change_filament_gcode = m_config.change_filament_gcode.value; - std::string toolchange_gcode_parsed; - //Orca: Ignore change_filament_gcode if is the first call for a tool change and manual_filament_change is enabled + std::string toolchange_gcode_parsed; + // Orca: Ignore change_filament_gcode if is the first call for a tool change and manual_filament_change is enabled if (!change_filament_gcode.empty() && !(m_config.manual_filament_change.value && m_toolchange_count == 1)) { dyn_config.set_key_value("toolchange_z", new ConfigOptionFloat(print_z)); @@ -6641,17 +6566,17 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b check_add_eol(toolchange_gcode_parsed); gcode += toolchange_gcode_parsed; - //BBS + // BBS { - //BBS: gcode writer doesn't know where the extruder is and whether fan speed is changed after inserting tool change gcode - //Set this flag so that normal lift will be used the first time after tool change. + // BBS: gcode writer doesn't know where the extruder is and whether fan speed is changed after inserting tool change gcode + // Set this flag so that normal lift will be used the first time after tool change. gcode += ";_FORCE_RESUME_FAN_SPEED\n"; m_writer.set_current_position_clear(false); - //BBS: check whether custom gcode changes the z position. Update if changed + // BBS: check whether custom gcode changes the z position. Update if changed double temp_z_after_tool_change; if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_parsed, temp_z_after_tool_change)) { Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_tool_change; + pos(2) = temp_z_after_tool_change; m_writer.set_position(pos); } } @@ -6665,7 +6590,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b // We inform the writer about what is happening, but we may not use the resulting gcode. std::string toolchange_command = m_writer.toolchange(extruder_id); - if (! custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), extruder_id)) + if (!custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), extruder_id)) gcode += toolchange_command; else { // user provided his own toolchange gcode, no need to do anything @@ -6684,8 +6609,8 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b this->placeholder_parser().set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(extruder_id)); // Append the filament start G-code. - const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); - if (! filament_start_gcode.empty()) { + const std::string& filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); + if (!filament_start_gcode.empty()) { // Process the filament_start_gcode for the new filament. DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); @@ -6699,38 +6624,39 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b if (m_ooze_prevention.enable) gcode += m_ooze_prevention.post_toolchange(*this); - int physical_extruder_id = m_writer.get_physical_extruder(extruder_id); - if (m_config.enable_pressure_advance.get_at(physical_extruder_id)) { - gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(physical_extruder_id)); + if (m_config.enable_pressure_advance.get_at(extruder_id)) { + gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(extruder_id)); } - //Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined + // Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined m_last_pos_defined = false; return gcode; } -inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) { +inline std::string polygon_to_string(const Polygon& polygon, Print* print, bool is_print_space = false) +{ std::ostringstream gcode; gcode << "["; - for (const Point &p : polygon.points) { + for (const Point& p : polygon.points) { const auto v = is_print_space ? Vec2d(p.x(), p.y()) : print->translate_to_print_space(p); gcode << "[" << v.x() << "," << v.y() << "],"; } - const auto first_v = is_print_space ? Vec2d(polygon.points.front().x(), polygon.points.front().y()) - : print->translate_to_print_space(polygon.points.front()); + const auto first_v = is_print_space ? Vec2d(polygon.points.front().x(), polygon.points.front().y()) : + print->translate_to_print_space(polygon.points.front()); gcode << "[" << first_v.x() << "," << first_v.y() << "]"; gcode << "]"; return gcode.str(); } // this function iterator PrintObject and assign a seqential id to each object. // this id is used to generate unique object id for each object. -std::string GCode::set_object_info(Print *print) { +std::string GCode::set_object_info(Print* print) +{ const auto gflavor = print->config().gcode_flavor.value; if (print->is_BBL_printer() || (gflavor != gcfKlipper && gflavor != gcfMarlinLegacy && gflavor != gcfMarlinFirmware && gflavor != gcfRepRapFirmware)) return ""; std::ostringstream gcode; - size_t object_id = 0; + size_t object_id = 0; // Orca: check if we are in pa calib mode if (print->calib_mode() == CalibMode::Calib_PA_Line || print->calib_mode() == CalibMode::Calib_PA_Pattern) { BoundingBoxf bbox_bed(print->config().printable_area.values); @@ -6774,58 +6700,37 @@ std::string GCode::set_object_info(Print *print) { } // convert a model-space scaled point into G-code coordinates -Vec2d GCode::point_to_gcode(const Point &point) const +Vec2d GCode::point_to_gcode(const Point& point) const { - static std::atomic call_count{0}; - int this_call = ++call_count; - - Vec2d extruder_offset = Vec2d::Zero(); - if (const Extruder *extruder = m_writer.extruder(); extruder) { - extruder_offset = m_config.extruder_offset.get_at(m_writer.get_physical_extruder(extruder->id())); - } - - Vec2d result = unscale(point) + m_origin - extruder_offset; - - if (std::isnan(result.x()) || std::isinf(result.x()) || std::isnan(result.y()) || std::isinf(result.y())) { - Vec2d unscaled_val = unscale(point); - BOOST_LOG_TRIVIAL(error) << "SM Orca: point_to_gcode produced NaN/inf" - << " extruder=" << (m_writer.extruder() ? m_writer.extruder()->id() : -1) - << " physical_extruder=" << (m_writer.extruder() ? m_writer.get_physical_extruder(m_writer.extruder()->id()) : -1) - << " point=(" << point.x() << ", " << point.y() << ")" - << " unscaled=(" << unscaled_val.x() << ", " << unscaled_val.y() << ")" - << " m_origin=(" << m_origin.x() << ", " << m_origin.y() << ")" - << " extruder_offset=(" << extruder_offset.x() << ", " << extruder_offset.y() << ")" - << " result=(" << result.x() << ", " << result.y() << ")"; - } - - return result; + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); + return unscale(point) + m_origin - extruder_offset; } // convert a model-space scaled point into G-code coordinates -Point GCode::gcode_to_point(const Vec2d &point) const +Point GCode::gcode_to_point(const Vec2d& point) const { Vec2d pt = point - m_origin; - if (const Extruder *extruder = m_writer.extruder(); extruder) + if (const Extruder* extruder = m_writer.extruder(); extruder) // This function may be called at the very start from toolchange G-code when the extruder is not assigned yet. - pt += m_config.extruder_offset.get_at(m_writer.get_physical_extruder(extruder->id())); + pt += m_config.extruder_offset.get_at(extruder->id()); return scaled(pt); - } Vec2d GCode::point_to_gcode_quantized(const Point& point) const { Vec2d p = this->point_to_gcode(point); - return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) }; + return {GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y())}; } // Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed // during infill/perimeter wiping, or normally (depends on wiping_entities parameter) // Fills in by_region_per_copy_cache and returns its reference. -const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const +const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy( + std::vector& by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const { bool has_overrides = false; for (const auto& reg : by_region) - if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { + if (!reg.infills_overrides.empty() || !reg.perimeters_overrides.empty()) { has_overrides = true; break; } @@ -6833,7 +6738,7 @@ const std::vector& GCode::ObjectByExtru // Data is cleared, but the memory is not. by_region_per_copy_cache.clear(); - if (! has_overrides) + if (!has_overrides) // Simple case. No need to copy the regions. return wiping_entities ? by_region_per_copy_cache : this->by_region; @@ -6847,16 +6752,17 @@ const std::vector& GCode::ObjectByExtru // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed // References are used so that we don't have to repeat the same code for (int iter = 0; iter < 2; ++iter) { - const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters); - ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); - const std::vector& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); + const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters); + ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); + const std::vector& overrides = (iter ? reg.infills_overrides : + reg.perimeters_overrides); // Now the most important thing - which extrusion should we print. // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. if (wiping_entities) { // Apply overrides for this region. - for (unsigned int i = 0; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + for (unsigned int i = 0; i < overrides.size(); ++i) { + const WipingExtrusions::ExtruderPerCopy* this_override = overrides[i]; // This copy (aka object instance) should be printed with this extruder, which overrides the default one. if (this_override != nullptr && (*this_override)[copy] == int(extruder)) target_eec.emplace_back(entities[i]); @@ -6864,13 +6770,13 @@ const std::vector& GCode::ObjectByExtru } else { // Apply normal extrusions (non-overrides) for this region. unsigned int i = 0; - for (; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + for (; i < overrides.size(); ++i) { + const WipingExtrusions::ExtruderPerCopy* this_override = overrides[i]; // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. - if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) + if (this_override == nullptr || (*this_override)[copy] == -int(extruder) - 1) target_eec.emplace_back(entities[i]); } - for (; i < entities.size(); ++ i) + for (; i < entities.size(); ++i) target_eec.emplace_back(entities[i]); } } @@ -6880,23 +6786,25 @@ const std::vector& GCode::ObjectByExtru // This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter) // It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy. -void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder) +void GCode::ObjectByExtruder::Island::Region::append(const Type type, + const ExtrusionEntityCollection* eec, + const WipingExtrusions::ExtruderPerCopy* copies_extruder) { - // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves: - ExtrusionEntitiesPtr* perimeters_or_infills; - std::vector* perimeters_or_infills_overrides; + // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to + // not repeat ourselves: + ExtrusionEntitiesPtr* perimeters_or_infills; + std::vector* perimeters_or_infills_overrides; switch (type) { case PERIMETERS: - perimeters_or_infills = &perimeters; + perimeters_or_infills = &perimeters; perimeters_or_infills_overrides = &perimeters_overrides; break; case INFILL: - perimeters_or_infills = &infills; + perimeters_or_infills = &infills; perimeters_or_infills_overrides = &infills_overrides; break; - default: - throw Slic3r::InvalidArgument("Unknown parameter!"); + default: throw Slic3r::InvalidArgument("Unknown parameter!"); } // First we append the entities, there are eec->entities.size() of them: diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index b163a6e134..fc1298f4fe 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -114,7 +114,7 @@ private: std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const; // Postprocesses gcode: rotates and moves G1 extrusions and returns result - std::string post_process_wipe_tower_moves(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const; + std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const; // Left / right edges of the wipe tower, for the planning of wipe moves. const float m_left; const float m_right; @@ -197,13 +197,6 @@ public: //BBS: set offset for gcode writer void set_gcode_offset(double x, double y) { m_writer.set_xy_offset(x, y); m_processor.set_xy_offset(x, y);} - // SM Orca: Set filament-extruder mapping - void set_filament_extruder_map(const std::unordered_map& map) { - m_writer.set_filament_extruder_map(map); - m_processor.set_filament_extruder_map(map); - if (m_cooling_buffer) m_cooling_buffer->set_filament_extruder_map(map); - } - // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Vec2d& origin() const { return m_origin; } void set_origin(const Vec2d &pointf); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 4c3d8ca80f..e412cf3e67 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -482,11 +482,8 @@ static inline float get_default_perimeter_spacing(const PrintObject &print_objec std::vector printing_extruders = print_object.object_extruders(); assert(!printing_extruders.empty()); float avg_extruder = 0; - for(unsigned int extruder_id : printing_extruders) { - // SM Orca: nozzle_diameter是物理挤出机参数,使用physical_extruder_id访问 - int physical_extruder_id = print_object.print()->get_physical_extruder(extruder_id); - avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(physical_extruder_id))); - } + for(unsigned int extruder_id : printing_extruders) + avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id))); avg_extruder /= printing_extruders.size(); return avg_extruder; } diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 59b34d7938..9541d68034 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -340,14 +340,12 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { PerExtruderAdjustments &adj = per_extruder_adjustments[i]; unsigned int extruder_id = m_extruder_ids[i]; - // SM Orca: 冷却参数都是物理挤出机参数(无耗材覆盖),使用physical_extruder_id访问 - int physical_extruder_id = get_physical_extruder(extruder_id); adj.extruder_id = extruder_id; - adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(physical_extruder_id); - adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(physical_extruder_id)); - adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(physical_extruder_id)); + adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id); + adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id)); + adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id)); // ORCA: To enable dont slow down external perimeters feature per filament (extruder) - adj.dont_slow_down_outer_wall = m_config.dont_slow_down_outer_wall.get_at(physical_extruder_id); + adj.dont_slow_down_outer_wall = m_config.dont_slow_down_outer_wall.get_at(extruder_id); map_extruder_to_per_extruder_adjustment[extruder_id] = i; } @@ -733,8 +731,7 @@ std::string CoolingBuffer::apply_layer_cooldown( &supp_interface_fan_control, &supp_interface_fan_speed, &ironing_fan_control, &ironing_fan_speed ](bool immediately_apply) { - // SM Orca: 风扇参数都是物理挤出机参数(无耗材覆盖),使用physical_extruder_id访问 -#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(get_physical_extruder(m_current_extruder)) +#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) float fan_min_speed = EXTRUDER_CONFIG(fan_min_speed); float fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0; //BBS diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 4c5222a972..fba27b289b 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -27,13 +27,6 @@ public: void reset(const Vec3d &position); void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); - // SM Orca: Set filament to physical extruder mapping for correct parameter access - void set_filament_extruder_map(const std::unordered_map& map) { m_filament_extruder_map = map; } - // SM Orca: Get physical extruder ID from filament ID - int get_physical_extruder(int filament_idx) const { - auto it = m_filament_extruder_map.find(filament_idx); - return (it != m_filament_extruder_map.end()) ? it->second : filament_idx; - } private: CoolingBuffer& operator=(const CoolingBuffer&) = delete; @@ -62,8 +55,6 @@ private: // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required. const PrintConfig &m_config; unsigned int m_current_extruder; - // SM Orca: Filament to physical extruder mapping for correct parameter access - std::unordered_map m_filament_extruder_map; //BBS: current fan speed int m_current_fan_speed; }; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index b87e1f4518..f770c8579f 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -94,6 +94,7 @@ const std::vector GCodeProcessor::Reserved_Tags_compatible = { " PA_CHANGE:" }; + const std::string GCodeProcessor::Flush_Start_Tag = " FLUSH_START"; const std::string GCodeProcessor::Flush_End_Tag = " FLUSH_END"; @@ -396,6 +397,7 @@ void GCodeProcessor::TimeProcessor::reset() filament_unload_times = 0.0f; machine_tool_change_time = 0.0f; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { machines[i].reset(); } @@ -455,6 +457,7 @@ void GCodeProcessor::UsedFilaments::process_color_change_cache() } } + void GCodeProcessor::UsedFilaments::process_total_volume_cache(GCodeProcessor* processor) { size_t active_extruder_id = processor->m_extruder_id; @@ -523,17 +526,9 @@ void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor if (role_cache != 0.0f) { std::pair filament = { 0.0f, 0.0f }; - float diameter = (static_cast(processor->m_extruder_id) < processor->m_result.filament_diameters.size()) - ? processor->m_result.filament_diameters[processor->m_extruder_id] - : processor->m_result.filament_diameters.back(); - - float density = (static_cast(processor->m_extruder_id) < processor->m_result.filament_densities.size()) - ? processor->m_result.filament_densities[processor->m_extruder_id] - : processor->m_result.filament_densities.back(); - - double s = PI * sqr(0.5 * diameter); + double s = PI * sqr(0.5 * processor->m_result.filament_diameters[processor->m_extruder_id]); filament.first = role_cache / s * 0.001; - filament.second = role_cache * density * 0.001; + filament.second = role_cache * processor->m_result.filament_densities[processor->m_extruder_id] * 0.001; ExtrusionRole active_role = processor->m_extrusion_role; if (filaments_per_role.find(active_role) != filaments_per_role.end()) { @@ -561,20 +556,6 @@ void GCodeProcessorResult::reset() { //BBS: add mutex for protection of gcode result lock(); - size_t saved_count = extruders_count; - - if (saved_count == 0 || saved_count > 256) { - // 尝试从已有数组大小推断(优先使用filament_diameters的大小) - if (!filament_diameters.empty() && filament_diameters.size() <= 256) { - saved_count = filament_diameters.size(); - << saved_count; - } else { - // 对于只有少量耗材的用户,稍微多分配一些内存影响很小 - saved_count = 16; - << saved_count << " (was " << extruders_count << ", array size was " << filament_diameters.size() << ")"; - } - } - moves = std::vector(); printable_area = Pointfs(); //BBS: add bed exclude area @@ -586,19 +567,10 @@ void GCodeProcessorResult::reset() { timelapse_warning_code = 0; printable_height = 0.0f; settings_ids.reset(); - - extruders_count = saved_count; + extruders_count = 0; extruder_colors = std::vector(); - - filament_diameters = std::vector(saved_count, DEFAULT_FILAMENT_DIAMETER); - filament_densities = std::vector(saved_count, DEFAULT_FILAMENT_DENSITY); - filament_costs = std::vector(saved_count, DEFAULT_FILAMENT_COST); - required_nozzle_HRC = std::vector(saved_count, DEFAULT_FILAMENT_HRC); - filament_vitrification_temperature = std::vector(saved_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE); - - << " (original extruders_count=" << (saved_count == extruders_count ? "preserved" : "inferred") - << ", this=" << this << ")"; - + filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); + filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); time = 0; @@ -611,18 +583,6 @@ void GCodeProcessorResult::reset() { //BBS: add mutex for protection of gcode result lock(); - size_t saved_count = extruders_count; - - if (saved_count == 0 || saved_count > 256) { - // 尝试从已有数组大小推断(优先使用filament_diameters的大小) - if (!filament_diameters.empty() && filament_diameters.size() <= 256) { - saved_count = filament_diameters.size(); - } else { - // 对于只有少量耗材的用户,稍微多分配一些内存影响很小 - saved_count = 16; - } - } - moves.clear(); lines_ends.clear(); printable_area = Pointfs(); @@ -636,17 +596,13 @@ void GCodeProcessorResult::reset() { timelapse_warning_code = 0; printable_height = 0.0f; settings_ids.reset(); + extruders_count = 0; backtrace_enabled = false; extruder_colors = std::vector(); - - extruders_count = saved_count; - - filament_diameters = std::vector(saved_count, DEFAULT_FILAMENT_DIAMETER); - required_nozzle_HRC = std::vector(saved_count, DEFAULT_FILAMENT_HRC); - filament_densities = std::vector(saved_count, DEFAULT_FILAMENT_DENSITY); - filament_costs = std::vector(saved_count, DEFAULT_FILAMENT_COST); - filament_vitrification_temperature = std::vector(saved_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE); - + filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); + required_nozzle_HRC = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_HRC); + filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); + filament_costs = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); bed_match_result = BedMatchResult(true); @@ -763,40 +719,6 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_preheat_steps = 1; m_result.backtrace_enabled = m_preheat_time > 0 && (m_is_XL_printer || (!m_single_extruder_multi_material && extruders_count > 1)); - size_t physical_extruder_count = config.extruder_offset.values.size(); - - // 验证各配置数组大小 - if (config.filament_density.values.size() < extruders_count) { - BOOST_LOG_TRIVIAL(warning) << "SM Orca: filament_density has " - << config.filament_density.values.size() << " values, expected " << extruders_count - << " (will use fallback values)"; - } - - if (config.filament_cost.values.size() < extruders_count) { - BOOST_LOG_TRIVIAL(warning) << "SM Orca: filament_cost has " - << config.filament_cost.values.size() << " values, expected " << extruders_count - << " (will use fallback values)"; - } - - if (config.nozzle_temperature.values.size() < extruders_count) { - BOOST_LOG_TRIVIAL(warning) << "SM Orca: nozzle_temperature has " - << config.nozzle_temperature.values.size() << " values, expected " << extruders_count - << " (will use fallback values)"; - } - - // 验证映射表有效性 - if (!m_filament_extruder_map.empty()) { - for (size_t i = 0; i < extruders_count; ++i) { - int physical_extruder = get_physical_extruder(i); - if (physical_extruder < 0 || - physical_extruder >= static_cast(physical_extruder_count)) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament " << i - << " maps to invalid physical extruder " << physical_extruder - << " (valid range: 0-" << (physical_extruder_count - 1) << ")"; - } - } - } - m_extruder_offsets.resize(extruders_count); m_extruder_colors.resize(extruders_count); m_result.filament_diameters.resize(extruders_count); @@ -809,94 +731,20 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_extruder_temps_first_layer_config.resize(extruders_count); m_result.nozzle_hrc = static_cast(config.nozzle_hrc.getInt()); m_result.nozzle_type = config.nozzle_type; - - size_t diameter_count = config.filament_diameter.values.size(); - size_t density_count = config.filament_density.values.size(); - size_t cost_count = config.filament_cost.values.size(); - size_t temp_initial_count = config.nozzle_temperature_initial_layer.values.size(); - size_t temp_count = config.nozzle_temperature.values.size(); - size_t hrc_count = config.required_nozzle_HRC.values.size(); - size_t vitrification_count = config.temperature_vitrification.values.size(); - for (size_t i = 0; i < extruders_count; ++ i) { - int physical_extruder = get_physical_extruder(i); - - if (physical_extruder < 0 || physical_extruder >= static_cast(physical_extruder_count)) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament " << i - << " maps to invalid physical extruder " << physical_extruder - << " (valid range: 0-" << (physical_extruder_count - 1) << ")"; - // 使用filament index作为fallback(1:1映射),如果也越界则使用0 - physical_extruder = (i < physical_extruder_count) ? static_cast(i) : 0; - BOOST_LOG_TRIVIAL(error) << " SM Orca: Using fallback physical extruder " << physical_extruder; - } - - m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(physical_extruder).cast().eval(), 0.f); + m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast().eval(), 0.f); m_extruder_colors[i] = static_cast(i); - - // 温度是挤出机属性,使用 physical_extruder 而不是 filament index - if (physical_extruder < static_cast(temp_initial_count)) { - m_extruder_temps_first_layer_config[i] = static_cast(config.nozzle_temperature_initial_layer.get_at(physical_extruder)); - } else { - int fallback = temp_initial_count > 0 ? - static_cast(config.nozzle_temperature_initial_layer.get_at(temp_initial_count - 1)) : 210; - m_extruder_temps_first_layer_config[i] = fallback; - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " (physical extruder " << physical_extruder << ") initial layer temperature not configured, using " << fallback; - } - - if (physical_extruder < static_cast(temp_count)) { - m_extruder_temps_config[i] = static_cast(config.nozzle_temperature.get_at(physical_extruder)); - } else { - int fallback = temp_count > 0 ? - static_cast(config.nozzle_temperature.get_at(temp_count - 1)) : 210; - m_extruder_temps_config[i] = fallback; - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " (physical extruder " << physical_extruder << ") temperature not configured, using " << fallback; - } - + m_extruder_temps_first_layer_config[i] = static_cast(config.nozzle_temperature_initial_layer.get_at(i)); + m_extruder_temps_config[i] = static_cast(config.nozzle_temperature.get_at(i)); if (m_extruder_temps_config[i] == 0) { // This means the value should be ignored and first layer temp should be used. m_extruder_temps_config[i] = m_extruder_temps_first_layer_config[i]; } - - if (i < diameter_count) { - m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); - } else { - float fallback = diameter_count > 0 ? - static_cast(config.filament_diameter.get_at(diameter_count - 1)) : 1.75f; - m_result.filament_diameters[i] = fallback; - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " diameter not configured, using " << fallback << "mm"; - } - - if (i < hrc_count) { - m_result.required_nozzle_HRC[i] = static_cast(config.required_nozzle_HRC.get_at(i)); - } else { - int fallback = hrc_count > 0 ? - static_cast(config.required_nozzle_HRC.get_at(hrc_count - 1)) : 0; - m_result.required_nozzle_HRC[i] = fallback; - } - - if (i < density_count) { - m_result.filament_densities[i] = static_cast(config.filament_density.get_at(i)); - } else { - float fallback = density_count > 0 ? - static_cast(config.filament_density.get_at(density_count - 1)) : 1.25f; - m_result.filament_densities[i] = fallback; - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " density not configured, using " << fallback << " g/cm³"; - } - - if (i < vitrification_count) { - m_result.filament_vitrification_temperature[i] = static_cast(config.temperature_vitrification.get_at(i)); - } else { - int fallback = vitrification_count > 0 ? - static_cast(config.temperature_vitrification.get_at(vitrification_count - 1)) : 0; - m_result.filament_vitrification_temperature[i] = fallback; - } - - if (i < cost_count) { - m_result.filament_costs[i] = static_cast(config.filament_cost.get_at(i)); - } else { - m_result.filament_costs[i] = 0.0f; - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " cost not configured, using 0.0"; - } + m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); + m_result.required_nozzle_HRC[i] = static_cast(config.required_nozzle_HRC.get_at(i)); + m_result.filament_densities[i] = static_cast(config.filament_density.get_at(i)); + m_result.filament_vitrification_temperature[i] = static_cast(config.temperature_vitrification.get_at(i)); + m_result.filament_costs[i] = static_cast(config.filament_cost.get_at(i)); } if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfKlipper || m_flavor == gcfRepRapFirmware) { @@ -1010,35 +858,24 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionFloats* filament_diameters = config.option("filament_diameter"); if (filament_diameters != nullptr) { - size_t config_size = filament_diameters->values.size(); - - if (m_result.filament_diameters.size() < m_result.extruders_count) { - m_result.filament_diameters.resize(m_result.extruders_count, DEFAULT_FILAMENT_DIAMETER); - } - - for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) { + m_result.filament_diameters.clear(); + m_result.filament_diameters.resize(filament_diameters->values.size()); + for (size_t i = 0; i < filament_diameters->values.size(); ++i) { m_result.filament_diameters[i] = static_cast(filament_diameters->values[i]); } - - if (config_size > 0 && config_size < m_result.extruders_count) { - float last_value = static_cast(filament_diameters->values[config_size - 1]); - for (size_t i = config_size; i < m_result.extruders_count; ++i) { - m_result.filament_diameters[i] = last_value; - BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i - << " diameter not in config, using last value " << last_value << "mm"; - } - } } if (m_result.filament_diameters.size() < m_result.extruders_count) { - m_result.filament_diameters.resize(m_result.extruders_count, DEFAULT_FILAMENT_DIAMETER); + for (size_t i = m_result.filament_diameters.size(); i < m_result.extruders_count; ++i) { + m_result.filament_diameters.emplace_back(DEFAULT_FILAMENT_DIAMETER); + } } const ConfigOptionInts *filament_HRC = config.option("required_nozzle_HRC"); if (filament_HRC != nullptr) { m_result.required_nozzle_HRC.clear(); m_result.required_nozzle_HRC.resize(filament_HRC->values.size()); - for (size_t i = 0; i < filament_HRC->values.size(); ++i) { m_result.required_nozzle_HRC[i] = static_cast(filament_HRC->values[i]); } + for (size_t i = 0; i < filament_HRC->values.size(); ++i) { m_result.required_nozzle_HRC[i] = static_cast(filament_HRC->values[i]); } } if (m_result.required_nozzle_HRC.size() < m_result.extruders_count) { @@ -1048,75 +885,43 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionFloats* filament_densities = config.option("filament_density"); if (filament_densities != nullptr) { - size_t config_size = filament_densities->values.size(); - - if (m_result.filament_densities.size() < m_result.extruders_count) { - m_result.filament_densities.resize(m_result.extruders_count, DEFAULT_FILAMENT_DENSITY); - } - - for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) { + m_result.filament_densities.clear(); + m_result.filament_densities.resize(filament_densities->values.size()); + for (size_t i = 0; i < filament_densities->values.size(); ++i) { m_result.filament_densities[i] = static_cast(filament_densities->values[i]); } - - if (config_size > 0 && config_size < m_result.extruders_count) { - float last_value = static_cast(filament_densities->values[config_size - 1]); - for (size_t i = config_size; i < m_result.extruders_count; ++i) { - m_result.filament_densities[i] = last_value; - BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i - << " density not in config, using last value " << last_value << " g/cm³"; - } - } } if (m_result.filament_densities.size() < m_result.extruders_count) { - m_result.filament_densities.resize(m_result.extruders_count, DEFAULT_FILAMENT_DENSITY); + for (size_t i = m_result.filament_densities.size(); i < m_result.extruders_count; ++i) { + m_result.filament_densities.emplace_back(DEFAULT_FILAMENT_DENSITY); + } } //BBS const ConfigOptionFloats* filament_costs = config.option("filament_cost"); if (filament_costs != nullptr) { - size_t config_size = filament_costs->values.size(); - - if (m_result.filament_costs.size() < m_result.extruders_count) { - m_result.filament_costs.resize(m_result.extruders_count, DEFAULT_FILAMENT_COST); - } - - for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) - m_result.filament_costs[i] = static_cast(filament_costs->values[i]); - - if (config_size < m_result.extruders_count) { - for (size_t i = config_size; i < m_result.extruders_count; ++i) { - m_result.filament_costs[i] = DEFAULT_FILAMENT_COST; - BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i - << " cost not in config, using default " << DEFAULT_FILAMENT_COST; - } - } + m_result.filament_costs.clear(); + m_result.filament_costs.resize(filament_costs->values.size()); + for (size_t i = 0; i < filament_costs->values.size(); ++i) + m_result.filament_costs[i]=static_cast(filament_costs->values[i]); } - - if (m_result.filament_costs.size() < m_result.extruders_count) { - m_result.filament_costs.resize(m_result.extruders_count, DEFAULT_FILAMENT_COST); + for (size_t i = m_result.filament_costs.size(); i < m_result.extruders_count; ++i) { + m_result.filament_costs.emplace_back(DEFAULT_FILAMENT_COST); } //BBS const ConfigOptionInts* filament_vitrification_temperature = config.option("temperature_vitrification"); if (filament_vitrification_temperature != nullptr) { - size_t config_size = filament_vitrification_temperature->values.size(); - - if (m_result.filament_vitrification_temperature.size() < m_result.extruders_count) { - m_result.filament_vitrification_temperature.resize(m_result.extruders_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE); - } - - for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) { + m_result.filament_vitrification_temperature.clear(); + m_result.filament_vitrification_temperature.resize(filament_vitrification_temperature->values.size()); + for (size_t i = 0; i < filament_vitrification_temperature->values.size(); ++i) { m_result.filament_vitrification_temperature[i] = static_cast(filament_vitrification_temperature->values[i]); } - - if (config_size > 0 && config_size < m_result.extruders_count) { - int last_value = static_cast(filament_vitrification_temperature->values[config_size - 1]); - for (size_t i = config_size; i < m_result.extruders_count; ++i) { - m_result.filament_vitrification_temperature[i] = last_value; - BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i - << " vitrification temperature not in config, using last value " << last_value; - } + } + if (m_result.filament_vitrification_temperature.size() < m_result.extruders_count) { + for (size_t i = m_result.filament_vitrification_temperature.size(); i < m_result.extruders_count; ++i) { + m_result.filament_vitrification_temperature.emplace_back(DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE); } } @@ -1132,32 +937,17 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } else { - size_t physical_count = extruder_offset->values.size(); - if (m_extruder_offsets.size() < m_result.extruders_count) { - m_extruder_offsets.resize(m_result.extruders_count, DEFAULT_EXTRUDER_OFFSET); - } - - // 只更新物理挤出机的offset - for (size_t i = 0; i < physical_count && i < m_extruder_offsets.size(); ++i) { + m_extruder_offsets.resize(extruder_offset->values.size()); + for (size_t i = 0; i < extruder_offset->values.size(); ++i) { Vec2f offset = extruder_offset->values[i].cast(); m_extruder_offsets[i] = { offset(0), offset(1), 0.0f }; } } } - + if (m_extruder_offsets.size() < m_result.extruders_count) { - size_t physical_count = m_extruder_offsets.size(); for (size_t i = m_extruder_offsets.size(); i < m_result.extruders_count; ++i) { - int physical_extruder = get_physical_extruder(i); - // 如果映射的物理挤出机索引在有效范围内,复用它的offset - if (physical_extruder >= 0 && physical_extruder < static_cast(physical_count)) { - m_extruder_offsets.emplace_back(m_extruder_offsets[physical_extruder]); - BOOST_LOG_TRIVIAL(debug) << "Filament " << i << " using offset from physical extruder " << physical_extruder; - } else { - // 否则使用默认offset - m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET); - BOOST_LOG_TRIVIAL(warning) << "Filament " << i << " using default offset (physical extruder " << physical_extruder << " out of range)"; - } + m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET); } } @@ -1201,6 +991,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_tool_change_time != nullptr) m_time_processor.machine_tool_change_time = static_cast(machine_tool_change_time->value); + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfKlipper) { const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); if (machine_max_acceleration_x != nullptr) @@ -1262,6 +1053,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_max_acceleration_retracting != nullptr) m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; + // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. const ConfigOptionFloats* machine_max_acceleration_travel = config.option(m_flavor == gcfMarlinLegacy || m_flavor == gcfKlipper ? "machine_max_acceleration_extruding" @@ -1269,6 +1061,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_max_acceleration_travel != nullptr) m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values; + const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); if (machine_min_extruding_rate != nullptr) m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; @@ -1320,32 +1113,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (bed_type != nullptr) m_result.bed_type = (BedType)bed_type->value; + const ConfigOptionFloat* z_offset = config.option("z_offset"); if (z_offset != nullptr) m_z_offset = z_offset->value; - - bool arrays_valid = true; - if (m_result.filament_diameters.size() != m_result.extruders_count) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_diameters size mismatch: " - << m_result.filament_diameters.size() << " != " << m_result.extruders_count; - arrays_valid = false; - } - if (m_result.filament_densities.size() != m_result.extruders_count) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_densities size mismatch: " - << m_result.filament_densities.size() << " != " << m_result.extruders_count; - arrays_valid = false; - } - if (m_result.filament_costs.size() != m_result.extruders_count) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_costs size mismatch: " - << m_result.filament_costs.size() << " != " << m_result.extruders_count; - arrays_valid = false; - } - if (m_result.filament_vitrification_temperature.size() != m_result.extruders_count) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_vitrification_temperature size mismatch: " - << m_result.filament_vitrification_temperature.size() << " != " << m_result.extruders_count; - arrays_valid = false; - } - } void GCodeProcessor::enable_stealth_time_estimator(bool enabled) @@ -1785,22 +1556,6 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool // update start position m_start_position = m_end_position; - if (std::isnan(m_start_position[X]) || std::isinf(m_start_position[X]) || - std::isnan(m_start_position[Y]) || std::isinf(m_start_position[Y]) || - std::isnan(m_start_position[Z]) || std::isinf(m_start_position[Z])) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Detected invalid m_start_position at line " << m_line_id - << " extruder=" << static_cast(m_extruder_id) - << " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")" - << " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")"; - // 重置为原点,防止污染继续传播 - m_start_position[X] = std::isnan(m_start_position[X]) || std::isinf(m_start_position[X]) ? 0.0f : m_start_position[X]; - m_start_position[Y] = std::isnan(m_start_position[Y]) || std::isinf(m_start_position[Y]) ? 0.0f : m_start_position[Y]; - m_start_position[Z] = std::isnan(m_start_position[Z]) || std::isinf(m_start_position[Z]) ? 0.0f : m_start_position[Z]; - m_end_position[X] = std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) ? 0.0f : m_end_position[X]; - m_end_position[Y] = std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) ? 0.0f : m_end_position[Y]; - m_end_position[Z] = std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z]) ? 0.0f : m_end_position[Z]; - } - const std::string_view cmd = line.cmd(); if (m_flavor == gcfKlipper) { @@ -2880,21 +2635,6 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o m_end_position[a] = absolute_position((Axis)a, line); } - if (std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) || - std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) || - std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z])) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_end_position after G1 processing for extruder " << static_cast(m_extruder_id) - << " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")" - << " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")" - << " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")" - << " has_X=" << line.has(X) << " has_Y=" << line.has(Y) << " has_Z=" << line.has(Z) - << " X_value=" << (line.has(X) ? line.value(X) : 0.0f) - << " Y_value=" << (line.has(Y) ? line.value(Y) : 0.0f) - << " Z_value=" << (line.has(Z) ? line.value(Z) : 0.0f) - << " positioning=" << (m_global_positioning_type == EPositioningType::Relative ? "relative" : "absolute") - << " units=" << (m_units == EUnits::Inches ? "inches" : "mm"); - } - // updates feedrate from line, if present if (line.has_f()) m_feedrate = line.f() * MMMIN_TO_MMSEC; @@ -2958,32 +2698,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o else if (m_extrusion_role == erExternalPerimeter) // cross section: rectangle m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); - else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone) { - float diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) - ? m_result.filament_diameters[m_extruder_id] - : m_result.filament_diameters.back(); - - float ratio = delta_pos[E] / delta_xyz; - if (ratio < 0.0f) { - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Negative E/XYZ ratio (" << ratio - << ") for extruder " << m_extruder_id << ", using absolute value"; - ratio = std::abs(ratio); - } - + else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone) // cross section: circle - m_width = diameter * std::sqrt(ratio); - } + m_width = static_cast(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); else // cross section: rectangle + 2 semicircles m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; - if (std::isnan(m_width) || std::isinf(m_width)) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid width calculated: " << m_width - << " for extruder " << m_extruder_id - << " (E=" << delta_pos[E] << ", XYZ=" << delta_xyz << ")"; - m_width = DEFAULT_TOOLPATH_WIDTH; - } - if (m_width == 0.0f) m_width = DEFAULT_TOOLPATH_WIDTH; @@ -3192,6 +2913,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o // axis reversal std::max(-v_exit, v_entry)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (jerk > axis_max_jerk) { v_factor *= axis_max_jerk / jerk; @@ -3364,22 +3086,6 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) for (unsigned char a = X; a <= E; ++a) { m_end_position[a] = absolute_position((Axis)a, line); } - - if (std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) || - std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) || - std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z])) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_end_position after G2/G3 processing for extruder " << static_cast(m_extruder_id) - << " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")" - << " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")" - << " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")" - << " has_X=" << line.has(X) << " has_Y=" << line.has(Y) << " has_Z=" << line.has(Z) - << " X_value=" << (line.has(X) ? line.value(X) : 0.0f) - << " Y_value=" << (line.has(Y) ? line.value(Y) : 0.0f) - << " Z_value=" << (line.has(Z) ? line.value(Z) : 0.0f) - << " positioning=" << (m_global_positioning_type == EPositioningType::Relative ? "relative" : "absolute") - << " units=" << (m_units == EUnits::Inches ? "inches" : "mm"); - } - //BBS: G2 G3 line but has no I and J axis, invalid G code format if (!line.has(I) && !line.has(J)) return; @@ -3424,6 +3130,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) EMoveType type = move_type(delta_pos[E]); + const float delta_xyz = std::sqrt(sqr(arc_length) + sqr(delta_pos[Z])); m_travel_dist = delta_xyz; if (type == EMoveType::Extrude) { @@ -3471,32 +3178,13 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) else if (m_extrusion_role == erExternalPerimeter) //BBS: cross section: rectangle m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); - else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone) { - float diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) - ? m_result.filament_diameters[m_extruder_id] - : m_result.filament_diameters.back(); - - float ratio = delta_pos[E] / delta_xyz; - if (ratio < 0.0f) { - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Negative E/XYZ ratio (" << ratio - << ") for extruder " << m_extruder_id << ", using absolute value"; - ratio = std::abs(ratio); - } - + else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone) //BBS: cross section: circle - m_width = diameter * std::sqrt(ratio); - } + m_width = static_cast(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); else //BBS: cross section: rectangle + 2 semicircles m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; - if (std::isnan(m_width) || std::isinf(m_width)) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid width calculated: " << m_width - << " for extruder " << m_extruder_id - << " (E=" << delta_pos[E] << ", XYZ=" << delta_xyz << ")"; - m_width = DEFAULT_TOOLPATH_WIDTH; - } - if (m_width == 0.0f) m_width = DEFAULT_TOOLPATH_WIDTH; @@ -3660,6 +3348,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) //BBS: axis reversal std::max(-v_exit, v_entry)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (jerk > axis_max_jerk) { v_factor *= axis_max_jerk / jerk; @@ -3885,25 +3574,12 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) simulate_st_synchronize(); if (!any_found && !line.has_unknown_axis()) { - // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510, + // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510, // where G92 A0 B0 is called although the extruder axis is till E. for (unsigned char a = X; a <= E; ++a) { m_origin[a] = m_end_position[a]; } } - - if (std::isnan(m_origin[X]) || std::isinf(m_origin[X]) || - std::isnan(m_origin[Y]) || std::isinf(m_origin[Y]) || - std::isnan(m_origin[Z]) || std::isinf(m_origin[Z])) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_origin after G92 processing" - << " extruder=" << static_cast(m_extruder_id) - << " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")" - << " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")"; - // 重置为0,防止污染继续传播 - m_origin[X] = std::isnan(m_origin[X]) || std::isinf(m_origin[X]) ? 0.0f : m_origin[X]; - m_origin[Y] = std::isnan(m_origin[Y]) || std::isinf(m_origin[Y]) ? 0.0f : m_origin[Y]; - m_origin[Z] = std::isnan(m_origin[Z]) || std::isinf(m_origin[Z]) ? 0.0f : m_origin[Z]; - } } void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line) @@ -4033,6 +3709,7 @@ void GCodeProcessor::process_M191(const GCodeReader::GCodeLine& line) simulate_st_synchronize(wait_chamber_temp_time); } + void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) { // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration @@ -4368,54 +4045,17 @@ void GCodeProcessor::run_post_process() double filament_total_cost = 0.0; for (const auto& [id, volume] : m_result.print_statistics.total_volumes_per_extruder) { - if (id >= m_result.filament_diameters.size() || - id >= m_result.filament_densities.size() || - id >= m_result.filament_costs.size()) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament index " << id << " out of bounds (sizes: " - << "diameter=" << m_result.filament_diameters.size() - << ", density=" << m_result.filament_densities.size() - << ", cost=" << m_result.filament_costs.size() << "), skipping cost calculation"; - continue; - } - - double diameter = m_result.filament_diameters[id]; - double density = m_result.filament_densities[id]; - double cost = m_result.filament_costs[id]; - - if (diameter <= 0.0 || std::isnan(diameter)) { - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament diameter " << diameter - << " for filament " << id << ", using default 1.75mm"; - diameter = 1.75; - } - - if (density <= 0.0 || std::isnan(density)) { - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament density " << density - << " for filament " << id << ", using default 1.25 g/cm³"; - density = 1.25; - } - - if (cost < 0.0 || std::isnan(cost)) { - BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament cost " << cost - << " for filament " << id << ", using 0.0"; - cost = 0.0; - } - - double cross_section = M_PI * sqr(0.5 * diameter); - filament_mm[id] = volume / cross_section; + filament_mm[id] = volume / (static_cast(M_PI) * sqr(0.5 * m_result.filament_diameters[id])); filament_cm3[id] = volume * 0.001; - filament_g[id] = filament_cm3[id] * density; - filament_cost[id] = filament_g[id] * cost * 0.001; - + filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]); + filament_cost[id] = filament_g[id] * double(m_result.filament_costs[id]) * 0.001; filament_total_g += filament_g[id]; filament_total_cost += filament_cost[id]; - - BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << id - << " - volume: " << volume << "mm³, length: " << filament_mm[id] - << "mm, weight: " << filament_g[id] << "g, cost: " << filament_cost[id]; } double total_g_wipe_tower = m_print->print_statistics().total_wipe_tower_filament; + auto time_in_minutes = [](float time_in_seconds) { assert(time_in_seconds >= 0.f); return int((time_in_seconds + 0.5f) / 60.0f); @@ -4543,6 +4183,7 @@ void GCodeProcessor::run_post_process() size_t m_times_cache_id{ 0 }; size_t m_out_file_pos{ 0 }; + public: ExportLines(EWriteType type, const std::array(PrintEstimatedStatistics::ETimeMode::Count)>& machines) @@ -5112,6 +4753,7 @@ void GCodeProcessor::run_post_process() export_lines.flush(out, m_result, out_path); + out.close(); in.close(); @@ -5129,10 +4771,6 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) m_line_id + 1 : ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); - Vec3f extruder_offset = (static_cast(m_extruder_id) < m_extruder_offsets.size()) - ? m_extruder_offsets[m_extruder_id] - : Vec3f(0.0f, 0.0f, 0.0f); - //BBS: apply plate's and extruder's offset to arc interpolation points if (path_type == EMovePathType::Arc_move_cw || path_type == EMovePathType::Arc_move_ccw) { @@ -5141,38 +4779,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) Vec3f(m_interpolation_points[i].x() + m_x_offset, m_interpolation_points[i].y() + m_y_offset, m_processing_start_custom_gcode ? m_first_layer_height : m_interpolation_points[i].z()) + - extruder_offset; - } - - if (std::isnan(m_width) || std::isinf(m_width)) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Blocking invalid width: " << m_width - << " (extruder " << m_extruder_id << ")"; - m_width = DEFAULT_TOOLPATH_WIDTH; - } - - if (std::isnan(m_height) || std::isinf(m_height)) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Blocking invalid height: " << m_height - << " (extruder " << m_extruder_id << ")"; - m_height = DEFAULT_TOOLPATH_HEIGHT; - } - - Vec3f final_position = Vec3f(m_end_position[X] + m_x_offset, - m_end_position[Y] + m_y_offset, - m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z] - m_z_offset) - + extruder_offset; - - if (std::isnan(final_position.x()) || std::isinf(final_position.x()) || - std::isnan(final_position.y()) || std::isinf(final_position.y()) || - std::isnan(final_position.z()) || std::isinf(final_position.z())) { - BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid position calculated for extruder " << static_cast(m_extruder_id) - << " position=(" << final_position.x() << ", " << final_position.y() << ", " << final_position.z() << ")" - << " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")" - << " offset=(" << m_x_offset << ", " << m_y_offset << ", " << m_z_offset << ")" - << " extruder_offset=(" << extruder_offset.x() << ", " << extruder_offset.y() << ", " << extruder_offset.z() << ")"; - // 使用不带offset的position作为fallback - final_position = Vec3f(m_end_position[X] + m_x_offset, - m_end_position[Y] + m_y_offset, - m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z] - m_z_offset); + m_extruder_offsets[m_extruder_id]; } m_result.moves.push_back({ @@ -5182,7 +4789,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) m_extruder_id, m_cp_color.current, //BBS: add plate's offset to the rendering vertices - final_position, + Vec3f(m_end_position[X] + m_x_offset, m_end_position[Y] + m_y_offset, m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]- m_z_offset) + m_extruder_offsets[m_extruder_id], static_cast(m_end_position[E] - m_start_position[E]), m_feedrate, m_width, diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index be13f4637d..64dc41e567 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -14,7 +14,6 @@ #include #include #include -#include namespace Slic3r { @@ -678,8 +677,6 @@ class Print; EPositioningType m_global_positioning_type; EPositioningType m_e_local_positioning_type; std::vector m_extruder_offsets; - // SM Orca: 耗材到物理挤出机的映射 - std::unordered_map m_filament_extruder_map; GCodeFlavor m_flavor; float m_nozzle_volume; AxisCoords m_start_position; // mm @@ -779,16 +776,6 @@ class Print; void apply_config(const PrintConfig& config); void set_print(Print* print) { m_print = print; } - // SM Orca: 设置耗材到物理挤出机的映射 - void set_filament_extruder_map(const std::unordered_map& map) { - m_filament_extruder_map = map; - } - // SM Orca: 获取物理挤出机ID(根据耗材索引) - int get_physical_extruder(int filament_idx) const { - auto it = m_filament_extruder_map.find(filament_idx); - int physical_extruder_id = (it != m_filament_extruder_map.end()) ? it->second : filament_idx; - return physical_extruder_id; - } void enable_stealth_time_estimator(bool enabled); bool is_stealth_time_estimator_enabled() const { return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 02bae5bd06..6bc0ddfeb9 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -668,20 +668,18 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi -void WipeTower::set_extruder(size_t idx, int physical_extruder, const PrintConfig& config) +void WipeTower::set_extruder(size_t idx, const PrintConfig& config) { //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector m_filpar.push_back(FilamentParameters()); - // SM Orca: 耗材属性使用 idx (filament index) m_filpar[idx].material = config.filament_type.get_at(idx); // m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx); m_filpar[idx].is_soluble = config.wipe_tower_filament == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_filament - 1)); // BBS m_filpar[idx].is_support = config.filament_is_support.get_at(idx); - // SM Orca: 温度是挤出机属性,使用 physical_extruder - m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(physical_extruder); - m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(physical_extruder); + m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(idx); + m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(idx); // If this is a single extruder MM printer, we will use all the SE-specific config values. // Otherwise, the defaults will be used to turn off the SE stuff. @@ -700,19 +698,14 @@ void WipeTower::set_extruder(size_t idx, int physical_extruder, const PrintConfi #endif m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point - // SM Orca: 喷嘴直径是挤出机属性,使用 physical_extruder - float nozzle_diameter = float(config.nozzle_diameter.get_at(physical_extruder)); + float nozzle_diameter = float(config.nozzle_diameter.get_at(idx)); m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx)); if (max_vol_speed!= 0.f) m_filpar[idx].max_e_speed = (max_vol_speed / filament_area()); - // SM Orca: Store per-filament perimeter width and also set the global one - // Note: m_perimeter_width gets overwritten with each set_extruder() call - // The brim should use m_filpar[0].perimeter_width for consistency - m_filpar[idx].perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; - m_perimeter_width = m_filpar[idx].perimeter_width; // all extruders are now assumed to have the same diameter + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter // BBS: remove useless config #if 0 if (m_semm) { @@ -1312,10 +1305,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool } // brim chamfer - // SM Orca: Use first filament's perimeter width for consistent brim spacing - // The brim is generated once for the entire wipe tower and should use a consistent spacing - float brim_perimeter_width = m_filpar.empty() ? m_perimeter_width : m_filpar[0].perimeter_width; - float spacing = brim_perimeter_width - m_layer_height * float(1. - M_PI_4); + float spacing = m_perimeter_width - m_layer_height * float(1. - M_PI_4); // How many perimeters shall the brim have? int loops_num = (m_wipe_tower_brim_width + spacing / 2.f) / spacing; const float max_chamfer_width = 3.f; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index de27b4dac0..3d8c71404e 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -144,8 +144,7 @@ public: // Set the extruder properties. - // SM Orca: 添加 physical_extruder 参数,用于支持耗材-挤出机映射 - void set_extruder(size_t idx, int physical_extruder, const PrintConfig& config); + void set_extruder(size_t idx, const PrintConfig& config); // Appends into internal structure m_plan containing info about the future wipe tower // to be used before building begins. The entries must be added ordered in z. @@ -270,8 +269,6 @@ public: std::vector ramming_speed; float nozzle_diameter; float filament_area; - // SM Orca: Store per-filament perimeter width for correct brim generation - float perimeter_width = 0.f; }; private: diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index d634cf61ac..2e7c12f6ea 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -20,15 +20,13 @@ #include +namespace Slic3r { -namespace Slic3r -{ - - float flat_iron_area = 4.f; +float flat_iron_area = 4.f; constexpr float flat_iron_speed = 10.f * 60.f; static const double wipe_tower_wall_infill_overlap = 0.0; static constexpr double WIPE_TOWER_RESOLUTION = 0.1; -static constexpr double WT_SIMPLIFY_TOLERANCE_SCALED = 0.001f / SCALING_FACTOR_INTERNAL; +static constexpr double WT_SIMPLIFY_TOLERANCE_SCALED = 0.001f / SCALING_FACTOR_INTERNAL; static constexpr int arc_fit_size = 20; #define SCALED_WIPE_TOWER_RESOLUTION (WIPE_TOWER_RESOLUTION / SCALING_FACTOR_INTERNAL) enum class LimitFlow { None, LimitPrintFlow, LimitRammingFlow }; @@ -104,7 +102,7 @@ Polygon chamfer_polygon(Polygon& polygon, double chamfer_dis = 2., double angle_ return res; } -Polygon rounding_polygon(Polygon& polygon, double rounding = 2., double angle_tol = 30. / 180. * PI) +Polygon rounding_polygon(Polygon& polygon, double rounding = 2., double angle_tol = 30. / 180. * PI) { if (polygon.points.size() < 3) return polygon; @@ -547,50 +545,48 @@ Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_m // Calculates length of extrusion line to extrude given volume static float volume_to_length(float volume, float line_width, float layer_height) { - return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); + return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); } static float length_to_volume(float length, float line_width, float layer_height) { - return std::max(0.f, length * layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))); + return std::max(0.f, length * layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))); } - - - class WipeTowerWriter2 { public: - WipeTowerWriter2(float layer_height, - float line_width, - GCodeFlavor flavor, + WipeTowerWriter2(float layer_height, + float line_width, + GCodeFlavor flavor, const std::vector& filament_parameters, - bool enable_arc_fitting, - const std::string& printer_model) - : - m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), - m_current_z(0.f), - m_current_feedrate(0.f), - m_layer_height(layer_height), - m_extrusion_flow(0.f), - m_preview_suppressed(false), - m_elapsed_time(0.f), - m_gcode_flavor(flavor), - m_filpar(filament_parameters), - m_printer_model(printer_model) + bool enable_arc_fitting, + const std::string& printer_model) + : m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()) + , m_current_z(0.f) + , m_current_feedrate(0.f) + , m_layer_height(layer_height) + , m_extrusion_flow(0.f) + , m_preview_suppressed(false) + , m_elapsed_time(0.f) + , m_gcode_flavor(flavor) + , m_filpar(filament_parameters) + , m_printer_model(printer_model) { - // ORCA: This class is only used by non BBL printers, so set the parameter appropriately. - // This fixes an issue where the wipe tower was using BBL tags resulting in statistics for purging in the purge tower not being displayed. - GCodeProcessor::s_IsBBLPrinter = false; - // adds tag for analyzer: - std::ostringstream str; - str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << m_layer_height << "\n"; // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming - str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << ExtrusionEntity::role_to_string(erWipeTower) << "\n"; - m_gcode += str.str(); - change_analyzer_line_width(line_width); + // ORCA: This class is only used by non BBL printers, so set the parameter appropriately. + // This fixes an issue where the wipe tower was using BBL tags resulting in statistics for purging in the purge tower not being displayed. + GCodeProcessor::s_IsBBLPrinter = false; + // adds tag for analyzer: + std::ostringstream str; + str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << m_layer_height + << "\n"; // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming + str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << ExtrusionEntity::role_to_string(erWipeTower) << "\n"; + m_gcode += str.str(); + change_analyzer_line_width(line_width); } - WipeTowerWriter2& change_analyzer_line_width(float line_width) { + WipeTowerWriter2& change_analyzer_line_width(float line_width) + { // adds tag for analyzer: std::stringstream str; str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) << line_width << "\n"; @@ -598,47 +594,65 @@ public: return *this; } - WipeTowerWriter2& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { + WipeTowerWriter2& set_initial_position(const Vec2f& pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) + { m_wipe_tower_width = width; m_wipe_tower_depth = depth; - m_internal_angle = internal_angle; - m_start_pos = this->rotate(pos); - m_current_pos = pos; - return *this; - } + m_internal_angle = internal_angle; + m_start_pos = this->rotate(pos); + m_current_pos = pos; + return *this; + } - WipeTowerWriter2& set_position(const Vec2f &pos) { m_current_pos = pos; return *this; } + WipeTowerWriter2& set_position(const Vec2f& pos) + { + m_current_pos = pos; + return *this; + } - WipeTowerWriter2& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; } + WipeTowerWriter2& set_initial_tool(size_t tool) + { + m_current_tool = tool; + return *this; + } - WipeTowerWriter2& set_z(float z) - { m_current_z = z; return *this; } + WipeTowerWriter2& set_z(float z) + { + m_current_z = z; + return *this; + } - WipeTowerWriter2& set_extrusion_flow(float flow) - { m_extrusion_flow = flow; return *this; } + WipeTowerWriter2& set_extrusion_flow(float flow) + { + m_extrusion_flow = flow; + return *this; + } - WipeTowerWriter2& set_y_shift(float shift) { - m_current_pos.y() -= shift-m_y_shift; + WipeTowerWriter2& set_y_shift(float shift) + { + m_current_pos.y() -= shift - m_y_shift; m_y_shift = shift; return (*this); } - WipeTowerWriter2& disable_linear_advance() { + WipeTowerWriter2& disable_linear_advance() + { if (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware) m_gcode += (std::string("M572 D") + std::to_string(m_current_tool) + " S0\n"); - else if (m_gcode_flavor == gcfKlipper){ + else if (m_gcode_flavor == gcfKlipper) { // m_gcode += "SET_PRESSURE_ADVANCE ADVANCE=0\n"; // Snapmaker U1 - + } - + else m_gcode += "M900 K0\n"; return *this; } - WipeTowerWriter2& disable_linear_advance_value(float value = 0.0) { + WipeTowerWriter2& disable_linear_advance_value(float value = 0.0) + { if (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware) - m_gcode += (std::string("M572 D") + std::to_string(m_current_tool) + " S"+ std::to_string(value) + "\n"); + m_gcode += (std::string("M572 D") + std::to_string(m_current_tool) + " S" + std::to_string(value) + "\n"); else if (m_gcode_flavor == gcfKlipper) { m_gcode += "SET_PRESSURE_ADVANCE ADVANCE=" + Slic3r::float_to_string_decimal_point(value, 4) + "\n"; // Snapmaker U1 } @@ -648,139 +662,154 @@ public: return *this; } - WipeTowerWriter2& switch_filament_monitoring(bool enable) { + WipeTowerWriter2& switch_filament_monitoring(bool enable) + { m_gcode += std::string("G4 S0\n") + "M591 " + (enable ? "R" : "S0") + "\n"; return *this; } - // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various - // filament loading and cooling moves from normal extrusion moves. Therefore the writer - // is asked to suppres output of some lines, which look like extrusions. - WipeTowerWriter2& suppress_preview() { m_preview_suppressed = true; return *this; } - WipeTowerWriter2& resume_preview() { m_preview_suppressed = false; return *this; } + // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various + // filament loading and cooling moves from normal extrusion moves. Therefore the writer + // is asked to suppres output of some lines, which look like extrusions. + WipeTowerWriter2& suppress_preview() + { + m_preview_suppressed = true; + return *this; + } + WipeTowerWriter2& resume_preview() + { + m_preview_suppressed = false; + return *this; + } - WipeTowerWriter2& feedrate(float f) - { + WipeTowerWriter2& feedrate(float f) + { if (f != m_current_feedrate) { - m_gcode += "G1" + set_format_F(f) + "\n"; + m_gcode += "G1" + set_format_F(f) + "\n"; m_current_feedrate = f; } - return *this; - } + return *this; + } - const std::string& gcode() const { return m_gcode; } - const std::vector& extrusions() const { return m_extrusions; } - float x() const { return m_current_pos.x(); } - float y() const { return m_current_pos.y(); } - const Vec2f& pos() const { return m_current_pos; } - const Vec2f start_pos_rotated() const { return m_start_pos; } - const Vec2f pos_rotated() const { return this->rotate(m_current_pos); } - float elapsed_time() const { return m_elapsed_time; } - float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } + const std::string& gcode() const { return m_gcode; } + const std::vector& extrusions() const { return m_extrusions; } + float x() const { return m_current_pos.x(); } + float y() const { return m_current_pos.y(); } + const Vec2f& pos() const { return m_current_pos; } + const Vec2f start_pos_rotated() const { return m_start_pos; } + const Vec2f pos_rotated() const { return this->rotate(m_current_pos); } + float elapsed_time() const { return m_elapsed_time; } + float get_and_reset_used_filament_length() + { + float temp = m_used_filament_length; + m_used_filament_length = 0.f; + return temp; + } - // Extrude with an explicitely provided amount of extrusion. - WipeTowerWriter2& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) - { - if (x == m_current_pos.x() && y == m_current_pos.y() && e == 0.f && (f == 0.f || f == m_current_feedrate)) - // Neither extrusion nor a travel move. - return *this; + // Extrude with an explicitely provided amount of extrusion. + WipeTowerWriter2& extrude_explicit( + float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + { + if (x == m_current_pos.x() && y == m_current_pos.y() && e == 0.f && (f == 0.f || f == m_current_feedrate)) + // Neither extrusion nor a travel move. + return *this; - float dx = x - m_current_pos.x(); - float dy = y - m_current_pos.y(); - float len = std::sqrt(dx*dx+dy*dy); + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); + float len = std::sqrt(dx * dx + dy * dy); if (record_length) m_used_filament_length += e; - // Now do the "internal rotation" with respect to the wipe tower center - Vec2f rotated_current_pos(this->pos_rotated()); - Vec2f rot(this->rotate(Vec2f(x,y))); // this is where we want to go + // Now do the "internal rotation" with respect to the wipe tower center + Vec2f rotated_current_pos(this->pos_rotated()); + Vec2f rot(this->rotate(Vec2f(x, y))); // this is where we want to go - if (! m_preview_suppressed && e > 0.f && len > 0.f) { - // Width of a squished extrusion, corrected for the roundings of the squished extrusions. - // This is left zero if it is a travel move. - float width = e * m_filpar[0].filament_area / (len * m_layer_height); - // Correct for the roundings of a squished extrusion. - width += m_layer_height * float(1. - M_PI / 4.); - if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) - m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); - m_extrusions.emplace_back(WipeTower::Extrusion(rot, width, m_current_tool)); - } + if (!m_preview_suppressed && e > 0.f && len > 0.f) { + // Width of a squished extrusion, corrected for the roundings of the squished extrusions. + // This is left zero if it is a travel move. + float width = e * m_filpar[0].filament_area / (len * m_layer_height); + // Correct for the roundings of a squished extrusion. + width += m_layer_height * float(1. - M_PI / 4.); + if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) + m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); + m_extrusions.emplace_back(WipeTower::Extrusion(rot, width, m_current_tool)); + } - m_gcode += "G1"; - if (std::abs(rot.x() - rotated_current_pos.x()) > (float)EPSILON) - m_gcode += set_format_X(rot.x()); + m_gcode += "G1"; + if (std::abs(rot.x() - rotated_current_pos.x()) > (float) EPSILON) + m_gcode += set_format_X(rot.x()); - if (std::abs(rot.y() - rotated_current_pos.y()) > (float)EPSILON) - m_gcode += set_format_Y(rot.y()); + if (std::abs(rot.y() - rotated_current_pos.y()) > (float) EPSILON) + m_gcode += set_format_Y(rot.y()); + if (e != 0.f) + m_gcode += set_format_E(e); - if (e != 0.f) - m_gcode += set_format_E(e); - - if (f != 0.f && f != m_current_feedrate) { + if (f != 0.f && f != m_current_feedrate) { if (limit_volumetric_flow) { float e_speed = e / (((len == 0.f) ? std::abs(e) : len) / f * 60.f); f /= std::max(1.f, e_speed / m_filpar[m_current_tool].max_e_speed); } - m_gcode += set_format_F(f); + m_gcode += set_format_F(f); } // Append newline if at least one of X,Y,E,F was changed. // Otherwise, remove the "G1". - if (! boost::ends_with(m_gcode, "G1")) + if (!boost::ends_with(m_gcode, "G1")) m_gcode += "\n"; else - m_gcode.erase(m_gcode.end()-2, m_gcode.end()); + m_gcode.erase(m_gcode.end() - 2, m_gcode.end()); m_current_pos.x() = x; m_current_pos.y() = y; - // Update the elapsed time with a rough estimate. + // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0.f) ? std::abs(e) : len) / m_current_feedrate * 60.f; - return *this; - } + return *this; + } - WipeTowerWriter2& extrude_explicit(const Vec2f &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) - { return extrude_explicit(dest.x(), dest.y(), e, f, record_length); } + WipeTowerWriter2& extrude_explicit( + const Vec2f& dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + { + return extrude_explicit(dest.x(), dest.y(), e, f, record_length); + } - // Travel to a new XY position. f=0 means use the current value. - WipeTowerWriter2& travel(float x, float y, float f = 0.f) - { return extrude_explicit(x, y, 0.f, f); } + // Travel to a new XY position. f=0 means use the current value. + WipeTowerWriter2& travel(float x, float y, float f = 0.f) { return extrude_explicit(x, y, 0.f, f); } - WipeTowerWriter2& travel(const Vec2f &dest, float f = 0.f) - { return extrude_explicit(dest.x(), dest.y(), 0.f, f); } + WipeTowerWriter2& travel(const Vec2f& dest, float f = 0.f) { return extrude_explicit(dest.x(), dest.y(), 0.f, f); } - // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. - WipeTowerWriter2& extrude(float x, float y, float f = 0.f) - { - float dx = x - m_current_pos.x(); - float dy = y - m_current_pos.y(); - return extrude_explicit(x, y, std::sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); - } + // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. + WipeTowerWriter2& extrude(float x, float y, float f = 0.f) + { + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); + return extrude_explicit(x, y, std::sqrt(dx * dx + dy * dy) * m_extrusion_flow, f, true); + } - WipeTowerWriter2& extrude(const Vec2f &dest, const float f = 0.f) - { return extrude(dest.x(), dest.y(), f); } + WipeTowerWriter2& extrude(const Vec2f& dest, const float f = 0.f) { return extrude(dest.x(), dest.y(), f); } - WipeTowerWriter2& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f) + WipeTowerWriter2& rectangle(const Vec2f& ld, float width, float height, const float f = 0.f) { Vec2f corners[4]; - corners[0] = ld; - corners[1] = ld + Vec2f(width,0.f); - corners[2] = ld + Vec2f(width,height); - corners[3] = ld + Vec2f(0.f,height); + corners[0] = ld; + corners[1] = ld + Vec2f(width, 0.f); + corners[2] = ld + Vec2f(width, height); + corners[3] = ld + Vec2f(0.f, height); int index_of_closest = 0; - if (x()-ld.x() > ld.x()+width-x()) // closer to the right + if (x() - ld.x() > ld.x() + width - x()) // closer to the right index_of_closest = 1; - if (y()-ld.y() > ld.y()+height-y()) // closer to the top - index_of_closest = (index_of_closest==0 ? 3 : 2); + if (y() - ld.y() > ld.y() + height - y()) // closer to the top + index_of_closest = (index_of_closest == 0 ? 3 : 2); - travel(corners[index_of_closest].x(), y()); // travel to the closest corner - travel(x(),corners[index_of_closest].y()); + travel(corners[index_of_closest].x(), y()); // travel to the closest corner + travel(x(), corners[index_of_closest].y()); int i = index_of_closest; do { ++i; - if (i==4) i=0; + if (i == 4) + i = 0; extrude(corners[i], f); } while (i != index_of_closest); return (*this); @@ -788,39 +817,36 @@ public: WipeTowerWriter2& rectangle(const WipeTower::box_coordinates& box, const float f = 0.f) { - rectangle(Vec2f(box.ld.x(), box.ld.y()), - box.ru.x() - box.lu.x(), - box.ru.y() - box.rd.y(), f); + rectangle(Vec2f(box.ld.x(), box.ld.y()), box.ru.x() - box.lu.x(), box.ru.y() - box.rd.y(), f); return (*this); } - WipeTowerWriter2& load(float e, float f = 0.f) - { - if (e == 0.f && (f == 0.f || f == m_current_feedrate)) - return *this; - m_gcode += "G1"; - if (e != 0.f) - m_gcode += set_format_E(e); - if (f != 0.f && f != m_current_feedrate) - m_gcode += set_format_F(f); - m_gcode += "\n"; - return *this; - } + WipeTowerWriter2& load(float e, float f = 0.f) + { + if (e == 0.f && (f == 0.f || f == m_current_feedrate)) + return *this; + m_gcode += "G1"; + if (e != 0.f) + m_gcode += set_format_E(e); + if (f != 0.f && f != m_current_feedrate) + m_gcode += set_format_F(f); + m_gcode += "\n"; + return *this; + } - WipeTowerWriter2& retract(float e, float f = 0.f) - { return load(-e, f); } + WipeTowerWriter2& retract(float e, float f = 0.f) { return load(-e, f); } -// Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary) + // Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary) WipeTowerWriter2& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f) { - float time = std::abs(loading_dist / loading_speed); // time that the move must take - float x_distance = std::abs(farthest_x - x()); // max x-distance that we can travel - float x_speed = x_distance / time; // x-speed to do it in that time + float time = std::abs(loading_dist / loading_speed); // time that the move must take + float x_distance = std::abs(farthest_x - x()); // max x-distance that we can travel + float x_speed = x_distance / time; // x-speed to do it in that time if (x_speed > max_x_speed) { // Necessary x_speed is too high - we must shorten the distance to achieve max_x_speed and still respect the time. x_distance = max_x_speed * time; - x_speed = max_x_speed; + x_speed = max_x_speed; } float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_distance; @@ -831,108 +857,107 @@ public: // both the loading_speed and x_speed. Can shorten the move. WipeTowerWriter2& load_move_x_advanced_there_and_back(float farthest_x, float e_dist, float e_speed, float x_speed) { - float old_x = x(); - float time = std::abs(e_dist / e_speed); // time that the whole move must take - float x_max_dist = std::abs(farthest_x - x()); // max x-distance that we can travel - float x_dist = x_speed * time; // totel x-distance to travel during the move - int n = int(x_dist / (2*x_max_dist) + 1.f); // how many there and back moves should we do - float r = 2*n*x_max_dist / x_dist; // actual/required dist if the move is not shortened + float old_x = x(); + float time = std::abs(e_dist / e_speed); // time that the whole move must take + float x_max_dist = std::abs(farthest_x - x()); // max x-distance that we can travel + float x_dist = x_speed * time; // totel x-distance to travel during the move + int n = int(x_dist / (2 * x_max_dist) + 1.f); // how many there and back moves should we do + float r = 2 * n * x_max_dist / x_dist; // actual/required dist if the move is not shortened float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_max_dist / r; - for (int i=0; i& wipe_path() const - { - return m_wipe_path; + m_last_fan_speed = speed; + return *this; } + WipeTowerWriter2& append(const std::string& text) + { + m_gcode += text; + return *this; + } + + const std::vector& wipe_path() const { return m_wipe_path; } + WipeTowerWriter2& add_wipe_point(const Vec2f& pt) { m_wipe_path.push_back(rotate(pt)); return *this; } - WipeTowerWriter2& add_wipe_point(float x, float y) - { - return add_wipe_point(Vec2f(x, y)); - } + WipeTowerWriter2& add_wipe_point(float x, float y) { return add_wipe_point(Vec2f(x, y)); } - // Extrude with an explicitely provided amount of extrusion. + // Extrude with an explicitely provided amount of extrusion. WipeTowerWriter2& extrude_arc_explicit(ArcSegment& arc, - float f = 0.f, - bool record_length = false, - LimitFlow limit_flow = LimitFlow::LimitPrintFlow) + float f = 0.f, + bool record_length = false, + LimitFlow limit_flow = LimitFlow::LimitPrintFlow) { float x = (float) unscale(arc.end_point).x(); float y = (float) unscale(arc.end_point).y(); @@ -1039,11 +1061,11 @@ public: } } - //if (e == 0.f) { - // m_gcode += set_travel_acceleration(); - //} else { - // m_gcode += set_normal_acceleration(); - //} + // if (e == 0.f) { + // m_gcode += set_travel_acceleration(); + // } else { + // m_gcode += set_normal_acceleration(); + // } m_gcode += arc.direction == ArcDirection::Arc_Dir_CCW ? "G3" : "G2"; const Vec2f center_offset = this->rotate(unscaled(arc.center)) - rotated_current_pos; @@ -1059,8 +1081,8 @@ public: if (limit_flow != LimitFlow::None) { float e_speed = e / (((len == 0.f) ? std::abs(e) : len) / f * 60.f); float tmp = m_filpar[m_current_tool].max_e_speed; - //if (limit_flow == LimitFlow::LimitRammingFlow) - // tmp = m_filpar[m_current_tool].max_e_ramming_speed; + // if (limit_flow == LimitFlow::LimitRammingFlow) + // tmp = m_filpar[m_current_tool].max_e_ramming_speed; f /= std::max(1.f, e_speed / tmp); } m_gcode += set_format_F(f); @@ -1080,7 +1102,7 @@ public: return extrude_arc_explicit(arc, f, false, limit_flow); } - void generate_path(Polylines& pls, float feedrate, float retract_length, float retract_speed, bool used_fillet) + void generate_path(Polylines& pls, float feedrate, float retract_length, float retract_speed, bool used_fillet) { auto get_closet_idx = [this](std::vector& corners) -> int { Vec2f anchor{this->m_current_pos.x(), this->m_current_pos.y()}; @@ -1150,142 +1172,137 @@ public: } private: - Vec2f m_start_pos; - Vec2f m_current_pos; - std::vector m_wipe_path; - float m_current_z; - float m_current_feedrate; - size_t m_current_tool; - float m_layer_height; - float m_extrusion_flow; - bool m_preview_suppressed; - std::string m_gcode; - std::vector m_extrusions; - float m_elapsed_time; - float m_internal_angle = 0.f; - float m_y_shift = 0.f; - float m_wipe_tower_width = 0.f; - float m_wipe_tower_depth = 0.f; - unsigned m_last_fan_speed = 0; - int current_temp = -1; - float m_used_filament_length = 0.f; - GCodeFlavor m_gcode_flavor; - bool m_enable_arc_fitting = false; + Vec2f m_start_pos; + Vec2f m_current_pos; + std::vector m_wipe_path; + float m_current_z; + float m_current_feedrate; + size_t m_current_tool; + float m_layer_height; + float m_extrusion_flow; + bool m_preview_suppressed; + std::string m_gcode; + std::vector m_extrusions; + float m_elapsed_time; + float m_internal_angle = 0.f; + float m_y_shift = 0.f; + float m_wipe_tower_width = 0.f; + float m_wipe_tower_depth = 0.f; + unsigned m_last_fan_speed = 0; + int current_temp = -1; + float m_used_filament_length = 0.f; + GCodeFlavor m_gcode_flavor; + bool m_enable_arc_fitting = false; const std::vector& m_filpar; - std::string m_printer_model; + std::string m_printer_model; - // 判断是否是 Snapmaker U1 打印机 - bool is_snapmaker_u1() const { - return boost::icontains(m_printer_model, "Snapmaker") && - boost::icontains(m_printer_model, "U1"); - } + // 判断是否是 Snapmaker U1 打印机 + bool is_snapmaker_u1() const { return boost::icontains(m_printer_model, "Snapmaker") && boost::icontains(m_printer_model, "U1"); } - std::string set_format_X(float x) + std::string set_format_X(float x) { m_current_pos.x() = x; return " X" + Slic3r::float_to_string_decimal_point(x, 3); - } + } - std::string set_format_Y(float y) { + std::string set_format_Y(float y) + { m_current_pos.y() = y; return " Y" + Slic3r::float_to_string_decimal_point(y, 3); - } + } - std::string set_format_Z(float z) { - return " Z" + Slic3r::float_to_string_decimal_point(z, 3); - } + std::string set_format_Z(float z) { return " Z" + Slic3r::float_to_string_decimal_point(z, 3); } - std::string set_format_E(float e) { - return " E" + Slic3r::float_to_string_decimal_point(e, 4); - } + std::string set_format_E(float e) { return " E" + Slic3r::float_to_string_decimal_point(e, 4); } - std::string set_format_F(float f) { + std::string set_format_F(float f) + { char buf[64]; sprintf(buf, " F%d", int(floor(f + 0.5f))); m_current_feedrate = f; return buf; - } - std::string set_format_I(float i) { return " I" + Slic3r::float_to_string_decimal_point(i, 3); } - std::string set_format_J(float j) { return " J" + Slic3r::float_to_string_decimal_point(j, 3); } + } + std::string set_format_I(float i) { return " I" + Slic3r::float_to_string_decimal_point(i, 3); } + std::string set_format_J(float j) { return " J" + Slic3r::float_to_string_decimal_point(j, 3); } - WipeTowerWriter2& operator=(const WipeTowerWriter2 &rhs); + WipeTowerWriter2& operator=(const WipeTowerWriter2& rhs); - // Rotate the point around center of the wipe tower about given angle (in degrees) - Vec2f rotate(Vec2f pt) const - { - pt.x() -= m_wipe_tower_width / 2.f; - pt.y() += m_y_shift - m_wipe_tower_depth / 2.f; - double angle = m_internal_angle * float(M_PI/180.); - double c = cos(angle); - double s = sin(angle); - return Vec2f(float(pt.x() * c - pt.y() * s) + m_wipe_tower_width / 2.f, float(pt.x() * s + pt.y() * c) + m_wipe_tower_depth / 2.f); - } + // Rotate the point around center of the wipe tower about given angle (in degrees) + Vec2f rotate(Vec2f pt) const + { + pt.x() -= m_wipe_tower_width / 2.f; + pt.y() += m_y_shift - m_wipe_tower_depth / 2.f; + double angle = m_internal_angle * float(M_PI / 180.); + double c = cos(angle); + double s = sin(angle); + Vec2f result(float(pt.x() * c - pt.y() * s) + m_wipe_tower_width / 2.f, float(pt.x() * s + pt.y() * c) + m_wipe_tower_depth / 2.f); + + return result; + } }; // class WipeTowerWriter2 - - -WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer, - bool priming, - size_t old_tool, - bool is_finish) const +WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer, bool priming, size_t old_tool, bool is_finish) const { WipeTower::ToolChangeResult result; - result.priming = priming; - result.initial_tool = int(old_tool); - result.new_tool = int(m_current_tool); - result.print_z = m_z_pos; - result.layer_height = m_layer_height; - result.elapsed_time = writer.elapsed_time(); - result.start_pos = writer.start_pos_rotated(); - result.end_pos = priming ? writer.pos() : writer.pos_rotated(); - result.gcode = std::move(writer.gcode()); - result.extrusions = std::move(writer.extrusions()); - result.wipe_path = std::move(writer.wipe_path()); + result.priming = priming; + result.initial_tool = int(old_tool); + result.new_tool = int(m_current_tool); + result.print_z = m_z_pos; + result.layer_height = m_layer_height; + result.elapsed_time = writer.elapsed_time(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = priming ? writer.pos() : writer.pos_rotated(); + result.gcode = std::move(writer.gcode()); + result.extrusions = std::move(writer.extrusions()); + result.wipe_path = std::move(writer.wipe_path()); result.is_finish_first = is_finish; return result; } - - -WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector>& wiping_matrix, size_t initial_tool) : - m_semm(config.single_extruder_multi_material.value), - m_enable_filament_ramming(config.enable_filament_ramming.value), - m_wipe_tower_pos(config.wipe_tower_x.get_at(plate_idx), config.wipe_tower_y.get_at(plate_idx)), - m_wipe_tower_width(float(config.prime_tower_width)), - m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)), - m_wipe_tower_brim_width(float(config.prime_tower_brim_width)), - m_prime_tower_brim_chamfer(config.prime_tower_brim_chamfer), - m_prime_tower_brim_chamfer_max_width(float(config.prime_tower_brim_chamfer_max_width)), - m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)), - m_extra_flow(float(config.wipe_tower_extra_flow/100.)), - m_extra_spacing_wipe(float(config.wipe_tower_extra_spacing/100. * config.wipe_tower_extra_flow/100.)), - m_extra_spacing_ramming(float(config.wipe_tower_extra_spacing/100.)), - m_y_shift(0.f), - m_z_pos(0.f), - m_bridging(float(config.wipe_tower_bridging)), - m_no_sparse_layers(config.wipe_tower_no_sparse_layers), - m_gcode_flavor(config.gcode_flavor), - m_travel_speed(config.travel_speed), - m_infill_speed(default_region_config.sparse_infill_speed), - m_perimeter_speed(default_region_config.inner_wall_speed), - m_current_tool(initial_tool), - wipe_volumes(wiping_matrix), - m_wipe_tower_max_purge_speed(float(config.wipe_tower_max_purge_speed)), - m_change_pressure(config.enable_change_pressure_when_wiping), - m_change_pressure_value(config.ramming_pressure_advance_value), - m_ramming_width_ratio(config.ramming_line_width_ratio), - m_enable_arc_fitting(config.enable_arc_fitting), - m_used_fillet(config.wipe_tower_fillet_wall), - m_rib_width(config.wipe_tower_rib_width), - m_extra_rib_length(config.wipe_tower_extra_rib_length), - m_wall_type((int)config.wipe_tower_wall_type) +WipeTower2::WipeTower2(const PrintConfig& config, + const PrintRegionConfig& default_region_config, + int plate_idx, + Vec3d plate_origin, + const std::vector>& wiping_matrix, + size_t initial_tool) + : m_semm(config.single_extruder_multi_material.value) + , m_enable_filament_ramming(config.enable_filament_ramming.value) + , m_wipe_tower_pos(config.wipe_tower_x.get_at(plate_idx), config.wipe_tower_y.get_at(plate_idx)) + , m_wipe_tower_width(float(config.prime_tower_width)) + , m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)) + , m_wipe_tower_brim_width(float(config.prime_tower_brim_width)) + , m_prime_tower_brim_chamfer(config.prime_tower_brim_chamfer) + , m_prime_tower_brim_chamfer_max_width(float(config.prime_tower_brim_chamfer_max_width)) + , m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)) + , m_extra_flow(float(config.wipe_tower_extra_flow / 100.)) + , m_extra_spacing_wipe(float(config.wipe_tower_extra_spacing / 100. * config.wipe_tower_extra_flow / 100.)) + , m_extra_spacing_ramming(float(config.wipe_tower_extra_spacing / 100.)) + , m_y_shift(0.f) + , m_z_pos(0.f) + , m_bridging(float(config.wipe_tower_bridging)) + , m_no_sparse_layers(config.wipe_tower_no_sparse_layers) + , m_gcode_flavor(config.gcode_flavor) + , m_travel_speed(config.travel_speed) + , m_infill_speed(default_region_config.sparse_infill_speed) + , m_perimeter_speed(default_region_config.inner_wall_speed) + , m_current_tool(initial_tool) + , wipe_volumes(wiping_matrix) + , m_wipe_tower_max_purge_speed(float(config.wipe_tower_max_purge_speed)) + , m_change_pressure(config.enable_change_pressure_when_wiping) + , m_change_pressure_value(config.ramming_pressure_advance_value) + , m_ramming_width_ratio(config.ramming_line_width_ratio) + , m_enable_arc_fitting(config.enable_arc_fitting) + , m_used_fillet(config.wipe_tower_fillet_wall) + , m_rib_width(config.wipe_tower_rib_width) + , m_extra_rib_length(config.wipe_tower_extra_rib_length) + , m_wall_type((int) config.wipe_tower_wall_type) { // Read absolute value of first layer speed, if given as percentage, // it is taken over following default. Speeds from config are not // easily accessible here. const float default_speed = 60.f; - m_first_layer_speed = config.initial_layer_speed; + m_first_layer_speed = config.initial_layer_speed; if (m_first_layer_speed == 0.f) // just to make sure autospeed doesn't break it. m_first_layer_speed = default_speed / 2.f; @@ -1295,7 +1312,6 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau if (m_perimeter_speed == 0.f) m_perimeter_speed = 80.f; - // If this is a single extruder MM printer, we will use all the SE-specific config values. // Otherwise, the defaults will be used to turn off the SE stuff. if (m_semm) { @@ -1313,76 +1329,67 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau // Calculate where the priming lines should be - very naive test not detecting parallelograms etc. const std::vector& bed_points = config.printable_area.values; - BoundingBoxf bb(bed_points); + BoundingBoxf bb(bed_points); m_bed_width = float(bb.size().x()); m_bed_shape = (bed_points.size() == 4 ? RectangularBed : CircularBed); if (m_bed_shape == CircularBed) { // this may still be a custom bed, check that the points are roughly on a circle - double r2 = std::pow(m_bed_width/2., 2.); - double lim2 = std::pow(m_bed_width/10., 2.); - Vec2d center = bb.center(); + double r2 = std::pow(m_bed_width / 2., 2.); + double lim2 = std::pow(m_bed_width / 10., 2.); + Vec2d center = bb.center(); for (const Vec2d& pt : bed_points) - if (std::abs(std::pow(pt.x()-center.x(), 2.) + std::pow(pt.y()-center.y(), 2.) - r2) > lim2) { + if (std::abs(std::pow(pt.x() - center.x(), 2.) + std::pow(pt.y() - center.y(), 2.) - r2) > lim2) { m_bed_shape = CustomBed; break; } } - m_bed_bottom_left = m_bed_shape == RectangularBed - ? Vec2f(bed_points.front().x(), bed_points.front().y()) - : Vec2f::Zero(); + m_bed_bottom_left = m_bed_shape == RectangularBed ? Vec2f(bed_points.front().x(), bed_points.front().y()) : Vec2f::Zero(); } - - -void WipeTower2::set_extruder(size_t idx, int physical_extruder, const PrintConfig& config) +void WipeTower2::set_extruder(size_t idx, const PrintConfig& config) { - //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector + // while (m_filpar.size() < idx+1) // makes sure the required element is in the vector m_filpar.push_back(FilamentParameters()); - // SM Orca: 耗材属性使用 idx (filament index) m_filpar[idx].material = config.filament_type.get_at(idx); - // m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx); - m_filpar[idx].is_soluble = config.wipe_tower_filament == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_filament - 1)); - // SM Orca: 温度和喷嘴直径是挤出机属性,使用 physical_extruder - m_filpar[idx].temperature = config.nozzle_temperature.get_at(physical_extruder); - m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(physical_extruder); + m_filpar[idx].is_soluble = config.wipe_tower_filament == 0 ? config.filament_soluble.get_at(idx) : + (idx != size_t(config.wipe_tower_filament - 1)); + m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx); + m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx); m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx); // If this is a single extruder MM printer, we will use all the SE-specific config values. // Otherwise, the defaults will be used to turn off the SE stuff. if (m_semm) { - m_filpar[idx].loading_speed = float(config.filament_loading_speed.get_at(idx)); - m_filpar[idx].loading_speed_start = float(config.filament_loading_speed_start.get_at(idx)); - m_filpar[idx].unloading_speed = float(config.filament_unloading_speed.get_at(idx)); - m_filpar[idx].unloading_speed_start = float(config.filament_unloading_speed_start.get_at(idx)); - m_filpar[idx].delay = float(config.filament_toolchange_delay.get_at(idx)); - m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx); - m_filpar[idx].cooling_initial_speed = float(config.filament_cooling_initial_speed.get_at(idx)); - m_filpar[idx].cooling_final_speed = float(config.filament_cooling_final_speed.get_at(idx)); - m_filpar[idx].filament_stamping_loading_speed = float(config.filament_stamping_loading_speed.get_at(idx)); - m_filpar[idx].filament_stamping_distance = float(config.filament_stamping_distance.get_at(idx)); + m_filpar[idx].loading_speed = float(config.filament_loading_speed.get_at(idx)); + m_filpar[idx].loading_speed_start = float(config.filament_loading_speed_start.get_at(idx)); + m_filpar[idx].unloading_speed = float(config.filament_unloading_speed.get_at(idx)); + m_filpar[idx].unloading_speed_start = float(config.filament_unloading_speed_start.get_at(idx)); + m_filpar[idx].delay = float(config.filament_toolchange_delay.get_at(idx)); + m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx); + m_filpar[idx].cooling_initial_speed = float(config.filament_cooling_initial_speed.get_at(idx)); + m_filpar[idx].cooling_final_speed = float(config.filament_cooling_final_speed.get_at(idx)); + m_filpar[idx].filament_stamping_loading_speed = float(config.filament_stamping_loading_speed.get_at(idx)); + m_filpar[idx].filament_stamping_distance = float(config.filament_stamping_distance.get_at(idx)); } - m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point - // SM Orca: 喷嘴直径是挤出机属性,使用 physical_extruder - float nozzle_diameter = float(config.nozzle_diameter.get_at(physical_extruder)); + m_filpar[idx].filament_area = float( + (M_PI / 4.f) * + pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point + float nozzle_diameter = float(config.nozzle_diameter.get_at(idx)); m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx)); - if (max_vol_speed!= 0.f) + if (max_vol_speed != 0.f) m_filpar[idx].max_e_speed = (max_vol_speed / filament_area()); - // SM Orca: Store per-filament perimeter width and also set the global one - // Note: m_perimeter_width gets overwritten with each set_extruder() call - // The brim should use m_filpar[0].perimeter_width for consistency - m_filpar[idx].perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; - m_perimeter_width = m_filpar[idx].perimeter_width; // all extruders are now assumed to have the same diameter + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter if (m_semm) { std::istringstream stream{config.filament_ramming_parameters.get_at(idx)}; - float speed = 0.f; + float speed = 0.f; stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; m_filpar[idx].ramming_line_width_multiplicator /= 100; m_filpar[idx].ramming_step_multiplicator /= 100; @@ -1393,89 +1400,84 @@ void WipeTower2::set_extruder(size_t idx, int physical_extruder, const PrintConf // and the same time step has to be used when the ramming is performed. } else { // We will use the same variables internally, but the correspondence to the configuration options will be different. - float vol = config.filament_multitool_ramming_volume.get_at(idx); - float flow = config.filament_multitool_ramming_flow.get_at(idx); - m_filpar[idx].multitool_ramming = config.filament_multitool_ramming.get_at(idx) && vol > 0.f && flow > 0.f; + float vol = config.filament_multitool_ramming_volume.get_at(idx); + float flow = config.filament_multitool_ramming_flow.get_at(idx); + m_filpar[idx].multitool_ramming = config.filament_multitool_ramming.get_at(idx) && vol > 0.f && flow > 0.f; m_filpar[idx].ramming_line_width_multiplicator = m_ramming_width_ratio; - m_filpar[idx].ramming_step_multiplicator = 1.; + m_filpar[idx].ramming_step_multiplicator = 1.; // Now the ramming speed vector. In this case it contains just one value (flow). // The time is calculated and saved separately. This is here so that the MM ramming // is not limited by the 0.25s granularity - it is not possible to create a SEMM-style - // ramming_speed vector that would respect both the volume and flow (because of + // ramming_speed vector that would respect both the volume and flow (because of // rounding issues with small volumes and high flow). m_filpar[idx].ramming_speed.push_back(flow); - m_filpar[idx].multitool_ramming_time = flow > 0.f ? vol/flow : 0.f; + m_filpar[idx].multitool_ramming_time = flow > 0.f ? vol / flow : 0.f; } - m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later + m_used_filament_length.resize( + std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later - // SM Orca: 回抽参数支持耗材覆盖,继承后存储在耗材位置,使用 filament_id (idx) m_filpar[idx].retract_length = config.retraction_length.get_at(idx); m_filpar[idx].retract_speed = config.retraction_speed.get_at(idx); } - - // Returns gcode to prime the nozzles at the front edge of the print bed. std::vector WipeTower2::prime( - // print_z of the first layer. - float initial_layer_print_height, - // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - const std::vector &tools, - // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. - // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool /*last_wipe_inside_wipe_tower*/) + // print_z of the first layer. + float initial_layer_print_height, + // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. + const std::vector& tools, + // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe + // tower. If false, the last priming are will be large enough to wipe the last extruder sufficiently. + bool /*last_wipe_inside_wipe_tower*/) { - this->set_layer(initial_layer_print_height, initial_layer_print_height, tools.size(), true, false); - m_current_tool = tools.front(); - + this->set_layer(initial_layer_print_height, initial_layer_print_height, tools.size(), true, false); + m_current_tool = tools.front(); + // The Prusa i3 MK2 has a working space of [0, -2.2] to [250, 210]. // Due to the XYZ calibration, this working space may shrink slightly from all directions, // therefore the homing position is shifted inside the bed by 0.2 in the firmware to [0.2, -2.0]. -// WipeTower::box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); + // WipeTower::box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); - float prime_section_width = std::min(0.9f * m_bed_width / tools.size(), 60.f); - WipeTower::box_coordinates cleaning_box(Vec2f(0.02f * m_bed_width, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); + float prime_section_width = std::min(0.9f * m_bed_width / tools.size(), 60.f); + WipeTower::box_coordinates cleaning_box(Vec2f(0.02f * m_bed_width, 0.01f + m_perimeter_width / 2.f), prime_section_width, 100.f); if (m_bed_shape == CircularBed) { - cleaning_box = WipeTower::box_coordinates(Vec2f(0.f, 0.f), prime_section_width, 100.f); + cleaning_box = WipeTower::box_coordinates(Vec2f(0.f, 0.f), prime_section_width, 100.f); float total_width_half = tools.size() * prime_section_width / 2.f; - cleaning_box.translate(-total_width_half, -std::sqrt(std::max(0.f, std::pow(m_bed_width/2, 2.f) - std::pow(1.05f * total_width_half, 2.f)))); - } - else + cleaning_box.translate(-total_width_half, + -std::sqrt(std::max(0.f, std::pow(m_bed_width / 2, 2.f) - std::pow(1.05f * total_width_half, 2.f)))); + } else cleaning_box.translate(m_bed_bottom_left); std::vector results; // Iterate over all priming toolchanges and push respective ToolChangeResults into results vector. - for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { + for (size_t idx_tool = 0; idx_tool < tools.size(); ++idx_tool) { size_t old_tool = m_current_tool; WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); - writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_initial_tool(m_current_tool); + writer.set_extrusion_flow(m_extrusion_flow).set_z(m_z_pos).set_initial_tool(m_current_tool); // This is the first toolchange - initiate priming if (idx_tool == 0) { - writer.append(";--------------------\n" - "; CP PRIMING START\n") - .append(";--------------------\n") - .speed_override_backup() - .speed_override(100) - .set_initial_position(Vec2f::Zero()) // Always move to the starting position - .travel(cleaning_box.ld, 7200); + writer + .append(";--------------------\n" + "; CP PRIMING START\n") + .append(";--------------------\n") + .speed_override_backup() + .speed_override(100) + .set_initial_position(Vec2f::Zero()) // Always move to the starting position + .travel(cleaning_box.ld, 7200); if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. - } - else + writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + } else writer.set_initial_position(results.back().end_pos); - unsigned int tool = tools[idx_tool]; - m_left_to_right = true; + m_left_to_right = true; toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials. - toolchange_Load(writer, cleaning_box); // Prime the tool. + toolchange_Load(writer, cleaning_box); // Prime the tool. if (idx_tool + 1 == tools.size()) { // Last tool should not be unloaded, but it should be wiped enough to become of a pure color. if (idx_tool == 0) @@ -1484,33 +1486,33 @@ std::vector WipeTower2::prime( toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool - 1]][tool]); } else { // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. - //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); - toolchange_Wipe(writer, cleaning_box , 20.f); + // writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); + toolchange_Wipe(writer, cleaning_box, 20.f); WipeTower::box_coordinates box = cleaning_box; box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); - toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature); + toolchange_Unload(writer, box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, + m_filpar[tools[idx_tool + 1]].first_layer_temperature); cleaning_box.translate(prime_section_width, 0.f); writer.travel(cleaning_box.ld, 7200); } - ++ m_num_tool_changes; - + ++m_num_tool_changes; // Ask our writer about how much material was consumed: if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); // This is the last priming toolchange - finish priming - if (idx_tool+1 == tools.size()) { + if (idx_tool + 1 == tools.size()) { // Reset the extruder current to a normal value. if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); writer.speed_override_restore() - .feedrate(m_travel_speed * 60.f) - .flush_planner_queue() - .reset_extruder() - .append("; CP PRIMING END\n" - ";------------------\n" - "\n\n"); + .feedrate(m_travel_speed * 60.f) + .flush_planner_queue() + .reset_extruder() + .append("; CP PRIMING END\n" + ";------------------\n" + "\n\n"); } results.emplace_back(construct_tcr(writer, true, old_tool, true)); @@ -1519,87 +1521,89 @@ std::vector WipeTower2::prime( m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) - return results; + return results; } WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool) { size_t old_tool = m_current_tool; - float wipe_area = 0.f; - float wipe_volume = 0.f; - - // Finds this toolchange info - if (tool != (unsigned int)(-1)) - { - for (const auto &b : m_layer_info->tool_changes) - if ( b.new_tool == tool ) { + float wipe_area = 0.f; + float wipe_volume = 0.f; + + // Finds this toolchange info + if (tool != (unsigned int) (-1)) { + for (const auto& b : m_layer_info->tool_changes) + if (b.new_tool == tool) { wipe_volume = b.wipe_volume; - wipe_area = b.required_depth; - break; - } - } - else { - // Otherwise we are going to Unload only. And m_layer_info would be invalid. - } + wipe_area = b.required_depth; + break; + } + } else { + // Otherwise we are going to Unload only. And m_layer_info would be invalid. + } - WipeTower::box_coordinates cleaning_box( - Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), - m_wipe_tower_width - m_perimeter_width, - (tool != (unsigned int)(-1) ? wipe_area+m_depth_traversed-0.5f*m_perimeter_width - : m_wipe_tower_depth-m_perimeter_width)); + WipeTower::box_coordinates cleaning_box(Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), m_wipe_tower_width - m_perimeter_width, + (tool != (unsigned int) (-1) ? wipe_area + m_depth_traversed - 0.5f * m_perimeter_width : + m_wipe_tower_depth - m_perimeter_width)); - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); - writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_initial_tool(m_current_tool) - .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) - .append(";--------------------\n" - "; CP TOOLCHANGE START\n"); + WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool) + .set_y_shift(m_y_shift + (tool != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? + m_layer_info->depth - m_layer_info->toolchanges_depth() : + 0.f)) + .append(";--------------------\n" + "; CP TOOLCHANGE START\n"); - if (tool != (unsigned)(-1)){ + if (tool != (unsigned) (-1)) { writer.comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based - writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str()) + writer + .append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + + " -> " + m_filpar[tool].material + "\n") + .c_str()) .append(";--------------------\n"); writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); } writer.speed_override_backup(); - writer.speed_override(100); + writer.speed_override(100); - Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); + Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); // Increase the extruder driver current to allow fast ramming. - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(750); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(750); // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. - if (tool != (unsigned int)-1){ // This is not the last change. + if (tool != (unsigned int) -1) { // This is not the last change. auto new_tool_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature; toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, (is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature), new_tool_temp); toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Load(writer, cleaning_box); - writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road - toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. + writer.travel(writer.x(), writer.y() - m_perimeter_width); // cooling and loading were done a bit down the road + toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); - ++ m_num_tool_changes; + ++m_num_tool_changes; } else - toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature, m_filpar[m_current_tool].temperature); + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature, + m_filpar[m_current_tool].temperature); m_depth_traversed += wipe_area; - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. - writer.speed_override_restore(); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. + writer.speed_override_restore(); writer.feedrate(m_travel_speed * 60.f) - .flush_planner_queue() - .reset_extruder() - .append("; CP TOOLCHANGE END\n" - ";------------------\n" - "\n\n"); + .flush_planner_queue() + .reset_extruder() + .append("; CP TOOLCHANGE END\n" + ";------------------\n" + "\n\n"); // Ask our writer about how much material was consumed: if (m_current_tool < m_used_filament_length.size()) @@ -1608,33 +1612,32 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool) return construct_tcr(writer, false, old_tool, false); } - // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. -void WipeTower2::toolchange_Unload( - WipeTowerWriter2 &writer, - const WipeTower::box_coordinates &cleaning_box, - const std::string& current_material, - const int old_temperature, - const int new_temperature) +void WipeTower2::toolchange_Unload(WipeTowerWriter2& writer, + const WipeTower::box_coordinates& cleaning_box, + const std::string& current_material, + const int old_temperature, + const int new_temperature) { - float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; - float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; + float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; + float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; - const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness - const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing_ramming; // spacing between lines in mm + const float line_width = m_perimeter_width * + m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness + const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * + m_extra_spacing_ramming; // spacing between lines in mm - const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f); + const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step / 2.f); - writer.append("; CP TOOLCHANGE UNLOAD\n") - .change_analyzer_line_width(line_width); + writer.append("; CP TOOLCHANGE UNLOAD\n").change_analyzer_line_width(line_width); - unsigned i = 0; // iterates through ramming_speed - m_left_to_right = true; // current direction of ramming - float remaining = xr - xl ; // keeps track of distance to the next turnaround - float e_done = 0; // measures E move done from each segment + unsigned i = 0; // iterates through ramming_speed + m_left_to_right = true; // current direction of ramming + float remaining = xr - xl; // keeps track of distance to the next turnaround + float e_done = 0; // measures E move done from each segment // Orca: Do ramming when SEMM and ramming is enabled or when multi tool head when ramming is enabled on the multi tool. - const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming; + const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming; const bool cold_ramming = m_is_mk4mmu3; if (do_ramming) { @@ -1644,36 +1647,34 @@ void WipeTower2::toolchange_Unload( writer.disable_linear_advance_value(m_change_pressure_value); } } - + if (cold_ramming) writer.set_extruder_temp(old_temperature - 20); - } - else + } else writer.set_position(ramming_start_pos); // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: - if (do_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { - + if (do_ramming && + (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info - 1 != m_plan.begin() || !m_adhesion))) { // this is y of the center of previous sparse infill border float sparse_beginning_y = 0.f; if (m_current_shape == SHAPE_REVERSED) - sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth()) - - ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ; + sparse_beginning_y += ((m_layer_info - 1)->depth - (m_layer_info - 1)->toolchanges_depth()) - + ((m_layer_info)->depth - (m_layer_info)->toolchanges_depth()); else - sparse_beginning_y += (m_layer_info-1)->toolchanges_depth() + m_perimeter_width; + sparse_beginning_y += (m_layer_info - 1)->toolchanges_depth() + m_perimeter_width; float sum_of_depths = 0.f; - for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange + for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange if (tch.old_tool == m_current_tool) { sum_of_depths += tch.ramming_depth; float ramming_end_y = sum_of_depths; - ramming_end_y -= (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f; // center of final ramming line + ramming_end_y -= (y_step / m_extra_spacing_ramming - m_perimeter_width) / 2.f; // center of final ramming line - if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) || - (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) ) - { - writer.extrude(xl + tch.first_wipe_line-1.f*m_perimeter_width,writer.y()); - remaining -= tch.first_wipe_line-1.f*m_perimeter_width; + if ((m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f * m_perimeter_width) || + (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f * m_perimeter_width)) { + writer.extrude(xl + tch.first_wipe_line - 1.f * m_perimeter_width, writer.y()); + remaining -= tch.first_wipe_line - 1.f * m_perimeter_width; } break; } @@ -1685,7 +1686,6 @@ void WipeTower2::toolchange_Unload( writer.switch_filament_monitoring(false); writer.wait(1.5f); } - bool is_over_tower_height = false; if (m_plan.size() > 0 && m_num_layer_changes == m_plan.size()) { @@ -1693,60 +1693,61 @@ void WipeTower2::toolchange_Unload( } // now the ramming itself: - while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size() && !is_over_tower_height) - { + while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size() && !is_over_tower_height) { // The time step is different for SEMM ramming and the MM ramming. See comments in set_extruder() for details. const float time_step = m_semm ? 0.25f : m_filpar[m_current_tool].multitool_ramming_time; - const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * time_step, line_width, m_layer_height); - const float e = m_filpar[m_current_tool].ramming_speed[i] * time_step / filament_area(); // transform volume per sec to E move; - const float dist = std::min(x - e_done, remaining); // distance to travel for either the next time_step, or to the next turnaround - const float actual_time = dist/x * time_step; + const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * time_step, line_width, m_layer_height); + const float e = m_filpar[m_current_tool].ramming_speed[i] * time_step / filament_area(); // transform volume per sec to E move; + const float dist = std::min(x - e_done, remaining); // distance to travel for either the next time_step, or to the next turnaround + const float actual_time = dist / x * time_step; writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0.f, 0.f, e * (dist / x), dist / (actual_time / 60.f)); remaining -= dist; - if (remaining < WT_EPSILON) { // we reached a turning point - writer.travel(writer.x(), writer.y() + y_step, 7200); - m_left_to_right = !m_left_to_right; - remaining = xr - xl; - } - e_done += dist; // subtract what was actually done - if (e_done > x - WT_EPSILON) { // current segment finished - ++i; - e_done = 0; - } - } - Vec2f end_of_ramming(writer.x(),writer.y()); - writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier + if (remaining < WT_EPSILON) { // we reached a turning point + writer.travel(writer.x(), writer.y() + y_step, 7200); + m_left_to_right = !m_left_to_right; + remaining = xr - xl; + } + e_done += dist; // subtract what was actually done + if (e_done > x - WT_EPSILON) { // current segment finished + ++i; + e_done = 0; + } + } + Vec2f end_of_ramming(writer.x(), writer.y()); + writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier // Retraction: - if(m_enable_filament_ramming) + if (m_enable_filament_ramming) writer.append("; Ramming start\n"); - float old_x = writer.x(); - float turning_point = (!m_left_to_right ? xl : xr ); + float old_x = writer.x(); + float turning_point = (!m_left_to_right ? xl : xr); if (m_enable_filament_ramming && m_semm && (m_cooling_tube_retraction != 0 || m_cooling_tube_length != 0)) { writer.append("; Retract(unload)\n"); - float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming + float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length / 2.f - + 15.f; // the 15mm is reserved for the first part after ramming writer.suppress_preview() - .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s - .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) - .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) - .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) - .resume_preview(); + .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s + .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) + .resume_preview(); } const int& number_of_cooling_moves = m_filpar[m_current_tool].cooling_moves; - const bool cooling_will_happen = m_enable_filament_ramming && m_semm && number_of_cooling_moves > 0 && m_cooling_tube_length != 0; - bool change_temp_later = false; + const bool cooling_will_happen = m_enable_filament_ramming && m_semm && number_of_cooling_moves > 0 && m_cooling_tube_length != 0; + bool change_temp_later = false; // Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should // be already set and there is no need to change anything. Also, the temperature could be changed // for wrong extruder. if (m_semm) { - if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer() || cold_ramming) ) { // Set the extruder temperature, but don't wait. - // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) - // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). + if (new_temperature != 0 && + (new_temperature != m_old_temperature || is_first_layer() || cold_ramming)) { // Set the extruder temperature, but don't wait. + // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be + // reset) However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). if (cold_ramming && cooling_will_happen) change_temp_later = true; else @@ -1768,19 +1769,15 @@ void WipeTower2::toolchange_Unload( writer.disable_linear_advance_value(m_change_pressure_value); } } - - writer.suppress_preview() - .travel(writer.x(), writer.y() + y_step); - old_x = writer.x(); - turning_point = xr-old_x > old_x-xl ? xr : xl; + writer.suppress_preview().travel(writer.x(), writer.y() + y_step); + old_x = writer.x(); + turning_point = xr - old_x > old_x - xl ? xr : xl; float stamping_dist_e = m_filpar[m_current_tool].filament_stamping_distance + m_cooling_tube_length / 2.f; - for (int i=0; i0 && m_filpar[m_current_tool].filament_stamping_distance != 0) { - + if (i > 0 && m_filpar[m_current_tool].filament_stamping_distance != 0) { // Stamping turning point shall be no farther than 20mm from the current nozzle position: float stamping_turning_point = std::clamp(old_x + 20.f * (turning_point - old_x > 0.f ? 1.f : -1.f), xl, xr); @@ -1788,11 +1785,13 @@ void WipeTower2::toolchange_Unload( // along the whole wipe tower. if (stamping_dist_e > 5) { float cent = writer.x(); - writer.load_move_x_advanced(stamping_turning_point, (stamping_dist_e - 5), m_filpar[m_current_tool].filament_stamping_loading_speed, 200); + writer.load_move_x_advanced(stamping_turning_point, (stamping_dist_e - 5), + m_filpar[m_current_tool].filament_stamping_loading_speed, 200); writer.load_move_x_advanced(cent, 5, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed); writer.travel(cent, writer.y()); } else - writer.load_move_x_advanced_there_and_back(stamping_turning_point, stamping_dist_e, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed); + writer.load_move_x_advanced_there_and_back(stamping_turning_point, stamping_dist_e, + m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed); // Retract while the print head is stationary, so if there is a blob, it is not dragged along. writer.retract(stamping_dist_e, m_filpar[m_current_tool].unloading_speed * 60.f); @@ -1800,10 +1799,10 @@ void WipeTower2::toolchange_Unload( if (i == number_of_cooling_moves - 1 && change_temp_later) { // If cold_ramming, the temperature change should be done before the last cooling move. - writer.set_extruder_temp(new_temperature, false); + writer.set_extruder_temp(new_temperature, false); } - float speed = initial_speed + speed_inc * 2*i; + float speed = initial_speed + speed_inc * 2 * i; writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed); speed += speed_inc; writer.load_move_x_advanced(old_x, -m_cooling_tube_length, speed); @@ -1820,35 +1819,31 @@ void WipeTower2::toolchange_Unload( writer.retract(_e, 2000); } - if(m_enable_filament_ramming) + if (m_enable_filament_ramming) writer.append("; Ramming end\n"); - // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material - Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width); + Vec2f pos = Vec2f(end_of_ramming.x(), + end_of_ramming.y() + (y_step / m_extra_spacing_ramming - m_perimeter_width) / 2.f + m_perimeter_width); if (do_ramming) writer.travel(pos, 2400.f); else writer.set_position(pos); - writer.resume_preview() - .flush_planner_queue(); + writer.resume_preview().flush_planner_queue(); } // Change the tool, set a speed override for soluble and flex materials. -void WipeTower2::toolchange_Change( - WipeTowerWriter2 &writer, - const size_t new_tool, - const std::string& new_material) +void WipeTower2::toolchange_Change(WipeTowerWriter2& writer, const size_t new_tool, const std::string& new_material) { // Ask the writer about how much of the old filament we consumed: if (m_current_tool < m_used_filament_length.size()) - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); // This is where we want to place the custom gcodes. We will use placeholders for this. // These will be substituted by the actual gcodes when the gcode is generated. - //writer.append("[end_filament_gcode]\n"); + // writer.append("[end_filament_gcode]\n"); writer.append("[change_filament_gcode]\n"); if (m_is_mk4mmu3) @@ -1858,45 +1853,41 @@ void WipeTower2::toolchange_Change( // gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the // postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before. Vec2f current_pos = writer.pos_rotated(); - writer.feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483 - .append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x()) - + " Y" + Slic3r::float_to_string_decimal_point(current_pos.y()) - + never_skip_tag() + "\n" - ); + writer + .feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483 + .append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x()) + " Y" + + Slic3r::float_to_string_decimal_point(current_pos.y()) + never_skip_tag() + "\n"); writer.append("[deretraction_from_wipe_tower_generator]"); // The toolchange Tn command will be inserted later, only in case that the user does // not provide a custom toolchange gcode. - writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed. - // writer.append("[filament_start_gcode]\n"); + writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed. + // writer.append("[filament_start_gcode]\n"); - - writer.flush_planner_queue(); - m_current_tool = new_tool; + writer.flush_planner_queue(); + m_current_tool = new_tool; } -void WipeTower2::toolchange_Load( - WipeTowerWriter2 &writer, - const WipeTower::box_coordinates &cleaning_box) +void WipeTower2::toolchange_Load(WipeTowerWriter2& writer, const WipeTower::box_coordinates& cleaning_box) { if (m_semm && m_enable_filament_ramming && (m_parking_pos_retraction != 0 || m_extra_loading_move != 0)) { - float xl = cleaning_box.ld.x() + m_perimeter_width * 0.75f; - float xr = cleaning_box.rd.x() - m_perimeter_width * 0.75f; - float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position + float xl = cleaning_box.ld.x() + m_perimeter_width * 0.75f; + float xr = cleaning_box.rd.x() - m_perimeter_width * 0.75f; + float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position // Load the filament while moving left / right, so the excess material will not create a blob at a single position. - float turning_point = ( oldx-xl < xr-oldx ? xr : xl ); - float edist = m_parking_pos_retraction+m_extra_loading_move; + float turning_point = (oldx - xl < xr - oldx ? xr : xl); + float edist = m_parking_pos_retraction + m_extra_loading_move; writer.append("; CP TOOLCHANGE LOAD\n") - .suppress_preview() - .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) - .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + .suppress_preview() + .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) + .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate - .resume_preview(); + .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate + .resume_preview(); // Reset the extruder current to the normal value. if (m_set_extruder_trimpot) @@ -1905,67 +1896,70 @@ void WipeTower2::toolchange_Load( } // Wipe the newly loaded filament until the end of the assigned wipe area. -void WipeTower2::toolchange_Wipe( - WipeTowerWriter2 &writer, - const WipeTower::box_coordinates &cleaning_box, - float wipe_volume) +void WipeTower2::toolchange_Wipe(WipeTowerWriter2& writer, const WipeTower::box_coordinates& cleaning_box, float wipe_volume) { - // Increase flow on first layer, slow down print. - writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f)) - .append("; CP TOOLCHANGE WIPE\n"); - const float& xl = cleaning_box.ld.x(); - const float& xr = cleaning_box.rd.x(); + // Increase flow on first layer, slow down print. + writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f)).append("; CP TOOLCHANGE WIPE\n"); + const float& xl = cleaning_box.ld.x(); + const float& xr = cleaning_box.rd.x(); writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow); const float line_width = m_perimeter_width * m_extra_flow; writer.change_analyzer_line_width(line_width); - // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least + // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply // wipe until the end of the assigned area. - float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow; - float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow. + float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow; + float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * + m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow. // All the calculations in all other places take the spacing into account for all the layers. - // If spare layers are excluded->if 1 or less toolchange has been done, it must be sill the first layer, too.So slow down. - const float target_speed = is_first_layer() || (m_num_tool_changes <= 1 && m_no_sparse_layers) ? m_first_layer_speed * 60.f : std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f); - float wipe_speed = 0.33f * target_speed; + // If spare layers are excluded->if 1 or less toolchange has been done, it must be sill the first layer, too.So slow down. + const float target_speed = is_first_layer() || (m_num_tool_changes <= 1 && m_no_sparse_layers) ? + m_first_layer_speed * 60.f : + std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f); + float wipe_speed = 0.33f * target_speed; // if there is less than 2.5*line_width to the edge, advance straightaway (there is likely a blob anyway) - if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*line_width) { - writer.travel((m_left_to_right ? xr-line_width : xl+line_width),writer.y()+dy); + if ((m_left_to_right ? xr - writer.x() : writer.x() - xl) < 2.5f * line_width) { + writer.travel((m_left_to_right ? xr - line_width : xl + line_width), writer.y() + dy); m_left_to_right = !m_left_to_right; } - + // now the wiping itself: - for (int i = 0; true; ++i) { - if (i!=0) { - if (wipe_speed < 0.34f * target_speed) wipe_speed = 0.375f * target_speed; - else if (wipe_speed < 0.377 * target_speed) wipe_speed = 0.458f * target_speed; - else if (wipe_speed < 0.46f * target_speed) wipe_speed = 0.875f * target_speed; - else wipe_speed = std::min(target_speed, wipe_speed + 50.f); - } + for (int i = 0; true; ++i) { + if (i != 0) { + if (wipe_speed < 0.34f * target_speed) + wipe_speed = 0.375f * target_speed; + else if (wipe_speed < 0.377 * target_speed) + wipe_speed = 0.458f * target_speed; + else if (wipe_speed < 0.46f * target_speed) + wipe_speed = 0.875f * target_speed; + else + wipe_speed = std::min(target_speed, wipe_speed + 50.f); + } - float traversed_x = writer.x(); - if (m_left_to_right) - writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed); - else - writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed); + float traversed_x = writer.x(); + if (m_left_to_right) + writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f * line_width), writer.y(), wipe_speed); + else + writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f * line_width), writer.y(), wipe_speed); - if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width) - break; // in case next line would not fit + if (writer.y() + float(EPSILON) > cleaning_box.lu.y() - 0.5f * line_width) + break; // in case next line would not fit - traversed_x -= writer.x(); + traversed_x -= writer.x(); x_to_wipe -= std::abs(traversed_x); - if (x_to_wipe < WT_EPSILON) { - writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200); - break; - } - // stepping to the next line: - writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*line_width, writer.y() + dy); - m_left_to_right = !m_left_to_right; - } + if (x_to_wipe < WT_EPSILON) { + writer.travel(m_left_to_right ? xl + 1.5f * line_width : xr - 1.5f * line_width, writer.y(), 7200); + break; + } + // stepping to the next line: + writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f * line_width, writer.y() + dy); + m_left_to_right = !m_left_to_right; + } // We may be going back to the model - wipe the nozzle. If this is followed // by finish_layer, this wipe path will be overwritten. @@ -1980,51 +1974,47 @@ void WipeTower2::toolchange_Wipe( writer.change_analyzer_line_width(m_perimeter_width); } - - - WipeTower::ToolChangeResult WipeTower2::finish_layer() { - assert(! this->layer_finished()); + assert(!this->layer_finished()); m_current_layer_finished = true; size_t old_tool = m_current_tool; - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); - writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_initial_tool(m_current_tool) + WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); - - // Slow down on the 1st layer. + // Slow down on the 1st layer. // If spare layers are excluded -> if 1 or less toolchange has been done, it must be still the first layer, too. So slow down. - bool first_layer = is_first_layer() || (m_num_tool_changes <= 1 && m_no_sparse_layers); - float feedrate = first_layer ? m_first_layer_speed * 60.f : std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f); + bool first_layer = is_first_layer() || (m_num_tool_changes <= 1 && m_no_sparse_layers); + float feedrate = first_layer ? m_first_layer_speed * 60.f : std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f); float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); - WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), - m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); - + WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth - (current_depth - m_perimeter_width)), + m_wipe_tower_width - 2 * m_perimeter_width, current_depth - m_perimeter_width); writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel - m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; // inner perimeter of the sparse section, if there is space for it: if (fill_box.ru.y() - fill_box.rd.y() > m_perimeter_width - WT_EPSILON) - writer.rectangle(fill_box.ld, fill_box.rd.x()-fill_box.ld.x(), fill_box.ru.y()-fill_box.rd.y(), feedrate); + writer.rectangle(fill_box.ld, fill_box.rd.x() - fill_box.ld.x(), fill_box.ru.y() - fill_box.rd.y(), feedrate); // we are in one of the corners, travel to ld along the perimeter: - if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); - if (writer.y() > fill_box.ld.y()+EPSILON) writer.travel(writer.x(),fill_box.ld.y()); + if (writer.x() > fill_box.ld.x() + EPSILON) + writer.travel(fill_box.ld.x(), writer.y()); + if (writer.y() > fill_box.ld.y() + EPSILON) + writer.travel(writer.x(), fill_box.ld.y()); // Extrude infill to support the material to be printed above. - const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); - float left = fill_box.lu.x() + 2*m_perimeter_width; - float right = fill_box.ru.x() - 2 * m_perimeter_width; - if (dy > m_perimeter_width) - { + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + float left = fill_box.lu.x() + 2 * m_perimeter_width; + float right = fill_box.ru.x() - 2 * m_perimeter_width; + if (dy > m_perimeter_width) { writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) .append(";--------------------\n" "; CP EMPTY GRID START\n") @@ -2032,30 +2022,27 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer() // Is there a soluble filament wiped/rammed at the next layer? // If so, the infill should not be sparse. - bool solid_infill = m_layer_info+1 == m_plan.end() - ? false - : std::any_of((m_layer_info+1)->tool_changes.begin(), - (m_layer_info+1)->tool_changes.end(), + bool solid_infill = m_layer_info + 1 == m_plan.end() ? + false : + std::any_of((m_layer_info + 1)->tool_changes.begin(), (m_layer_info + 1)->tool_changes.end(), [this](const WipeTowerInfo::ToolChange& tch) { - return m_filpar[tch.new_tool].is_soluble - || m_filpar[tch.old_tool].is_soluble; + return m_filpar[tch.new_tool].is_soluble || m_filpar[tch.old_tool].is_soluble; }); solid_infill |= first_layer && m_adhesion; if (solid_infill) { float sparse_factor = 1.5f; // 1=solid, 2=every other line, etc. - if (first_layer) { // the infill should touch perimeters - left -= m_perimeter_width; + if (first_layer) { // the infill should touch perimeters + left -= m_perimeter_width; right += m_perimeter_width; sparse_factor = 1.f; } - float y = fill_box.ld.y() + m_perimeter_width; - int n = dy / (m_perimeter_width * sparse_factor); - float spacing = (dy-m_perimeter_width)/(n-1); - int i=0; - for (i=0; itoolchanges_depth() : 0.f)), - m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + if (m_wall_type == (int) wtwCone) { + WipeTower::box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), + m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); // outer contour (always) bool infill_cone = first_layer && m_wipe_tower_width > 2 * spacing && m_wipe_tower_depth > 2 * spacing; - poly = generate_support_cone_wall(writer, wt_box, feedrate, infill_cone, spacing); + poly = generate_support_cone_wall(writer, wt_box, feedrate, infill_cone, spacing); } else { WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); - poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true, false); + poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int) wtwRib, true, false); } // brim with chamfer (gradual layer-by-layer reduction) - int loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing; + int loops_num = (m_wipe_tower_brim_width + spacing / 2.f) / spacing; - // Apply brim logic based on chamfer setting - if (m_wipe_tower_brim_width > 0) { - if (first_layer) { - // First layer: always print full brim (loops_num unchanged) - } else if (m_prime_tower_brim_chamfer) { - // Non-first layer + chamfer enabled: apply gradual reduction + // Apply chamfer reduction if feature is enabled and brim width is configured + if (m_wipe_tower_brim_width > 0 && m_prime_tower_brim_chamfer) { + if (!first_layer) { // Calculate distance from first layer with tool changes size_t current_idx = m_layer_info - m_plan.begin(); - int dist_to_1st = (int)current_idx - (int)m_first_layer_idx; + int dist_to_1st = (int) current_idx - (int) m_first_layer_idx; - // Validate m_first_layer_idx to prevent invalid index access - if (m_first_layer_idx == size_t(-1) || m_first_layer_idx >= m_plan.size()) { - // Invalid first layer index, don't print brim + // Stop print chamfer if depth changes + bool depth_changed = (m_layer_info->depth != m_plan[m_first_layer_idx].depth); + if (depth_changed) { loops_num = 0; - } - else { - // Stop print chamfer if depth changes - bool depth_changed = (m_layer_info->depth != m_plan[m_first_layer_idx].depth); - if (depth_changed) { + } else { + // Limit max chamfer width to configured value + int chamfer_loops_num = (int) (m_prime_tower_brim_chamfer_max_width / spacing); + loops_num = std::min(loops_num, chamfer_loops_num) - dist_to_1st; + // Ensure loops_num doesn't go negative + if (loops_num < 0) loops_num = 0; - } else { - // Limit max chamfer width to configured value - int chamfer_loops_num = (int) (m_prime_tower_brim_chamfer_max_width / spacing); - loops_num = std::min(loops_num, chamfer_loops_num) - dist_to_1st; - // Ensure loops_num doesn't go negative - if (loops_num < 0) - loops_num = 0; - } } - } else { - // Non-first layer + chamfer disabled: don't print brim (revert to original behavior) - loops_num = 0; } } @@ -2133,47 +2107,16 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer() writer.append("; WIPE_TOWER_BRIM_START\n"); for (int i = 0; i < loops_num; ++i) { - // SM Orca: FIX - Copy Bambu Studio's approach to prevent visual artifacts - // The issue: ClipperLib's jtMiter switches to DoSquare() when miter limit is exceeded - // This causes point count to drop (e.g., 336->256) creating "two extra lines" artifact - // Bambu's solution: Use default offset + apply simplify() to smooth geometry - // The simplification removes redundant points and prevents visual discontinuities - - // Use default offset (jtMiter with DefaultMiterLimit=3, matching Bambu) - poly = offset(poly, scale_(spacing)).front(); - - // CRITICAL: Apply Bambu's simplification approach - // Convert to Polyline and apply Douglas-Peucker simplification - // This removes redundant points and smooths geometric transitions - Polyline pl = to_polyline(poly); - - // Use simplified tolerance matching Bambu's WT_SIMPLIFY_TOLERANCE_SCALED - // Bambu: 0.001 / SCALING_FACTOR, we use scaled(0.01) for similar effect - pl.simplify(scaled(0.01)); - - // Find closest point and travel there - // Note: pl.points has one extra point (closed loop), so we iterate to size()-1 - int cp = 0; - Point current_pos = Point::new_scale(writer.x(), writer.y()); - float min_dist_sq = std::numeric_limits::max(); - - for (size_t j = 0; j < pl.points.size() - 1; ++j) { - float dist_sq = (pl.points[j].cast() - current_pos.cast()).squaredNorm(); - if (dist_sq < min_dist_sq) { - min_dist_sq = dist_sq; - cp = j; - } + poly = offset(poly, scale_(spacing)).front(); + int cp = poly.closest_point_index(Point::new_scale(writer.x(), writer.y())); + writer.travel(unscale(poly.points[cp]).cast()); + for (int j = cp + 1; true; ++j) { + if (j == int(poly.points.size())) + j = 0; + writer.extrude(unscale(poly.points[j]).cast()); + if (j == cp) + break; } - - writer.travel(unscale(pl.points[cp]).cast()); - - // Extrude the simplified polyline (skip the last duplicate point) - for (size_t j = 1; j < pl.points.size(); ++j) { - size_t idx = (cp + j) % (pl.points.size() - 1); - writer.extrude(unscale(pl.points[idx]).cast()); - } - // Close the loop - writer.extrude(unscale(pl.points[cp]).cast()); } writer.append("; WIPE_TOWER_BRIM_END\n"); @@ -2187,11 +2130,11 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer() // Now prepare future wipe. int i = poly.closest_point_index(Point::new_scale(writer.x(), writer.y())); writer.add_wipe_point(writer.pos()); - writer.add_wipe_point(unscale(poly.points[i==0 ? int(poly.points.size())-1 : i-1]).cast()); + writer.add_wipe_point(unscale(poly.points[i == 0 ? int(poly.points.size()) - 1 : i - 1]).cast()); // Ask our writer about how much material was consumed. // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. - if (! m_no_sparse_layers || toolchanges_on_layer || first_layer) { + if (!m_no_sparse_layers || toolchanges_on_layer || first_layer) { if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); m_current_height += m_layer_info->height; @@ -2203,15 +2146,15 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer() // Static method to get the radius and x-scaling of the stabilizing cone base. std::pair WipeTower2::get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg) { - double R = std::tan(Geometry::deg2rad(angle_deg/2.)) * height; - double fake_width = 0.66 * width; - double diag = std::hypot(fake_width / 2., depth / 2.); + double R = std::tan(Geometry::deg2rad(angle_deg / 2.)) * height; + double fake_width = 0.66 * width; + double diag = std::hypot(fake_width / 2., depth / 2.); double support_scale = 1.; if (R > diag) { - double w = fake_width; - double sin = 0.5 * depth / diag; - double tan = depth / w; - double t = (R - diag) * sin; + double w = fake_width; + double sin = 0.5 * depth / diag; + double tan = depth / w; + double t = (R - diag) * sin; support_scale = (w / 2. + t / tan + t * tan) / (w / 2.); } return std::make_pair(R, support_scale); @@ -2222,22 +2165,23 @@ std::vector> WipeTower2::extract_wipe_volumes(const PrintConf { // Get wiping matrix to get number of extruders and convert vector to vector: std::vector wiping_matrix(cast(config.flush_volumes_matrix.values)); - auto scale = config.flush_multiplier; + auto scale = config.flush_multiplier; // The values shall only be used when SEMM is enabled. The purging for other printers // is determined by filament_minimal_purge_on_wipe_tower. - if (! config.purge_in_prime_tower.value || ! config.single_extruder_multi_material.value) + if (!config.purge_in_prime_tower.value || !config.single_extruder_multi_material.value) std::fill(wiping_matrix.begin(), wiping_matrix.end(), 0.f); // Extract purging volumes for each extruder pair: std::vector> wipe_volumes; - const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON); - for (size_t i = 0; i(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders)); + const unsigned int number_of_extruders = (unsigned int) (sqrt(wiping_matrix.size()) + EPSILON); + for (size_t i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back( + std::vector(wiping_matrix.begin() + i * number_of_extruders, wiping_matrix.begin() + (i + 1) * number_of_extruders)); // Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview. - for (unsigned int i = 0; i(wipe_volumes[i][j] * scale, config.filament_minimal_purge_on_wipe_tower.get_at(j)); return wipe_volumes; @@ -2246,73 +2190,74 @@ std::vector> WipeTower2::extract_wipe_volumes(const PrintConf static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width) { float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow; - length_to_extrude = std::max(length_to_extrude,0.f); + length_to_extrude = std::max(length_to_extrude, 0.f); - return (int(length_to_extrude / width) + 1) * perimeter_width * extra_spacing; + return (int(length_to_extrude / width) + 1) * perimeter_width * extra_spacing; } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box -void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, - unsigned int new_tool, float wipe_volume) +void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume) { - assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one + assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one - if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first - m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); + if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first + m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); - if (m_first_layer_idx == size_t(-1) && (! m_no_sparse_layers || old_tool != new_tool || m_plan.size() == 1)) + if (m_first_layer_idx == size_t(-1) && (!m_no_sparse_layers || old_tool != new_tool || m_plan.size() == 1)) m_first_layer_idx = m_plan.size() - 1; - if (old_tool == new_tool) // new layer without toolchanges - we are done + if (old_tool == new_tool) // new layer without toolchanges - we are done return; // this is an actual toolchange - let's calculate depth to reserve on the wipe tower - float width = m_wipe_tower_width - 3*m_perimeter_width; - float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f), - m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator, - layer_height_par); + float width = m_wipe_tower_width - 3 * m_perimeter_width; + float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), + m_filpar[old_tool].ramming_speed.end(), 0.f), + m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator, layer_height_par); // Orca: Set ramming depth to 0 if ramming is disabled. - float ramming_depth = m_enable_filament_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0; - float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width); + float ramming_depth = m_enable_filament_ramming ? ((int(length_to_extrude / width) + 1) * + (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * + m_filpar[old_tool].ramming_step_multiplicator) * + m_extra_spacing_ramming) : + 0; + float first_wipe_line = -(width * ((length_to_extrude / width) - int(length_to_extrude / width)) - width); float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par); - float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); - - m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume)); + float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, + m_extra_spacing_wipe, width); + + m_plan.back().tool_changes.push_back( + WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume)); } - - void WipeTower2::plan_tower() { - // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards - m_wipe_tower_depth = 0.f; - for (auto& layer : m_plan) - layer.depth = 0.f; + // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards + m_wipe_tower_depth = 0.f; + for (auto& layer : m_plan) + layer.depth = 0.f; m_wipe_tower_height = m_plan.empty() ? 0.f : m_plan.back().z; - m_current_height = 0.f; - - for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) - { - float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth()); - m_plan[layer_index].depth = this_layer_depth; - - if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width) - m_wipe_tower_depth = this_layer_depth + m_perimeter_width; + m_current_height = 0.f; - for (int i = layer_index - 1; i >= 0 ; i--) - { - if (m_plan[i].depth - this_layer_depth < 2*m_perimeter_width ) - m_plan[i].depth = this_layer_depth; - } - } + for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) { + float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth()); + m_plan[layer_index].depth = this_layer_depth; + + if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width) + m_wipe_tower_depth = this_layer_depth + m_perimeter_width; + + for (int i = layer_index - 1; i >= 0; i--) { + if (m_plan[i].depth - this_layer_depth < 2 * m_perimeter_width) + m_plan[i].depth = this_layer_depth; + } + } } void WipeTower2::save_on_last_wipe() { - for (m_layer_info=m_plan.begin();m_layer_infoz, m_layer_info->height, 0, m_layer_info->z == m_plan.front().z, m_layer_info->z == m_plan.back().z); - if (m_layer_info->tool_changes.size()==0) // we have no way to save anything on an empty layer + if (m_layer_info->tool_changes.size() == 0) // we have no way to save anything on an empty layer continue; // Which toolchange will finish_layer extrusions be subtracted from? @@ -2323,68 +2268,73 @@ void WipeTower2::save_on_last_wipe() finish_layer().total_extrusion_length_in_plane(); } - for (int i=0; itool_changes.size()); ++i) { + for (int i = 0; i < int(m_layer_info->tool_changes.size()); ++i) { auto& toolchange = m_layer_info->tool_changes[i]; tool_change(toolchange.new_tool); if (i == idx) { - float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into + float width = m_wipe_tower_width - 3 * m_perimeter_width; // width we draw into - float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height); - float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save); - float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height)); - float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); + float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, + m_layer_info->height); + float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, + toolchange.wipe_volume_total - volume_to_save); + float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, + m_perimeter_width * m_extra_flow, + m_layer_info->height)); + float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, + m_extra_spacing_wipe, width); toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe; - toolchange.wipe_volume = volume_left_to_wipe; + toolchange.wipe_volume = volume_left_to_wipe; } } } } - // Return index of first toolchange that switches to non-soluble extruder // ot -1 if there is no such toolchange. -int WipeTower2::first_toolchange_to_nonsoluble( - const std::vector& tool_changes) const +int WipeTower2::first_toolchange_to_nonsoluble(const std::vector& tool_changes) const { // 使用 wipe_tower_filament 配置来决定哪个挤出机用于 wipe tower - for (size_t idx=0; idx> &result) +void WipeTower2::generate(std::vector>& result) { - if (m_plan.empty()) + if (m_plan.empty()) return; - plan_tower(); -#if 1 - for (int i=0;i<5;++i) { + plan_tower(); +#if 0 +// BBS: Disabled 5-iteration loop - matching Bambu Studio's approach +// This loop causes instability in depth calculations which leads to out-of-bounds coordinates +// The loop recalculates required_depth in save_on_last_wipe() and propagates it downward via plan_tower() +// After multiple iterations, depth can exceed reasonable bounds, causing m_y_shift to change +// This in turn causes the rotate() function to generate negative coordinates + for (int i = 0; i < 5; ++i) { save_on_last_wipe(); plan_tower(); } @@ -2396,7 +2346,7 @@ void WipeTower2::generate(std::vector> m_rib_width = std::min(m_rib_width, std::min(m_wipe_tower_depth, m_wipe_tower_width) / 2.f); // Ensure that the rib wall of the wipetower are attached to the infill. - m_layer_info = m_plan.begin(); + m_layer_info = m_plan.begin(); m_current_height = 0.f; // we don't know which extruder to start with - we'll set it according to the first toolchange @@ -2413,16 +2363,15 @@ void WipeTower2::generate(std::vector> m_old_temperature = -1; // reset last temperature written in the gcode - for (const WipeTower2::WipeTowerInfo& layer : m_plan) - { + for (const WipeTower2::WipeTowerInfo& layer : m_plan) { std::vector layer_result; - set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z); + set_layer(layer.z, layer.height, 0, false /*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z); m_internal_rotation += 180.f; if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) - m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; + m_y_shift = (m_wipe_tower_depth - m_layer_info->depth - m_perimeter_width) / 2.f; - int idx = first_toolchange_to_nonsoluble(layer.tool_changes); + int idx = first_toolchange_to_nonsoluble(layer.tool_changes); WipeTower::ToolChangeResult finish_layer_tcr; if (idx == -1) { @@ -2432,7 +2381,7 @@ void WipeTower2::generate(std::vector> finish_layer_tcr = finish_layer(); } - for (int i=0; i> if (layer_result.empty()) { // there is nothing to merge finish_layer with layer_result.emplace_back(std::move(finish_layer_tcr)); - } - else { + } else { if (idx == -1) { - layer_result[0] = merge_tcr(finish_layer_tcr, layer_result[0]); + layer_result[0] = merge_tcr(finish_layer_tcr, layer_result[0]); layer_result[0].force_travel = true; - } - else + } else layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr); } - result.emplace_back(std::move(layer_result)); + result.emplace_back(std::move(layer_result)); if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z) m_used_filament_length_until_layer.emplace_back(); m_used_filament_length_until_layer.back() = std::make_pair(layer.z, m_used_filament_length); - } + } } - - std::vector> WipeTower2::get_z_and_depth_pairs() const { std::vector> out = {{0.f, m_wipe_tower_depth}}; @@ -2474,10 +2419,8 @@ std::vector> WipeTower2::get_z_and_depth_pairs() const return out; } - Polygon WipeTower2::generate_rib_polygon(const WipeTower::box_coordinates& wt_box) { - auto get_current_layer_rib_len = [](float cur_height, float max_height, float max_len) -> float { return std::abs(max_height - cur_height) / max_height * max_len; }; @@ -2512,13 +2455,12 @@ Polygon WipeTower2::generate_rib_polygon(const WipeTower::box_coordinates& wt_bo Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2& writer, const WipeTower::box_coordinates& wt_box, - double feedrate, - bool first_layer, - bool rib_wall, - bool extrude_perimeter, - bool skip_points) + double feedrate, + bool first_layer, + bool rib_wall, + bool extrude_perimeter, + bool skip_points) { - float retract_length = m_filpar[m_current_tool].retract_length; float retract_speed = m_filpar[m_current_tool].retract_speed * 60; Polygon wall_polygon = rib_wall ? generate_rib_polygon(wt_box) : generate_rectange_polygon(wt_box.ld, wt_box.ru); @@ -2544,20 +2486,19 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2& insert_skip_polygon = wall_polygon; } writer.generate_path(result_wall, feedrate, retract_length, retract_speed, m_used_fillet); - //if (m_cur_layer_id == 0) { - // BoundingBox bbox = get_extents(result_wall); - // m_rib_offset = Vec2f(-unscaled(bbox.min.x()), -unscaled(bbox.min.y())); - //} + // if (m_cur_layer_id == 0) { + // BoundingBox bbox = get_extents(result_wall); + // m_rib_offset = Vec2f(-unscaled(bbox.min.x()), -unscaled(bbox.min.y())); + // } return insert_skip_polygon; } - // This block creates the stabilization cone. // First define a lambda to draw the rectangle with stabilization. Polygon WipeTower2::generate_support_cone_wall( - WipeTowerWriter2& writer, const WipeTower::box_coordinates& wt_box, double feedrate, bool infill_cone, float spacing){ - + WipeTowerWriter2& writer, const WipeTower::box_coordinates& wt_box, double feedrate, bool infill_cone, float spacing) +{ const auto [R, support_scale] = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth, m_wipe_tower_cone_angle); diff --git a/src/libslic3r/GCode/WipeTower2.hpp b/src/libslic3r/GCode/WipeTower2.hpp index 374468154e..736430046b 100644 --- a/src/libslic3r/GCode/WipeTower2.hpp +++ b/src/libslic3r/GCode/WipeTower2.hpp @@ -45,9 +45,7 @@ public: // Set the extruder properties. - // SM Orca: 添加 physical_extruder 参数,用于支持耗材-挤出机映射 - // idx: 耗材索引, physical_extruder: 物理挤出机索引 - void set_extruder(size_t idx, int physical_extruder, const PrintConfig& config); + void set_extruder(size_t idx, const PrintConfig& config); // Appends into internal structure m_plan containing info about the future wipe tower // to be used before building begins. The entries must be added ordered in z. @@ -162,8 +160,6 @@ public: float filament_minimal_purge_on_wipe_tower = 0.f; float retract_length; float retract_speed; - // SM Orca: Store per-filament perimeter width for correct brim generation - float perimeter_width = 0.f; }; private: diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index f2ffcc8683..ba36c2b6d4 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -27,10 +27,6 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) { this->config.apply(print_config, true); m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; - m_physical_extruder_count = print_config.nozzle_diameter.values.size(); - if (m_physical_extruder_count == 0) { - m_physical_extruder_count = 1; // 防止除零,默认为1 - } bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware || print_config.gcode_flavor.value == gcfKlipper || print_config.gcode_flavor.value == gcfRepRapFirmware; m_max_acceleration = std::lrint(use_mach_limits ? print_config.machine_max_acceleration_extruding.values.front() : 0); @@ -49,18 +45,17 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) void GCodeWriter::set_extruders(std::vector extruder_ids) { - std::sort(extruder_ids.begin(), extruder_ids.end()); m_extruder = nullptr; // this points to object inside `m_extruders`, so should be cleared too m_extruders.clear(); m_extruders.reserve(extruder_ids.size()); - for (unsigned int extruder_id : extruder_ids) { - int physical_extruder_id = get_physical_extruder(extruder_id); - - m_extruders.emplace_back(Extruder(extruder_id, physical_extruder_id, &this->config, config.single_extruder_multi_material.value)); - } - - this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0; + for (unsigned int extruder_id : extruder_ids) + m_extruders.emplace_back(Extruder(extruder_id, &this->config, config.single_extruder_multi_material.value)); + + /* we enable support for multiple extruder if any extruder greater than 0 is used + (even if prints only uses that one) since we need to output Tx commands + first extruder has index 0 */ + this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0; } std::string GCodeWriter::preamble() @@ -404,6 +399,7 @@ std::string GCodeWriter::set_input_shaping(char axis, float damp, float freq) co return gcode.str(); } + std::string GCodeWriter::reset_e(bool force) { if (FLAVOR_IS(gcfMach3) @@ -458,9 +454,6 @@ std::string GCodeWriter::toolchange_prefix() const std::string GCodeWriter::toolchange(unsigned int extruder_id) { - - int physical_extruder = get_physical_extruder(extruder_id); - // set the new extruder auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; }); assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id); @@ -476,7 +469,6 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id) gcode << " ; change extruder"; gcode << "\n"; gcode << this->reset_e(true); - } else { } return gcode.str(); } @@ -503,7 +495,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com this->set_current_position_clear(true); //BBS: take plate offset into consider Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset }; - + GCodeG1Formatter w; w.emit_xy(point_on_plate); auto speed = m_is_first_layer @@ -721,7 +713,7 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: m_pos(1) = point(1); if(std::abs(dE) <= std::numeric_limits::epsilon()) force_no_extrusion = true; - + if (!force_no_extrusion) m_extruder->extrude(dE); diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 67e7e09621..103bfb27c2 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -4,7 +4,6 @@ #include "libslic3r.h" #include #include -#include #include "Extruder.hpp" #include "Point.hpp" #include "PrintConfig.hpp" @@ -120,26 +119,6 @@ public: void set_is_first_layer(bool bval) { m_is_first_layer = bval; } GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; } - // SM Orca: 设置耗材-挤出机映射 - void set_filament_extruder_map(const std::unordered_map& map) { m_filament_extruder_map = map; } - const std::unordered_map& get_filament_extruder_map() const { return m_filament_extruder_map; } - // SM Orca: 获取物理挤出机ID - // 关键修复:当映射表为空时,使用模运算而不是直接返回耗材ID,避免越界 - // 例如:4个物理挤出机时,耗材0-7分别映射到0,1,2,3,0,1,2,3 - int get_physical_extruder(int filament_idx) const { - auto it = m_filament_extruder_map.find(filament_idx); - int physical_extruder_id; - if (it != m_filament_extruder_map.end()) { - // 从映射表获取 - physical_extruder_id = it->second; - } else { - // 映射表为空或没有该耗材的映射,使用默认模运算映射 - physical_extruder_id = filament_idx % m_physical_extruder_count; - } - - return physical_extruder_id; - } - // Returns whether this flavor supports separate print and travel acceleration. static bool supports_separate_travel_acceleration(GCodeFlavor flavor); private: @@ -191,11 +170,6 @@ public: double m_current_speed; bool m_is_first_layer = true; - // SM Orca: 耗材到物理挤出机的映射表(filament_idx -> physical_extruder_id) - std::unordered_map m_filament_extruder_map; - // SM Orca: 物理挤出机数量(用于默认模运算映射) - size_t m_physical_extruder_count = 1; // 默认为1,防止除零 - enum class Acceleration { Travel, Print diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b20f22a133..2011a58636 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -293,6 +293,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "wipe_tower_no_sparse_layers" || opt_key == "flush_volumes_matrix" || opt_key == "prime_volume" + || opt_key == "prime_tower_brim_chamfer" + || opt_key == "prime_tower_brim_chamfer_max_width" || opt_key == "flush_into_infill" || opt_key == "flush_into_support" || opt_key == "initial_layer_infill_speed" @@ -493,56 +495,6 @@ std::vector Print::extruders(bool conside_custom_gcode) const return extruders; } -// This must be called before the mapping is used (e.g., before export_gcode) -void Print::initialize_filament_extruder_map() -{ - m_filament_extruder_map.clear(); - - // Get the number of physical extruders (number of nozzle_diameter entries) - size_t physical_extruder_count = m_config.nozzle_diameter.values.size(); - - if (physical_extruder_count == 0) { - BOOST_LOG_TRIVIAL(error) << "Print::initialize_filament_extruder_map: ERROR - No physical extruders configured!"; - return; - } - - // Get all filament indices that will be used - std::vector filament_extruders = this->extruders(); - - // IMPORTANT: Always create mappings for ALL configured filaments, not just those used by objects. - // This is critical because filament override parameters need to access the mapping for all filaments. - // For example, if a user has 8 filaments configured but only uses 4 in their model, - // the mapping table must still contain entries for all 8 filaments to correctly - // inherit parameters from the corresponding physical extruders. - // extruders() returns empty. In this case, use filament_diameter.size() to determine filament count. - // This ensures the mapping is created for all configured filaments, not just those used by objects. - 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); - } - } else { - // Even if extruders() returns some values, we need to ensure ALL configured filaments are in the map. - // Add any missing filament indices that are configured but not used by objects. - size_t configured_filament_count = m_config.filament_diameter.size(); - for (size_t i = 0; i < configured_filament_count; ++i) { - if (std::find(filament_extruders.begin(), filament_extruders.end(), (unsigned int)i) == filament_extruders.end()) { - filament_extruders.push_back((unsigned int)i); - } - } - } - - // Create mapping: filament_id -> physical_extruder_id - // Mapping formula: physical_extruder = filament_id % physical_extruder_count - // This allows using 8 filaments with 4 physical extruders: - // filament 0,1,2,3 -> extruder 0,1,2,3 - // filament 4,5,6,7 -> extruder 0,1,2,3 - for (unsigned int filament_idx : filament_extruders) { - int physical_extruder = filament_idx % physical_extruder_count; - m_filament_extruder_map[filament_idx] = physical_extruder; - } -} - unsigned int Print::num_object_instances() const { unsigned int instances = 0; @@ -554,10 +506,8 @@ unsigned int Print::num_object_instances() const double Print::max_allowed_layer_height() const { double nozzle_diameter_max = 0.; - for (unsigned int extruder_id : this->extruders()) { - int physical_extruder = get_physical_extruder(extruder_id); - nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(physical_extruder)); - } + for (unsigned int extruder_id : this->extruders()) + nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(extruder_id)); return nozzle_diameter_max; } @@ -1235,12 +1185,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (this->has_wipe_tower() && ! m_objects.empty()) { // Make sure all extruders use same diameter filament and have the same nozzle diameter // EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments - int first_physical = get_physical_extruder(extruders.front()); - double first_nozzle_diam = m_config.nozzle_diameter.get_at(first_physical); + double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front()); double first_filament_diam = m_config.filament_diameter.get_at(extruders.front()); for (const auto& extruder_idx : extruders) { - int physical_extruder = get_physical_extruder(extruder_idx); - double nozzle_diam = m_config.nozzle_diameter.get_at(physical_extruder); + double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx); double filament_diam = m_config.filament_diameter.get_at(extruder_idx); if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam || std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) { @@ -1346,8 +1294,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* double min_nozzle_diameter = std::numeric_limits::max(); double max_nozzle_diameter = 0; for (unsigned int extruder_id : extruders) { - int physical_extruder = get_physical_extruder(extruder_id); - double dmr = m_config.nozzle_diameter.get_at(physical_extruder); + double dmr = m_config.nozzle_diameter.get_at(extruder_id); min_nozzle_diameter = std::min(min_nozzle_diameter, dmr); max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); } @@ -1435,10 +1382,9 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* size_t first_layer_extruder = object->config().raft_layers == 1 ? object->config().support_interface_filament-1 : object->config().support_filament-1; - int physical_extruder = get_physical_extruder(first_layer_extruder); first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? min_nozzle_diameter : - m_config.nozzle_diameter.get_at(physical_extruder); + m_config.nozzle_diameter.get_at(first_layer_extruder); } else { // if we don't have raft layers, any nozzle diameter is potentially used in first layer first_layer_min_nozzle_diameter = min_nozzle_diameter; @@ -1756,13 +1702,11 @@ Flow Print::brim_flow() const extruders and take the one with, say, the smallest index. The same logic should be applied to the code that selects the extruder during G-code generation as well. */ - int filament_idx = m_print_regions.front()->config().wall_filament - 1; - int physical_extruder = get_physical_extruder(filament_idx); return Flow::new_from_config_width( frPerimeter, // Flow::new_from_config_width takes care of the percent to value substitution width, - (float)m_config.nozzle_diameter.get_at(physical_extruder), + (float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament-1), (float)this->skirt_first_layer_height()); } @@ -1777,13 +1721,11 @@ Flow Print::skirt_flow() const extruders and take the one with, say, the smallest index; The same logic should be applied to the code that selects the extruder during G-code generation as well. */ - int filament_idx = m_objects.front()->config().support_filament - 1; - int physical_extruder = get_physical_extruder(filament_idx); return Flow::new_from_config_width( frPerimeter, // Flow::new_from_config_width takes care of the percent to value substitution width, - (float)m_config.nozzle_diameter.get_at(physical_extruder), + (float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_filament-1), (float)this->skirt_first_layer_height()); } @@ -1862,6 +1804,7 @@ void PrintObject::copy_layers_overhang_from_shared_object() } } + // BBS BoundingBox PrintObject::get_first_layer_bbox(float& a, float& layer_height, std::string& name) { @@ -2212,6 +2155,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) append(m_first_layer_convex_hull.points, std::move(poly.points)); } + if (has_skirt() && ! draft_shield) { // In case that draft shield is NOT active, generate skirt now. // It will be placed around the brim, so brim has to be ready. @@ -2299,42 +2243,11 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor //BBS: compute plate offset for gcode-generator const Vec3d origin = this->get_plate_origin(); gcode.set_gcode_offset(origin(0), origin(1)); - - this->initialize_filament_extruder_map(); - - for (const auto& pair : m_filament_extruder_map) { - } - gcode.set_filament_extruder_map(m_filament_extruder_map); gcode.do_export(this, path.c_str(), result, thumbnail_cb); //BBS result->conflict_result = m_conflict_result; - - // After G-code export is complete, finalize the output path by replacing placeholders with actual values - // This ensures that placeholders like {print_time} are replaced with calculated values - // Note: This is needed for direct export (not through BackgroundSlicingProcess) where - // finalize_output_path() might not be called automatically - std::string final_path = this->print_statistics().finalize_output_path(path); - - // Rename the file from the placeholder path to the finalized path - if (final_path != path) { - std::error_code ret = rename_file(path, final_path); - if (ret) { - BOOST_LOG_TRIVIAL(warning) << "Failed to rename G-code file from '" << path - << "' to '" << final_path << "': " << ret.message(); - // If rename fails, return the original path - return path; - } else { - BOOST_LOG_TRIVIAL(info) << "Renamed G-code file from '" << path - << "' to '" << final_path << "'"; - // Update result filename to reflect the new path - if (result) { - result->filename = final_path; - } - } - } - - return final_path; + return path.c_str(); } void Print::_make_skirt() @@ -2420,8 +2333,7 @@ void Print::_make_skirt() extruders_e_per_mm.reserve(set_extruders.size()); for (auto &extruder_id : set_extruders) { extruders.push_back(extruder_id); - int physical_extruder_id = get_physical_extruder(extruder_id); - extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, physical_extruder_id, &m_config, m_config.single_extruder_multi_material).e_per_mm(mm3_per_mm)); + extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &m_config, m_config.single_extruder_multi_material).e_per_mm(mm3_per_mm)); } } @@ -2821,10 +2733,8 @@ void Print::_make_wipe_tower() // wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. - for (size_t i = 0; i < number_of_extruders; ++i) { - int physical_extruder = get_physical_extruder(i); - wipe_tower.set_extruder(i, physical_extruder, m_config); - } + for (size_t i = 0; i < number_of_extruders; ++i) + wipe_tower.set_extruder(i, m_config); // BBS: remove priming logic // m_wipe_tower_data.priming = Slic3r::make_unique>( @@ -2919,10 +2829,8 @@ void Print::_make_wipe_tower() // wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. - for (size_t i = 0; i < number_of_extruders; ++i) { - int physical_extruder = get_physical_extruder(i); - wipe_tower.set_extruder(i, physical_extruder, m_config); - } + for (size_t i = 0; i < number_of_extruders; ++i) + wipe_tower.set_extruder(i, m_config); m_wipe_tower_data.priming = Slic3r::make_unique>( wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); @@ -3012,10 +2920,7 @@ std::string Print::output_filename(const std::string &filename_base) const { // Set the placeholders for the data know first after the G-code export is finished. // These values will be just propagated into the output file name. - // Use cached statistics if available (even if not finished) to avoid placeholders like {print_time} - const PrintStatistics& stats = this->print_statistics(); - bool has_valid_stats = stats.total_used_filament > 0 || !stats.estimated_normal_print_time.empty(); - DynamicConfig config = (this->finished() || has_valid_stats) ? stats.config() : stats.placeholders(); + DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); config.set_key_value("num_filaments", new ConfigOptionInt((int)m_config.nozzle_diameter.size())); config.set_key_value("num_extruders", new ConfigOptionInt((int) m_config.nozzle_diameter.size())); config.set_key_value("plate_name", new ConfigOptionString(get_plate_name())); @@ -3065,7 +2970,6 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces GCodeProcessor::s_IsBBLPrinter = is_BBL_printer(); const Vec3d origin = this->get_plate_origin(); processor.set_xy_offset(origin(0), origin(1)); - processor.set_filament_extruder_map(m_filament_extruder_map); //processor.enable_producers(true); processor.process_file(file); @@ -3209,6 +3113,7 @@ const std::string PrintStatistics::TotalFilamentCostValueMask = "; total filamen const std::string PrintStatistics::TotalFilamentUsedWipeTower = "total filament used for wipe tower [g]"; const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; total filament used for wipe tower [g] = %.2lf\n"; + /*add json export/import related functions */ #define JSON_POLYGON_CONTOUR "contour" #define JSON_POLYGON_HOLES "holes" @@ -3218,6 +3123,7 @@ const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; tota #define JSON_OBJECT_NAME "name" #define JSON_IDENTIFY_ID "identify_id" + #define JSON_LAYERS "layers" #define JSON_SUPPORT_LAYERS "support_layers" #define JSON_TREE_SUPPORT_LAYERS "tree_support_layers" @@ -3254,6 +3160,8 @@ const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; tota #define JSON_LAYER_REGION_PERIMETERS "perimeters" #define JSON_LAYER_REGION_FILLS "fills" + + #define JSON_SURF_TYPE "surface_type" #define JSON_SURF_THICKNESS "thickness" #define JSON_SURF_THICKNESS_LAYER "thickness_layers" @@ -3293,6 +3201,7 @@ const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; tota #define JSON_EXTRUSION_NO_EXTRUSION "no_extrusion" #define JSON_EXTRUSION_LOOP_ROLE "loop_role" + static void to_json(json& j, const Points& p_s) { for (const Point& p : p_s) { @@ -3356,6 +3265,7 @@ static void to_json(json& j, const ArcSegment& arc_seg) { j[JSON_ARC_CENTER] = std::move(center_point_json); } + static void to_json(json& j, const Polyline& poly_line) { json points_json = json::array(), fittings_json = json::array(); points_json = poly_line.points; @@ -3629,6 +3539,7 @@ static void from_json(const json& j, ArcSegment& arc_seg) { return; } + static void from_json(const json& j, Polyline& poly_line) { poly_line.points = j[JSON_POINTS]; @@ -3839,6 +3750,7 @@ static void convert_layer_region_from_json(const json& j, LayerRegion& layer_reg return; } + void extract_layer(const json& layer_json, Layer& layer) { //slice_polygons int slice_polygons_count = layer_json[JSON_LAYER_SLICED_POLYGONS].size(); @@ -4209,6 +4121,7 @@ int Print::export_cached_data(const std::string& directory, bool with_space) return ret; } + int Print::load_cached_data(const std::string& directory) { int ret = 0; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 2a5e8c9553..7261bc2ce6 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -23,7 +23,6 @@ #include #include -#include #include "calib.hpp" @@ -888,34 +887,6 @@ public: std::vector object_extruders() const; std::vector support_material_extruders() const; - - // SM Orca: 设置耗材-挤出机映射 - void set_filament_extruder_map(const std::unordered_map& map) { m_filament_extruder_map = map; } - // SM Orca: 获取耗材-挤出机映射表 - const std::unordered_map& get_filament_extruder_map() const { return m_filament_extruder_map; } - // SM Orca: 获取物理挤出机ID(根据耗材索引) - // 关键修复:当映射表为空时,使用模运算而不是直接返回耗材ID,避免越界 - int get_physical_extruder(int filament_idx) const { - auto it = m_filament_extruder_map.find(filament_idx); - int physical_extruder_id; - if (it != m_filament_extruder_map.end()) { - // 从映射表获取 - physical_extruder_id = it->second; - } else { - // 映射表为空或没有该耗材的映射,使用默认模运算映射 - size_t physical_count = m_config.nozzle_diameter.values.size(); - if (physical_count == 0) { - // 防止除零,使用安全的默认值 - physical_extruder_id = 0; - - } else { - physical_extruder_id = filament_idx % physical_count; - } - } - return physical_extruder_id; - } - // SM Orca: Initialize filament-to-physical-extruder mapping table - void initialize_filament_extruder_map(); std::vector extruders(bool conside_custom_gcode = false) const; double max_allowed_layer_height() const; bool has_support_material() const; @@ -1095,9 +1066,6 @@ private: //SoftFever: calibration Calib_Params m_calib_params; - // SM Orca: 耗材到物理挤出机的映射表 - std::unordered_map m_filament_extruder_map; - // To allow GCode to set the Print's GCodeExport step status. friend class GCode; // Allow PrintObject to access m_mutex and m_cancel_callback. diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 6976a718cf..608af65bee 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -3,7 +3,6 @@ #include #include -#include namespace Slic3r { @@ -217,133 +216,13 @@ static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector& filament_extruder_map) -{ - if (!extruder_defaults->is_vector()) - return nullptr; - - auto* extruder_vec = dynamic_cast(extruder_defaults); - const ConfigOptionVectorBase* override_vec = filament_overrides ? - dynamic_cast(filament_overrides) : nullptr; - - if (!extruder_vec) - return nullptr; - - // Clone the extruder defaults to create the target - auto* target = extruder_defaults->clone(); - auto* target_vec = dynamic_cast(target); - if (!target_vec) { - delete target; - return nullptr; - } - - // Resize target to filament_count - target_vec->resize(filament_count); - - for (size_t filament_idx = 0; filament_idx < filament_count; ++filament_idx) { - bool has_override = false; - if (override_vec && filament_idx < override_vec->size()) { - if (override_vec->nullable()) { - // Nullable type: use override only if not nil (checkbox is checked) - has_override = !override_vec->is_nil(filament_idx); - } else { - // Non-nullable type: check if the value differs from the default (printer config) - // Only if it's different do we consider it a user override - // Get the default value from the mapped physical extruder - auto map_it = filament_extruder_map.find(filament_idx); - int physical_extruder_idx; - if (map_it != filament_extruder_map.end()) { - physical_extruder_idx = map_it->second; - } else { - // Fallback: use modulo to map filament to physical extruder - // This handles edge cases where the map is incomplete or filament_idx is out of range - size_t physical_extruder_count = extruder_vec->size(); - if (physical_extruder_count == 0) { - // Should not happen, but safety check - physical_extruder_idx = 0; - } else { - physical_extruder_idx = (int)filament_idx % (int)physical_extruder_count; - } - } - - if (physical_extruder_idx < extruder_vec->size()) { - // Try different types: double, int, bool - auto* override_dbl = dynamic_cast*>(override_vec); - auto* extruder_dbl = dynamic_cast*>(extruder_vec); - if (override_dbl && extruder_dbl) { - // Compare with the value from the mapped physical extruder - double override_value = override_dbl->get_at(filament_idx); - double default_value = extruder_dbl->get_at(physical_extruder_idx); - // Use override only if value differs from default - has_override = (override_value != default_value); - - } else { - // Try int type - auto* override_int = dynamic_cast*>(override_vec); - auto* extruder_int = dynamic_cast*>(extruder_vec); - if (override_int && extruder_int) { - int override_value = override_int->get_at(filament_idx); - int default_value = extruder_int->get_at(physical_extruder_idx); - has_override = (override_value != default_value); - } else { - // Try bool type - auto* override_bool = dynamic_cast*>(override_vec); - auto* extruder_bool = dynamic_cast*>(extruder_vec); - if (override_bool && extruder_bool) { - unsigned char override_value = override_bool->get_at(filament_idx); - unsigned char default_value = extruder_bool->get_at(physical_extruder_idx); - has_override = (override_value != default_value); - } - } - } - } - } - } - - if (!has_override) { - // No override: inherit from the mapped physical extruder - auto map_it = filament_extruder_map.find(filament_idx); - int physical_extruder_idx; - if (map_it != filament_extruder_map.end()) { - physical_extruder_idx = map_it->second; - } else { - // Fallback: use modulo to map filament to physical extruder - // This handles edge cases where the map is incomplete or filament_idx is out of range - size_t physical_extruder_count = extruder_vec->size(); - if (physical_extruder_count == 0) { - // Should not happen, but safety check - physical_extruder_idx = 0; - } else { - physical_extruder_idx = (int)filament_idx % (int)physical_extruder_count; - } - } - - if (physical_extruder_idx < extruder_vec->size() && filament_idx < target_vec->size()) { - target_vec->set_at(extruder_vec, filament_idx, physical_extruder_idx); - } - } else if (override_vec && filament_idx < override_vec->size()) { - // Has override: use the value from filament config - target_vec->set_at(override_vec, filament_idx, filament_idx); - } - } - - return target; -} - // Collect changes to print config, account for overrides of extruder retract values by filament presets. //BBS: add plate index static t_config_option_keys print_config_diffs( const PrintConfig ¤t_config, const DynamicPrintConfig &new_full_config, DynamicPrintConfig &filament_overrides, - int plate_index, - const std::unordered_map &filament_extruder_map) + int plate_index) { const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); const std::string filament_prefix = "filament_"; @@ -362,81 +241,25 @@ static t_config_option_keys print_config_diffs( // const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; const ConfigOption* opt_new_filament = (iter == extruder_retract_keys.end()) ? nullptr : new_full_config.option(filament_prefix + opt_key); - - bool is_extruder_retract_param = (iter != extruder_retract_keys.end()); - - // 1. This is an extruder retract parameter AND - // 2. Filament overrides exist AND - // 3. Filament-extruder map is not empty (meaning objects are loaded and mapping is initialized) - // When user edits printer config directly (without objects loaded or without filament overrides), - // we should treat it as a regular config change to ensure UI updates work correctly. - bool has_filament_overrides = (opt_new_filament != nullptr && !opt_new_filament->is_nil()); - bool needs_physical_mapping = is_extruder_retract_param && has_filament_overrides && !filament_extruder_map.empty(); - - if (needs_physical_mapping) { - - // This is safe because both opt_old and opt_new should have the same number of physical extruders - bool printer_config_changed = (*opt_old != *opt_new); - - auto* override_vec = dynamic_cast(opt_new_filament); - if (override_vec) { - BOOST_LOG_TRIVIAL(info) << "print_config_diffs: " << opt_key - << " - filament_override size=" << override_vec->size() - << ", nullable=" << override_vec->nullable(); - } - - // since we know has_filament_overrides is true at this point - if ((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut") - && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) - continue; - - // - Check if printer config or filament override changed the effective value - // - Only add to print_diff (not filament_overrides) to avoid array size mismatch - // The actual filament->extruder mapping is applied later during config usage - // - // 关键修复:只要打印机配置变化了,就应该添加到 print_diff - // 这确保了用户在UI中修改打印机配置时,修改能被正确保存 - auto opt_copy = opt_new->clone(); - opt_copy->apply_override(opt_new_filament); - if (printer_config_changed || *opt_old != *opt_copy) { - print_diff.emplace_back(opt_key); - BOOST_LOG_TRIVIAL(info) << "print_config_diffs: " << opt_key - << " - adding to print_diff (printer_changed=" << (printer_config_changed ? "Y" : "N") - << ", effective_changed=" << (*opt_old != *opt_copy ? "Y" : "N") << ")"; - } - delete opt_copy; - } else if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { + if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { // An extruder retract override is available at some of the filament presets. bool overriden = opt_new->overriden_by(opt_new_filament); - - bool printer_config_changed = (*opt_old != *opt_new); - - if (overriden || printer_config_changed) { + if (overriden || *opt_old != *opt_new) { auto opt_copy = opt_new->clone(); if (!((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut") && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament)) // ugly code, remove it later if firmware supports opt_copy->apply_override(opt_new_filament); - bool changed = *opt_old != *opt_copy; if (changed) print_diff.emplace_back(opt_key); - - // If user directly edited printer config, don't override it with filament values - if ((changed || overriden) && !printer_config_changed) { + if (changed || overriden) { if ((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut") && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) continue; // filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config. filament_overrides.set_key_value(opt_key, opt_copy); - } else if (changed && printer_config_changed) { - // Only add to print_diff, not to filament_overrides - // This preserves user's printer config edit - BOOST_LOG_TRIVIAL(info) << "print_config_diffs: " << opt_key - << " - printer config changed, not adding to filament_overrides to preserve user edit"; + } else delete opt_copy; - } else { - delete opt_copy; - } } } else if (*opt_new != *opt_old) { //BBS: add plate_index logic for wipe_tower_x/wipe_tower_y @@ -1271,10 +1094,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // BBS int used_filaments = this->extruders(true).size(); - // 此时 m_objects 和 m_config 是稳定的,可以安全地计算映射 - // 这确保了 print_config_diffs 中的参数继承机制能正常工作 - this->initialize_filament_extruder_map(); - //new_full_config.normalize_fdm(used_filaments); new_full_config.normalize_fdm_1(); t_config_option_keys changed_keys = new_full_config.normalize_fdm_2(objects().size(), used_filaments); @@ -1312,36 +1131,12 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. DynamicPrintConfig filament_overrides; //BBS: add plate index - t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, m_filament_extruder_map); + t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index); t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config, this->m_plate_index); // Collect changes to object and region configs. t_config_option_keys object_diff = m_default_object_config.diff(new_full_config); t_config_option_keys region_diff = m_default_region_config.diff(new_full_config); - // - // 问题根源: - // 1. 回抽参数(如retraction_length)既存在于打印机配置,也可能被耗材覆盖 - // 2. 当耗材-挤出机映射激活时(m_filament_extruder_map不为空), - // filament_overrides中的回抽值会覆盖用户对打印机配置的直接修改 - // - // 修复策略: - // - 当存在耗材-挤出机映射时(说明已加载项目),移除filament_overrides中的所有回抽参数 - // - 这样用户的打印机配置修改就不会被耗材覆盖值覆盖 - // - 保留filament_overrides中其他参数的功能不受影响 - // - // 注意:此修复确保用户对打印机挤出机回抽参数的直接编辑拥有最高优先级 - const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); - bool has_mapping = !m_filament_extruder_map.empty(); - if (has_mapping) { - // 当有映射时,移除所有回抽参数的耗材覆盖,让打印机配置生效 - for (const std::string &key : extruder_retract_keys) { - if (filament_overrides.erase(key)) { - BOOST_LOG_TRIVIAL(info) << "Print::apply - Clearing filament override for '" << key - << "' to allow printer config to take effect (filament-extruder mapping active)"; - } - } - } - // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; auto update_apply_status = [&apply_status](bool invalidated) @@ -1755,10 +1550,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_full_print_config = std::move(new_full_config); } - // 在对象同步后,m_objects 可能已经被重建,需要重新计算映射以反映最新状态 - // 这确保了后续使用映射表时(如 export_gcode)能获得正确的映射关系 - this->initialize_filament_extruder_map(); - // All regions now have distinct settings. // Check whether applying the new region config defaults we would get different regions, // update regions or create regions from scratch. diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 0292e4524c..5b6b619e86 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -136,12 +136,12 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt } case coPercents:{ ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, 0); // SM Orca: Fix - use src_idx=0 for single-element vectors + config.option(opt_key)->set_at(vec_new, opt_index, opt_index); break; } case coFloats:{ ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, 0); // SM Orca: Fix - use src_idx=0 for single-element vectors + config.option(opt_key)->set_at(vec_new, opt_index, opt_index); break; } case coString: diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 76f021619a..ba6ff14f37 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -645,10 +645,6 @@ void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const b const std::string &opt_key = itOption.first; int opt_index = itOption.second; - // SM Orca: Debug logging - track parameter changes from UI - BOOST_LOG_TRIVIAL(error) << "ConfigOptionsGroup::on_change_OG: opt_id=" << opt_id - << ", opt_key=" << opt_key << ", opt_index=" << opt_index; - this->change_opt_value(opt_key, value, opt_index == -1 ? 0 : opt_index); } @@ -1231,12 +1227,18 @@ void ExtruderOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const auto itOption = it->second; const std::string& opt_key = itOption.first; - int opt_index = itOption.second; - // SM Orca: FIX - Only modify the specific extruder's value, not all extruders - // The original code iterated through all indices and set them to the same value, - // which caused all extruders to have identical values when editing one extruder - this->change_opt_value(opt_key, value, opt_index == -1 ? 0 : opt_index); + auto opt = m_config->option(opt_key); + const ConfigOptionVectorBase* opt_vec = dynamic_cast(opt); + if (opt_vec != nullptr) { + for (int opt_index = 0; opt_index < opt_vec->size(); opt_index++) { + this->change_opt_value(opt_key, value, opt_index); + } + } + else { + int opt_index = itOption.second; + this->change_opt_value(opt_key, value, opt_index == -1 ? 0 : opt_index); + } } OptionsGroup::on_change_OG(opt_id, value); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index de4e163ff7..4fd9e4ce45 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3286,24 +3286,7 @@ void TabFilament::add_filament_overrides_page() else { const std::string printer_opt_key = opt_key.substr(strlen("filament_")); const auto printer_config = m_preset_bundle->printers.get_edited_preset().config; - // SM Orca: Map filament slot to physical extruder index for inheritance - auto& filament_extruder_map = wxGetApp().app_config->get_filament_extruder_map_ref(); - // SM Orca: First calculate num_extruders to use modulo for default mapping - const ConfigOptionFloats* nozzle_diameter = printer_config.option("nozzle_diameter"); - int num_extruders = nozzle_diameter ? (int)nozzle_diameter->values.size() : 1; - // SM Orca: Use modulo arithmetic for default mapping when no explicit mapping exists - int physical_extruder_idx = opt_index % num_extruders; // default: filament N maps to extruder N % num_extruders - auto map_it = filament_extruder_map.find(opt_index); - if (map_it != filament_extruder_map.end()) { - physical_extruder_idx = map_it->second; - } - // SM Orca: Bounds check to prevent crash from misconfigured map - if (physical_extruder_idx < 0 || physical_extruder_idx >= num_extruders) { - BOOST_LOG_TRIVIAL(warning) << "Invalid physical_extruder_idx " << physical_extruder_idx - << " for filament slot " << opt_index << ", using default"; - physical_extruder_idx = std::clamp(physical_extruder_idx, 0, num_extruders - 1); - } - const boost::any printer_config_value = optgroup_sh->get_config_value(printer_config, printer_opt_key, physical_extruder_idx); + const boost::any printer_config_value = optgroup_sh->get_config_value(printer_config, printer_opt_key, opt_index); field->update_na_value(printer_config_value); field->set_na_value(); } @@ -3318,7 +3301,7 @@ void TabFilament::add_filament_overrides_page() optgroup->append_line(line); }; - const int extruder_idx = (m_presets_choice && m_presets_choice->get_filament_idx() >= 0) ? m_presets_choice->get_filament_idx() : 0; // SM Orca: Get actual filament slot index + const int extruder_idx = 0; // #ys_FIXME for (const std::string opt_key : { "filament_retraction_length", "filament_z_hop", @@ -3384,7 +3367,7 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print // "filament_seam_gap" }; - const int extruder_idx = (m_presets_choice && m_presets_choice->get_filament_idx() >= 0) ? m_presets_choice->get_filament_idx() : 0; // SM Orca: Get actual filament slot index + const int extruder_idx = 0; // #ys_FIXME const bool have_retract_length = m_config->option("filament_retraction_length")->is_nil() || m_config->opt_float("filament_retraction_length", extruder_idx) > 0; @@ -3416,26 +3399,7 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print } else { if (!is_checked) { const std::string printer_opt_key = opt_key.substr(strlen("filament_")); - // SM Orca: Map filament slot to physical extruder index for inheritance - auto& filament_extruder_map = wxGetApp().app_config->get_filament_extruder_map_ref(); - // SM Orca: Determine extruder count first for proper modulo calculation - const ConfigOptionFloats* nozzle_diameter = printers_config->option("nozzle_diameter"); - int num_extruders = nozzle_diameter ? (int)nozzle_diameter->values.size() : 1; - int physical_extruder_idx = extruder_idx; // default: filament N uses extruder N - auto map_it = filament_extruder_map.find(extruder_idx); - if (map_it != filament_extruder_map.end()) { - physical_extruder_idx = map_it->second; - } else { - // SM Orca: Use modulo arithmetic when map entry doesn't exist - physical_extruder_idx = extruder_idx % num_extruders; - } - // SM Orca: Bounds check to prevent crash from misconfigured map - if (physical_extruder_idx < 0 || physical_extruder_idx >= num_extruders) { - BOOST_LOG_TRIVIAL(warning) << "Invalid physical_extruder_idx " << physical_extruder_idx - << " for filament slot " << extruder_idx << ", using default"; - physical_extruder_idx = std::clamp(physical_extruder_idx, 0, num_extruders - 1); - } - boost::any printer_config_value = optgroup->get_config_value(*printers_config, printer_opt_key, physical_extruder_idx); + boost::any printer_config_value = optgroup->get_config_value(*printers_config, printer_opt_key, extruder_idx); field->update_na_value(printer_config_value); field->set_value(printer_config_value, false); } diff --git a/参数访问修复表.md b/参数访问修复表.md deleted file mode 100644 index ee0e273b38..0000000000 --- a/参数访问修复表.md +++ /dev/null @@ -1,200 +0,0 @@ -# 参数访问修复表 - -## 修复概述 - -**问题**: 在8耗材4挤出机的配置下,使用耗材ID(0-7)访问只有4个元素的物理挤出机参数数组,导致数组越界。 - -**解决**: 通过 `get_physical_extruder(filament_id)` 获取正确的物理挤出机ID进行访问。 - -**修复统计**: -- 修改文件: 9个 -- 修改代码行: 约40处 -- 修复参数: 22个 - ---- - -## GCode.cpp 中的修复 - -| 代码位置 | 参数名 | 原来索引 | 现在索引 | 有无改动 | 改动原因 | -|---------|-------|---------|---------|---------|---------| -| 1051-1054 | EXTRUDER_CONFIG宏定义 | `m_writer.extruder()->id()` (filament_id) | `m_writer.extruder()->id()` (filament_id) | ❌ 无 | 用于耗材参数,保持不变 | -| 1053-1054 | **PHYSICAL_EXTRUDER_CONFIG宏定义** | - | `m_writer.get_physical_extruder(m_writer.extruder()->id())` | ✅ **新增** | **新增宏用于访问物理挤出机参数** | -| 2857-2858 | nozzle_diameter (spiral_vase) | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | nozzle_diameter是物理挤出机参数,只有4个元素,filament 5-7会越界 | -| 2958-2959 | nozzle_diameter (spiral_vase) | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 同上 | -| 4771-4772 | nozzle_diameter (seam_slope) | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 同上 | -| 4995-4996 | nozzle_diameter (hide_seam) | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 同上 | -| 5589-5590 | enable_pressure_advance | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5589-5590 | adaptive_pressure_advance | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5785-5789 | enable_pressure_advance | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5785-5789 | adaptive_pressure_advance | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5785-5789 | adaptive_pressure_advance_overhangs | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5995-5999 | enable_pressure_advance | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5995-5999 | adaptive_pressure_advance | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5995-5999 | adaptive_pressure_advance_overhangs | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5702-5703 | overhang_fan_threshold | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5702-5703 | enable_overhang_bridge_fan | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5769-5771 | support_material_interface_fan_speed | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5769-5771 | ironing_fan_speed | `extruder()->id()` (filament_id) | `get_physical_extruder(extruder()->id())` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 6510-6515 | enable_pressure_advance (set_extruder) | `extruder_id` (filament_id) | `physical_extruder_id` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 6510-6515 | pressure_advance (set_extruder) | `extruder_id` (filament_id) | `physical_extruder_id` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 6760-6763 | enable_pressure_advance (set_extruder) | `extruder_id` (filament_id) | `physical_extruder_id` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 6760-6763 | pressure_advance (set_extruder) | `extruder_id` (filament_id) | `physical_extruder_id` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 5437, 5475, 5491, 5517, 5519 | filament_max_volumetric_speed | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 耗材参数(名称带filament_),使用耗材索引正确 | -| 4685-4688 | retract_when_changing_layer | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 有耗材覆盖(filament_retract_when_changing_layer) | -| 4685-4688 | z_hop_types | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 有耗材覆盖(filament_z_hop_types) | -| 6286 | retraction_minimum_travel | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 有耗材覆盖 | -| 6365-6369 | z_hop_types | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 有耗材覆盖 | -| 6399-6403 | z_hop_types | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 有耗材覆盖 | -| 6416 | wipe | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 有耗材覆盖 | -| 6416 | wipe_distance | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 有耗材覆盖 | -| 6440 | retract_lift_enforce | `extruder()->id()` (filament_id) | `extruder()->id()` (filament_id) | ❌ 无 | 有耗材覆盖 | - ---- - -## CoolingBuffer.cpp 中的修复 - -| 代码位置 | 参数名 | 原来索引 | 现在索引 | 有无改动 | 改动原因 | -|---------|-------|---------|---------|---------|---------| -| 734-735 | EXTRUDER_CONFIG宏定义 | `m_current_extruder` (filament_id) | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | **所有风扇参数都是物理挤出机参数** | -| 736 | fan_min_speed | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 737 | reduce_fan_stop_start_freq | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 739 | additional_cooling_fan_speed | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 740 | close_fan_the_first_x_layers | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 742 | full_fan_speed_layer | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 743 | support_material_interface_fan_speed | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 751 | fan_max_speed | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 752 | slow_down_layer_time | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 753 | fan_cooling_layer_time | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 766 | overhang_fan_speed | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 773 | support_material_interface_fan_speed | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 779 | internal_bridge_fan_speed | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 788 | ironing_fan_speed | `m_current_extruder` | `get_physical_extruder(m_current_extruder)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 344-350 | slow_down_for_layer_cooling | `extruder_id` | `get_physical_extruder(extruder_id)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 347 | slow_down_layer_time | `extruder_id` | `get_physical_extruder(extruder_id)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 348 | slow_down_min_speed | `extruder_id` | `get_physical_extruder(extruder_id)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | -| 350 | dont_slow_down_outer_wall | `extruder_id` | `get_physical_extruder(extruder_id)` | ✅ **已改** | 无耗材覆盖,物理挤出机参数 | - ---- - -## AvoidCrossingPerimeters.cpp 中的修复 - -| 代码位置 | 参数名 | 原来索引 | 现在索引 | 有无改动 | 改动原因 | -|---------|-------|---------|---------|---------|---------| -| 486-488 | nozzle_diameter | `extruder_id` (filament_id) | `get_physical_extruder(extruder_id)` | ✅ **已改** | nozzle_diameter是物理挤出机参数,只有4个元素 | - ---- - -## Extruder.cpp 中的修复(之前已完成) - -| 代码位置 | 参数名 | 原来索引 | 现在索引 | 有无改动 | 改动原因 | -|---------|-------|---------|---------|---------|---------| -| 163 | retract_before_wipe | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖,继承后存储在耗材位置 | -| 168 | retraction_length | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖 | -| 173 | z_hop | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖 | -| 178 | retraction_speed | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖 | -| 188 | deretraction_speed | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖 | -| 194 | retract_restart_extra | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖 | -| 199 | retract_length_toolchange | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖 | -| 204 | retract_restart_extra_toolchange | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖 | -| 209 | travel_slope | `m_physical_extruder_id` | `m_id` | ✅ **已改** | 有耗材覆盖 | - ---- - -## GCode.cpp 中未改动(正确的耗材参数) - -| 代码位置 | 参数名 | 使用索引 | 是否改动 | 原因 | -|---------|-------|---------|---------|------| -| 262, 274 | filament_idle_temp | `extruder_id` | ❌ 无 | 耗材参数(名称带filament_) | -| 333 | wipe_distance | `extruder_id` | ❌ 无 | 有耗材覆盖 | -| 339 | retraction_speed | `extruder_id` | ❌ 无 | 有耗材覆盖 | -| 2731 | filament_end_gcode | `extruder_id` | ❌ 无 | 耗材参数 | -| 3979, 4044, 4074 | filament_soluble | `extruder_id` | ❌ 无 | 耗材参数 | -| 6494 | filament_start_gcode | `extruder_id` | ❌ 无 | 耗材参数 | -| 6503 | retraction_distances_when_cut | `extruder_id` | ❌ 无 | 有耗材覆盖 | -| 6504 | long_retractions_when_cut | `extruder_id` | ❌ 无 | 有耗材覆盖 | -| 6581 | retraction_length | `extruder_id` | ❌ 无 | 有耗材覆盖 | -| 6582 | retract_length_toolchange | `extruder_id` | ❌ 无 | 有耗材覆盖 | -| 6584, 6587, 6734-6735 | nozzle_temperature | `extruder_id` | ❌ 无 | 耗材参数(在耗材配置文件中定义) | -| 6593 | filament_diameter | `extruder_id` | ❌ 无 | 耗材参数 | -| 6632 | filament_max_volumetric_speed | `extruder_id` | ❌ 无 | 耗材参数 | -| 6741-6742 | retraction_distances_when_cut | `extruder_id` | ❌ 无 | 有耗材覆盖 | - ---- - -## 修复的物理挤出机参数列表(共22个) - -| 参数 | 说明 | 数组大小 | 修复文件 | -|------|------|----------|----------| -| nozzle_diameter | 喷嘴直径 | physical_extruder_count (4) | GCode.cpp, AvoidCrossingPerimeters.cpp | -| enable_pressure_advance | 启用压力提前 | physical_extruder_count (4) | GCode.cpp | -| adaptive_pressure_advance | 自适应压力提前 | physical_extruder_count (4) | GCode.cpp | -| adaptive_pressure_advance_overhangs | 悬空自适应PA | physical_extruder_count (4) | GCode.cpp | -| pressure_advance | 压力提前值 | physical_extruder_count (4) | GCode.cpp | -| overhang_fan_threshold | 悬空风扇阈值 | physical_extruder_count (4) | GCode.cpp | -| enable_overhang_bridge_fan | 悬空桥风扇开关 | physical_extruder_count (4) | GCode.cpp | -| overhang_fan_speed | 悬空风扇速度 | physical_extruder_count (4) | CoolingBuffer.cpp | -| support_material_interface_fan_speed | 支持界面风扇速度 | physical_extruder_count (4) | GCode.cpp, CoolingBuffer.cpp | -| ironing_fan_speed | 熨烫风扇速度 | physical_extruder_count (4) | GCode.cpp, CoolingBuffer.cpp | -| internal_bridge_fan_speed | 内部桥风扇速度 | physical_extruder_count (4) | CoolingBuffer.cpp | -| fan_min_speed | 最小风扇速度 | physical_extruder_count (4) | CoolingBuffer.cpp | -| fan_max_speed | 最大风扇速度 | physical_extruder_count (4) | CoolingBuffer.cpp | -| slow_down_layer_time | 减速层时间 | physical_extruder_count (4) | CoolingBuffer.cpp | -| slow_down_for_layer_cooling | 层冷却减速开关 | physical_extruder_count (4) | CoolingBuffer.cpp | -| slow_down_min_speed | 最小减速速度 | physical_extruder_count (4) | CoolingBuffer.cpp | -| fan_cooling_layer_time | 风扇冷却层时间 | physical_extruder_count (4) | CoolingBuffer.cpp | -| close_fan_the_first_x_layers | 前N层关闭风扇 | physical_extruder_count (4) | CoolingBuffer.cpp | -| full_fan_speed_layer | 全速风扇层 | physical_extruder_count (4) | CoolingBuffer.cpp | -| additional_cooling_fan_speed | 额外冷却风扇速度 | physical_extruder_count (4) | CoolingBuffer.cpp | -| reduce_fan_stop_start_frequency | 减少风扇启停频率 | physical_extruder_count (4) | CoolingBuffer.cpp | -| dont_slow_down_outer_wall | 外墙不减速 | physical_extruder_count (4) | CoolingBuffer.cpp | - ---- - -## 修复统计汇总 - -| 类别 | 数量 | -|------|------| -| **需要改动的参数** | **22个** | -| **修改的代码行数** | **约40处** | -| **新增的宏** | **1个 (PHYSICAL_EXTRUDER_CONFIG)** | -| **新增的方法** | **2个 (CoolingBuffer)** | - ---- - -## 核心问题与解决方案 - -### 问题根源 -``` -8耗材 (索引0-7) → 访问4元素数组 → 索引5-7时数组越界 ❌ -``` - -### 解决方案 -``` -耗材ID → 映射表 → 物理挤出机ID (0-3) → 访问4元素数组 ✅ -``` - -### 关键改动 -1. **新增 `PHYSICAL_EXTRUDER_CONFIG` 宏**:专门用于访问物理挤出机参数 - ```cpp - #define PHYSICAL_EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.get_physical_extruder(m_writer.extruder()->id())) - ``` - -2. **CoolingBuffer 添加映射表和方法**: - - `m_filament_extruder_map`:存储耗材到物理挤出机的映射 - - `get_physical_extruder(filament_idx)`:获取物理挤出机ID - - `set_filament_extruder_map(map)`:设置映射表 - -3. **所有物理挤出机参数改用 `physical_extruder_id`**: - - 原来使用 `filament_id` (0-7) - - 现在使用 `physical_extruder_id` (0-3) - - 确保不会数组越界 - ---- - -## 验证检查点 - -- ✅ GCode.cpp 中的 PHYSICAL_EXTRUDER_CONFIG 宏正确使用 -- ✅ CoolingBuffer 中的映射表正确设置 -- ✅ 所有物理挤出机参数使用 physical_extruder_id 访问 -- ✅ 所有耗材参数使用 filament_id 访问 -- ✅ 大于4号的耗材能够正确切片,不再越界 diff --git a/耗材继承修复总结.md b/耗材继承修复总结.md deleted file mode 100644 index ce85da6e13..0000000000 --- a/耗材继承修复总结.md +++ /dev/null @@ -1,127 +0,0 @@ -# 耗材继承修复总结 - -**状态**: ✅ 已完成并验证工作 - -## 问题描述 - -### 问题1: 打印机配置参数无法修改 -修改挤出机2的回抽长度从1.5到1.6后,值会闪回1.5 - -### 问题2: 所有耗材都从挤出机1继承参数 -期望: -- 耗材1 → 继承挤出机1 -- 耗材2 → 继承挤出机2 -- 耗材3 → 继承挤出机3 -- 耗材4 → 继承挤出机4 -- 耗材5 → 继承挤出机1(通过 filament_extruder_map 映射) -- 耗材6 → 继承挤出机2(通过 filament_extruder_map 映射) - -实际:所有耗材都继承挤出机1的参数 - -## 修复方案 - -### 修复1: GUI.cpp (打印机配置修改) - -**文件**: `src/slic3r/GUI/GUI.cpp` -**位置**: 第139行、第144行 - -```cpp -// 之前(错误): -config.option(opt_key)->set_at(vec_new, opt_index, opt_index); -config.option(opt_key)->set_at(vec_new, opt_index, opt_index); - -// 之后(正确): -config.option(opt_key)->set_at(vec_new, opt_index, 0); // SM Orca: Fix -config.option(opt_key)->set_at(vec_new, opt_index, 0); // SM Orca: Fix -``` - -**原因**: 用户输入单个值时创建的是1元素向量 `{value}`,原代码试图访问 `vec_new[opt_index]` 会越界 - -### 修复2: Tab.cpp (耗材继承) - -**文件**: `src/slic3r/GUI/Tab.cpp` - -#### 更改2.1: 获取实际耗材槽索引 -**位置**: 第3317行、第3383行 - -```cpp -// 之前(硬编码): -const int extruder_idx = 0; // #ys_FIXME - -// 之后(动态获取): -const int extruder_idx = (m_presets_choice && m_presets_choice->get_filament_idx() >= 0) - ? m_presets_choice->get_filament_idx() : 0; -``` - -#### 更改2.2: 添加耗材→挤出机映射逻辑 -**位置**: 第3289-3303行、第3418-3435行 - -```cpp -// SM Orca: Map filament slot to physical extruder index for inheritance -auto& filament_extruder_map = wxGetApp().app_config->get_filament_extruder_map_ref(); -int physical_extruder_idx = opt_index; // default: filament N uses extruder N -auto map_it = filament_extruder_map.find(opt_index); -if (map_it != filament_extruder_map.end()) { - physical_extruder_idx = map_it->second; -} - -// SM Orca: Bounds check to prevent crash from misconfigured map -const ConfigOptionFloats* nozzle_diameter = printer_config.option("nozzle_diameter"); -int num_extruders = nozzle_diameter ? (int)nozzle_diameter->values.size() : 1; -if (physical_extruder_idx < 0 || physical_extruder_idx >= num_extruders) { - BOOST_LOG_TRIVIAL(warning) << "Invalid physical_extruder_idx " << physical_extruder_idx - << " for filament slot " << opt_index << ", using default"; - physical_extruder_idx = std::clamp(physical_extruder_idx, 0, num_extruders - 1); -} - -const boost::any printer_config_value = optgroup_sh->get_config_value(printer_config, printer_opt_key, physical_extruder_idx); -``` - -## 架构验证 - -由 Architect (Opus) 验证: -- ✅ 架构正确 -- ✅ 使用现有 filament_extruder_map 机制(只读,不修改) -- ✅ 逻辑流程正确 -- ✅ 边界检查已添加 - -## 风险审视 - -### 低风险 -1. **只读映射**: 代码只读取 `filament_extruder_map`,不修改映射机制本身 -2. **边界检查**: 添加了边界检查,防止配置错误导致崩溃 -3. **默认回退**: 如果映射不存在或索引无效,回退到 1:1 映射 - -### 需要注意的点 -1. **filament_extruder_map 同步**: - - GUI 层使用 `AppConfig::filament_extruder_map` - - Print 层使用 `Print::m_filament_extruder_map` - - 需确保这两个映射保持同步 - -2. **多线程访问**: - - `get_filament_extruder_map_ref()` 返回非const引用 - - 如果GUI和Print线程同时访问可能存在竞态 - - 当前 AppConfig 似乎是主线程专用 - -## 保存当前状态 - -创建备份命令: -```bash -# 保存当前更改到stash -git stash save "耗材继承修复 - 工作版本" - -# 或创建补丁文件 -git diff src/slic3r/GUI/Tab.cpp > filament_inheritance_fix.patch -git diff src/slic3r/GUI/GUI.cpp > printer_config_fix.patch -``` - -## 修改的文件列表 - -1. `src/slic3r/GUI/GUI.cpp` - 打印机配置修改修复 -2. `src/slic3r/GUI/Tab.cpp` - 耗材继承修复(4处更改) -3. `src/libslic3r/Config.hpp` - 添加了 set_at 重载方法 -4. `src/libslic3r/PrintApply.cpp` - 相关调整 -5. 其他GCode相关文件的适配性修改 - -**总计**: 17个文件,479行新增,135行删除 -