mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-12 15:03:33 +00:00
* Add docs about time_estimate * Fix: Problems with graceful program exit caused by Flutter refactoring * Add: sw_OpenBrowser() & sw_OpenOrcaWebview * Fix: NetworkTestDialog Crash & Add: Lan Device test \ cloud test
846 lines
25 KiB
Markdown
846 lines
25 KiB
Markdown
# M109/M190温度等待时间计算修复方案(方案B:智能预热)
|
||
|
||
> **文档版本**: v1.0
|
||
> **创建日期**: 2025-12-06
|
||
> **目标机型**: U1多喷头打印机(也适用于其他多喷头机型)
|
||
> **优先级**: 高
|
||
> **预计影响**: 多喷头打印时间估算精度从±30-50%提升到±5-15%
|
||
|
||
---
|
||
|
||
## 一、问题背景
|
||
|
||
### 1.1 核心问题描述
|
||
|
||
**问题1**: M109(喷头加热等待)没有计算等待时间
|
||
- **位置**: `src/libslic3r/GCode/GCodeProcessor.cpp:3640-3655`
|
||
- **现象**: 只更新温度值,不调用`simulate_st_synchronize()`添加等待时间
|
||
- **影响**: 实际等待30-80秒,时间估算为0秒
|
||
|
||
**问题2**: M190(热床加热等待)没有计算等待时间
|
||
- **位置**: `src/libslic3r/GCode/GCodeProcessor.cpp:3696-3701`
|
||
- **现象**: 只更新温度值,不调用`simulate_st_synchronize()`添加等待时间
|
||
- **影响**: 实际等待60-180秒,时间估算为0秒
|
||
|
||
**问题3**: 预热逻辑未与M109等待时间关联
|
||
- **位置**: `src/libslic3r/GCode/GCodeProcessor.cpp:4620-4648`
|
||
- **现象**: M104预热是异步的,M109等待时间没有考虑预热效果
|
||
- **影响**: 对U1多头机型,时间估算会严重高估50-100%
|
||
|
||
### 1.2 U1机型的特殊性
|
||
|
||
U1是多喷头打印机,具有以下特点:
|
||
1. **工具切换频繁**: 多色打印时可能有50+次工具切换
|
||
2. **依赖预热逻辑**: 每次切换前20-30秒就开始预热新工具
|
||
3. **预热效果显著**: 到达M109时,温度通常已接近目标值
|
||
4. **不考虑预热的后果**: 每次切换错误地添加30-40秒等待时间,50次切换会多估算25-33分钟
|
||
|
||
### 1.3 为什么必须实现方案B
|
||
|
||
**简单方案(方案A)的问题**:
|
||
- 不考虑预热,直接计算温差÷加热速率
|
||
- 对单喷头打印机可能可以接受(略微高估)
|
||
- 对U1多头机型**完全不可接受**:
|
||
- 每次工具切换都有预热
|
||
- 预热成功率接近100%(因为preheat_time设置合理)
|
||
- 错误地为每次切换添加30-40秒等待
|
||
- 累积误差巨大
|
||
|
||
**方案B的优势**:
|
||
- 追踪M104预热状态和时间
|
||
- M109时计算已预热时间,扣除已加热的温度
|
||
- 准确反映实际等待时间
|
||
- 适配U1的预热逻辑
|
||
|
||
---
|
||
|
||
## 二、方案B详细设计
|
||
|
||
### 2.1 核心思路
|
||
|
||
```
|
||
时间轴示例(工具切换场景):
|
||
|
||
t=100s: 打印中(使用T0)
|
||
t=110s: 预热逻辑插入 M104 T1 S220(开始预热T1)⬅️ 记录预热开始时间
|
||
t=110s~130s: 继续使用T0打印,T1在后台加热
|
||
t=130s: 工具切换,执行 M109 T1 S220 ⬅️ 计算等待时间
|
||
- 当前时间:130s
|
||
- 预热开始时间:110s
|
||
- 已预热时间:20s
|
||
- 加热速率:2.5°C/s
|
||
- 已加热温度:20s × 2.5°C/s = 50°C
|
||
- 温度差:220°C - 170°C = 50°C
|
||
- 剩余需加热:50°C - 50°C = 0°C
|
||
- 等待时间:0s ✅ 准确!
|
||
|
||
对比方案A(不考虑预热):
|
||
- 温度差:220°C - 170°C = 50°C
|
||
- 等待时间:50°C ÷ 2.5°C/s = 20s ❌ 错误!
|
||
```
|
||
|
||
### 2.2 数据结构设计
|
||
|
||
#### 预热状态结构体
|
||
|
||
```cpp
|
||
// 在 GCodeProcessor.hpp 中定义
|
||
struct PreheatingState {
|
||
bool is_preheating = false; // 是否正在预热
|
||
float preheat_start_time = 0.0f; // 预热开始时的估算时间戳
|
||
float preheat_target_temp = 0.0f; // 预热目标温度
|
||
float preheat_start_temp = 0.0f; // 预热开始时的温度(备用)
|
||
};
|
||
|
||
struct BedPreheatingState {
|
||
bool is_preheating = false;
|
||
float preheat_start_time = 0.0f;
|
||
float preheat_target_temp = 0.0f;
|
||
float preheat_start_temp = 0.0f;
|
||
};
|
||
```
|
||
|
||
#### GCodeProcessor新增成员变量
|
||
|
||
```cpp
|
||
class GCodeProcessor
|
||
{
|
||
private:
|
||
// ... 现有成员 ...
|
||
|
||
// 加热速率配置(从PrintConfig加载)
|
||
float m_extruder_heating_rate; // 喷头加热速率(°C/s),默认2.5
|
||
float m_bed_heating_rate; // 热床加热速率(°C/s),默认0.75
|
||
|
||
// 预热状态追踪(关键数据结构)
|
||
std::vector<PreheatingState> m_extruder_preheat_states; // 每个喷头的预热状态
|
||
BedPreheatingState m_bed_preheat_state; // 热床预热状态
|
||
};
|
||
```
|
||
|
||
### 2.3 配置参数设计
|
||
|
||
#### PrintConfig新增参数
|
||
|
||
**文件**: `src/libslic3r/PrintConfig.cpp`
|
||
|
||
```cpp
|
||
def = this->add("extruder_heating_rate", coFloat);
|
||
def->label = L("喷头加热速率");
|
||
def->tooltip = L("喷头的加热速率,单位°C/秒。用于打印时间预估。\n"
|
||
"不同打印机的加热速率差异较大,建议根据实际测试调整。\n"
|
||
"测试方法:从室温加热到目标温度,记录时间,计算速率。\n"
|
||
"典型值:2-4°C/s");
|
||
def->sidetext = L("°C/s");
|
||
def->min = 0.5;
|
||
def->max = 10.0;
|
||
def->mode = comAdvanced; // 高级选项
|
||
def->set_default_value(new ConfigOptionFloat(2.5));
|
||
|
||
def = this->add("bed_heating_rate", coFloat);
|
||
def->label = L("热床加热速率");
|
||
def->tooltip = L("热床的加热速率,单位°C/秒。用于打印时间预估。\n"
|
||
"热床加热速度通常比喷头慢。\n"
|
||
"测试方法:从室温加热到目标温度,记录时间,计算速率。\n"
|
||
"典型值:0.5-1.5°C/s");
|
||
def->sidetext = L("°C/s");
|
||
def->min = 0.1;
|
||
def->max = 3.0;
|
||
def->mode = comAdvanced;
|
||
def->set_default_value(new ConfigOptionFloat(0.75));
|
||
```
|
||
|
||
**文件**: `src/libslic3r/PrintConfig.hpp`
|
||
|
||
```cpp
|
||
// 在 MachineEnvelopeConfig 或 PrintConfig 类中添加
|
||
((ConfigOptionFloat, extruder_heating_rate))
|
||
((ConfigOptionFloat, bed_heating_rate))
|
||
```
|
||
|
||
---
|
||
|
||
## 三、核心函数实现
|
||
|
||
### 3.1 配置加载 - apply_config()
|
||
|
||
**文件**: `src/libslic3r/GCode/GCodeProcessor.cpp`
|
||
|
||
```cpp
|
||
void GCodeProcessor::apply_config(const PrintConfig& config)
|
||
{
|
||
// ... 现有代码 ...
|
||
|
||
// 加载加热速率配置
|
||
m_extruder_heating_rate = config.extruder_heating_rate;
|
||
m_bed_heating_rate = config.bed_heating_rate;
|
||
|
||
// 确保速率在合理范围内(防御性编程)
|
||
m_extruder_heating_rate = std::clamp(m_extruder_heating_rate, 0.5f, 10.0f);
|
||
m_bed_heating_rate = std::clamp(m_bed_heating_rate, 0.1f, 3.0f);
|
||
}
|
||
```
|
||
|
||
### 3.2 初始化 - reset()
|
||
|
||
**文件**: `src/libslic3r/GCode/GCodeProcessor.cpp`
|
||
|
||
```cpp
|
||
void GCodeProcessor::reset()
|
||
{
|
||
// ... 现有代码 ...
|
||
|
||
// 初始化预热状态数组(根据喷头数量)
|
||
m_extruder_preheat_states.clear();
|
||
m_extruder_preheat_states.resize(m_extruder_temps.size());
|
||
for (auto& state : m_extruder_preheat_states) {
|
||
state.is_preheating = false;
|
||
state.preheat_start_time = 0.0f;
|
||
state.preheat_target_temp = 0.0f;
|
||
state.preheat_start_temp = 0.0f;
|
||
}
|
||
|
||
// 初始化热床预热状态
|
||
m_bed_preheat_state.is_preheating = false;
|
||
m_bed_preheat_state.preheat_start_time = 0.0f;
|
||
m_bed_preheat_state.preheat_target_temp = 0.0f;
|
||
m_bed_preheat_state.preheat_start_temp = 0.0f;
|
||
}
|
||
```
|
||
|
||
### 3.3 M104处理 - process_M104()(记录预热开始)
|
||
|
||
**文件**: `src/libslic3r/GCode/GCodeProcessor.cpp`
|
||
|
||
**原始代码**:
|
||
```cpp
|
||
void GCodeProcessor::process_M104(const GCodeReader::GCodeLine& line)
|
||
{
|
||
float new_temp;
|
||
if (line.has_value('S', new_temp)) {
|
||
m_extruder_temps[m_extruder_id] = new_temp;
|
||
}
|
||
}
|
||
```
|
||
|
||
**修改后代码**:
|
||
```cpp
|
||
void GCodeProcessor::process_M104(const GCodeReader::GCodeLine& line)
|
||
{
|
||
float new_temp;
|
||
if (line.has_value('S', new_temp)) {
|
||
size_t target_extruder = m_extruder_id;
|
||
|
||
// 处理T参数(指定喷头)
|
||
float val;
|
||
if (line.has_value('T', val)) {
|
||
target_extruder = static_cast<size_t>(val);
|
||
}
|
||
|
||
// 更新温度
|
||
if (target_extruder < m_extruder_temps.size()) {
|
||
m_extruder_temps[target_extruder] = new_temp;
|
||
}
|
||
|
||
// 🔥 关键:记录预热状态
|
||
if (target_extruder < m_extruder_preheat_states.size()) {
|
||
m_extruder_preheat_states[target_extruder].is_preheating = true;
|
||
m_extruder_preheat_states[target_extruder].preheat_start_time =
|
||
get_time(PrintEstimatedStatistics::ETimeMode::Normal);
|
||
m_extruder_preheat_states[target_extruder].preheat_target_temp = new_temp;
|
||
m_extruder_preheat_states[target_extruder].preheat_start_temp =
|
||
m_extruder_temps[target_extruder];
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点说明**:
|
||
1. `get_time()`返回当前的估算时间(不是实际时钟时间)
|
||
2. 记录目标温度和当前温度(当前温度可用于更精确的计算)
|
||
3. 支持T参数指定喷头
|
||
|
||
### 3.4 M109处理 - process_M109()(考虑预热效果)
|
||
|
||
**文件**: `src/libslic3r/GCode/GCodeProcessor.cpp`
|
||
|
||
**原始代码**:
|
||
```cpp
|
||
void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line)
|
||
{
|
||
float new_temp;
|
||
if (line.has_value('R', new_temp)) {
|
||
float val;
|
||
if (line.has_value('T', val)) {
|
||
size_t eid = static_cast<size_t>(val);
|
||
if (eid < m_extruder_temps.size())
|
||
m_extruder_temps[eid] = new_temp;
|
||
}
|
||
else
|
||
m_extruder_temps[m_extruder_id] = new_temp;
|
||
}
|
||
else if (line.has_value('S', new_temp))
|
||
m_extruder_temps[m_extruder_id] = new_temp;
|
||
// ❌ 没有添加等待时间
|
||
}
|
||
```
|
||
|
||
**修改后代码**:
|
||
```cpp
|
||
void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line)
|
||
{
|
||
float new_temp;
|
||
float target_temp = 0;
|
||
size_t target_extruder = m_extruder_id;
|
||
bool use_r_param = false; // R参数可以等待降温
|
||
|
||
// 解析参数
|
||
if (line.has_value('R', new_temp)) {
|
||
use_r_param = true;
|
||
target_temp = new_temp;
|
||
float val;
|
||
if (line.has_value('T', val)) {
|
||
target_extruder = static_cast<size_t>(val);
|
||
if (target_extruder < m_extruder_temps.size())
|
||
m_extruder_temps[target_extruder] = new_temp;
|
||
}
|
||
else
|
||
m_extruder_temps[m_extruder_id] = new_temp;
|
||
}
|
||
else if (line.has_value('S', new_temp)) {
|
||
target_temp = new_temp;
|
||
m_extruder_temps[m_extruder_id] = new_temp;
|
||
}
|
||
|
||
// 🔥 计算等待时间(考虑预热)
|
||
if (target_extruder >= m_extruder_temps.size())
|
||
return; // 无效喷头ID
|
||
|
||
float current_temp = m_extruder_temps[target_extruder];
|
||
float temp_diff = target_temp - current_temp;
|
||
bool is_heating = temp_diff > 0;
|
||
|
||
// 只有温差超过阈值才计算等待时间
|
||
if (std::abs(temp_diff) > 5.0f) {
|
||
float wait_time = 0.0f;
|
||
float heating_rate = m_extruder_heating_rate;
|
||
|
||
// 降温处理(R参数)
|
||
if (!is_heating) {
|
||
// 降温速度比加热慢
|
||
// 被动降温:约0.5-1°C/s
|
||
// 主动风扇降温:约1-2°C/s
|
||
heating_rate = 0.8f; // 保守估计
|
||
temp_diff = -temp_diff; // 转为正值
|
||
}
|
||
|
||
// 检查是否有预热(只对加热有效)
|
||
if (is_heating &&
|
||
target_extruder < m_extruder_preheat_states.size() &&
|
||
m_extruder_preheat_states[target_extruder].is_preheating) {
|
||
|
||
// 计算从预热开始到现在已经过去的时间
|
||
float current_time = get_time(PrintEstimatedStatistics::ETimeMode::Normal);
|
||
float elapsed_preheat_time = current_time -
|
||
m_extruder_preheat_states[target_extruder].preheat_start_time;
|
||
|
||
// 计算预热期间已经加热了多少度
|
||
float preheated_temp_diff = elapsed_preheat_time * heating_rate;
|
||
|
||
// 计算剩余需要加热的温度
|
||
float remaining_temp_diff = std::max(0.0f, temp_diff - preheated_temp_diff);
|
||
|
||
// 计算剩余等待时间
|
||
if (remaining_temp_diff > 0.0f) {
|
||
wait_time = remaining_temp_diff / heating_rate;
|
||
}
|
||
// else: 预热已经完成,等待时间为0
|
||
|
||
// 清除预热状态
|
||
m_extruder_preheat_states[target_extruder].is_preheating = false;
|
||
} else {
|
||
// 没有预热或降温,完整计算等待时间
|
||
wait_time = temp_diff / heating_rate;
|
||
}
|
||
|
||
// 限制在合理范围
|
||
// 喷头加热最长120秒(从室温到300°C)
|
||
wait_time = std::clamp(wait_time, 0.0f, 120.0f);
|
||
|
||
// 只有等待时间>1秒才添加(避免噪音)
|
||
if (wait_time > 1.0f) {
|
||
simulate_st_synchronize(wait_time);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点说明**:
|
||
1. **支持R参数**: 可以等待降温,使用较慢的降温速率
|
||
2. **预热效果计算**: 已预热时间 × 加热速率 = 已加热温度
|
||
3. **剩余等待时间**: 温度差 - 已加热温度 / 加热速率
|
||
4. **边界情况处理**:
|
||
- 温度已达标(预热成功)→ 等待时间0秒
|
||
- 预热失败或部分成功 → 计算剩余等待时间
|
||
- 降温 → 使用降温速率
|
||
|
||
### 3.5 M140处理 - process_M140()(记录热床预热)
|
||
|
||
**文件**: `src/libslic3r/GCode/GCodeProcessor.cpp`
|
||
|
||
**原始代码**:
|
||
```cpp
|
||
void GCodeProcessor::process_M140(const GCodeReader::GCodeLine& line)
|
||
{
|
||
float new_temp;
|
||
if (line.has_value('S', new_temp))
|
||
m_highest_bed_temp = m_highest_bed_temp < (int)new_temp ? (int)new_temp : m_highest_bed_temp;
|
||
}
|
||
```
|
||
|
||
**修改后代码**:
|
||
```cpp
|
||
void GCodeProcessor::process_M140(const GCodeReader::GCodeLine& line)
|
||
{
|
||
float new_temp;
|
||
if (line.has_value('S', new_temp)) {
|
||
m_highest_bed_temp = m_highest_bed_temp < (int)new_temp ?
|
||
(int)new_temp : m_highest_bed_temp;
|
||
|
||
// 🔥 记录热床预热状态
|
||
m_bed_preheat_state.is_preheating = true;
|
||
m_bed_preheat_state.preheat_start_time =
|
||
get_time(PrintEstimatedStatistics::ETimeMode::Normal);
|
||
m_bed_preheat_state.preheat_target_temp = new_temp;
|
||
m_bed_preheat_state.preheat_start_temp = m_highest_bed_temp;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.6 M190处理 - process_M190()(考虑预热效果)
|
||
|
||
**文件**: `src/libslic3r/GCode/GCodeProcessor.cpp`
|
||
|
||
**原始代码**:
|
||
```cpp
|
||
void GCodeProcessor::process_M190(const GCodeReader::GCodeLine& line)
|
||
{
|
||
float new_temp;
|
||
if (line.has_value('S', new_temp))
|
||
m_highest_bed_temp = m_highest_bed_temp < (int)new_temp ? (int)new_temp : m_highest_bed_temp;
|
||
// ❌ 没有添加等待时间
|
||
}
|
||
```
|
||
|
||
**修改后代码**:
|
||
```cpp
|
||
void GCodeProcessor::process_M190(const GCodeReader::GCodeLine& line)
|
||
{
|
||
float new_temp;
|
||
if (line.has_value('S', new_temp)) {
|
||
float current_bed_temp = m_highest_bed_temp;
|
||
m_highest_bed_temp = m_highest_bed_temp < (int)new_temp ?
|
||
(int)new_temp : m_highest_bed_temp;
|
||
|
||
float temp_diff = new_temp - current_bed_temp;
|
||
|
||
// 只有加热且温差>5°C才计算
|
||
if (temp_diff > 5.0f) {
|
||
float wait_time = 0.0f;
|
||
|
||
// 🔥 检查是否有预热
|
||
if (m_bed_preheat_state.is_preheating) {
|
||
float current_time = get_time(PrintEstimatedStatistics::ETimeMode::Normal);
|
||
float elapsed_preheat_time = current_time -
|
||
m_bed_preheat_state.preheat_start_time;
|
||
|
||
float preheated_temp_diff = elapsed_preheat_time * m_bed_heating_rate;
|
||
float remaining_temp_diff = std::max(0.0f, temp_diff - preheated_temp_diff);
|
||
|
||
if (remaining_temp_diff > 0.0f) {
|
||
wait_time = remaining_temp_diff / m_bed_heating_rate;
|
||
}
|
||
|
||
m_bed_preheat_state.is_preheating = false;
|
||
} else {
|
||
wait_time = temp_diff / m_bed_heating_rate;
|
||
}
|
||
|
||
// 热床加热较慢,最长300秒(5分钟)
|
||
wait_time = std::clamp(wait_time, 0.0f, 300.0f);
|
||
|
||
if (wait_time > 1.0f) {
|
||
simulate_st_synchronize(wait_time);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、实施步骤
|
||
|
||
### 步骤1:添加配置参数(15分钟)
|
||
|
||
1. 修改`src/libslic3r/PrintConfig.cpp`
|
||
- 添加`extruder_heating_rate`定义
|
||
- 添加`bed_heating_rate`定义
|
||
|
||
2. 修改`src/libslic3r/PrintConfig.hpp`
|
||
- 在`MachineEnvelopeConfig`或相应类中声明配置
|
||
|
||
3. 编译测试
|
||
```bash
|
||
build_release_vs2022.bat slicer
|
||
```
|
||
|
||
### 步骤2:修改GCodeProcessor头文件(10分钟)
|
||
|
||
1. 修改`src/libslic3r/GCode/GCodeProcessor.hpp`
|
||
- 添加`PreheatingState`结构体定义
|
||
- 添加`BedPreheatingState`结构体定义
|
||
- 添加成员变量:
|
||
- `m_extruder_heating_rate`
|
||
- `m_bed_heating_rate`
|
||
- `m_extruder_preheat_states`
|
||
- `m_bed_preheat_state`
|
||
|
||
### 步骤3:修改GCodeProcessor实现(45分钟)
|
||
|
||
1. 修改`src/libslic3r/GCode/GCodeProcessor.cpp`
|
||
- `apply_config()` - 加载配置
|
||
- `reset()` - 初始化预热状态
|
||
- `process_M104()` - 记录喷头预热
|
||
- `process_M109()` - 计算等待时间(考虑预热)
|
||
- `process_M140()` - 记录热床预热
|
||
- `process_M190()` - 计算等待时间(考虑预热)
|
||
|
||
2. 编译测试
|
||
```bash
|
||
build_release_vs2022.bat slicer
|
||
```
|
||
|
||
### 步骤4:验证测试(30分钟)
|
||
|
||
1. **简单测试**: 单次工具切换的双色模型
|
||
- 检查G-code中的M104和M109位置
|
||
- 对比估算时间 vs 手动计算时间
|
||
- 验证预热逻辑是否生效
|
||
|
||
2. **复杂测试**: 50+次工具切换的多色模型
|
||
- 对比估算时间改善
|
||
- 验证累积误差是否可接受
|
||
|
||
3. **边缘测试**:
|
||
- 首层打印前的M109(无预热)
|
||
- 温度跨度大的切换(PLA→PETG)
|
||
- 降温等待(M109 R参数)
|
||
|
||
### 步骤5:参数调优(可选)
|
||
|
||
1. 实测U1的加热速率
|
||
- 从室温到220°C的加热时间
|
||
- 从150°C到220°C的加热时间
|
||
- 计算实际加热速率
|
||
|
||
2. 调整U1配置文件中的默认值
|
||
```json
|
||
{
|
||
"extruder_heating_rate": 3.0, // 根据实测调整
|
||
"bed_heating_rate": 0.8
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、测试计划
|
||
|
||
### 5.1 单元测试
|
||
|
||
**测试场景1**: 无预热的M109
|
||
```
|
||
输入:
|
||
- 当前温度:25°C
|
||
- 目标温度:220°C
|
||
- 无预热状态
|
||
|
||
预期:
|
||
- 温度差:195°C
|
||
- 等待时间:195 / 2.5 = 78秒
|
||
```
|
||
|
||
**测试场景2**: 预热成功的M109
|
||
```
|
||
输入:
|
||
- 当前温度:170°C
|
||
- 目标温度:220°C
|
||
- 预热开始时间:t=100s
|
||
- 当前时间:t=120s
|
||
- 已预热时间:20s
|
||
|
||
预期:
|
||
- 温度差:50°C
|
||
- 已加热温度:20s × 2.5 = 50°C
|
||
- 剩余需加热:0°C
|
||
- 等待时间:0秒 ✅
|
||
```
|
||
|
||
**测试场景3**: 预热部分成功的M109
|
||
```
|
||
输入:
|
||
- 当前温度:170°C
|
||
- 目标温度:220°C
|
||
- 预热开始时间:t=100s
|
||
- 当前时间:t=110s
|
||
- 已预热时间:10s
|
||
|
||
预期:
|
||
- 温度差:50°C
|
||
- 已加热温度:10s × 2.5 = 25°C
|
||
- 剩余需加热:25°C
|
||
- 等待时间:25 / 2.5 = 10秒 ✅
|
||
```
|
||
|
||
**测试场景4**: 降温等待(M109 R参数)
|
||
```
|
||
输入:
|
||
- 当前温度:220°C
|
||
- 目标温度:150°C(R参数)
|
||
- 无预热(降温不适用预热)
|
||
|
||
预期:
|
||
- 温度差:70°C
|
||
- 降温速率:0.8°C/s
|
||
- 等待时间:70 / 0.8 = 87.5秒
|
||
```
|
||
|
||
### 5.2 集成测试
|
||
|
||
**测试用例1**: 双色打印(2-3次工具切换)
|
||
```
|
||
模型:简单双色立方体
|
||
工具切换次数:3次
|
||
预期:
|
||
- 每次切换估算时间接近实际时间
|
||
- 总时间估算误差 < 10%
|
||
```
|
||
|
||
**测试用例2**: 多色打印(50+次工具切换)
|
||
```
|
||
模型:复杂多色模型
|
||
工具切换次数:50+
|
||
预期:
|
||
- 累积误差 < 15%
|
||
- 不会出现时间估算爆炸
|
||
```
|
||
|
||
**测试用例3**: 首层打印(无预热)
|
||
```
|
||
场景:首次加热,start_gcode中的M109
|
||
预期:
|
||
- 正确计算加热等待时间
|
||
- 估算时间与实际接近
|
||
```
|
||
|
||
### 5.3 性能测试
|
||
|
||
**测试项目**:
|
||
1. 预热状态追踪的内存开销(应该可忽略)
|
||
2. G-code处理速度(应该无明显影响)
|
||
3. 大模型处理(100k+行G-code)
|
||
|
||
---
|
||
|
||
## 六、风险评估与缓解
|
||
|
||
### 6.1 风险点
|
||
|
||
| 风险 | 严重性 | 概率 | 缓解措施 |
|
||
|-----|-------|------|---------|
|
||
| 预热时间追踪不准确 | 高 | 中 | 详细测试,边界情况处理 |
|
||
| 加热速率配置不准确 | 中 | 高 | 提供实测指南,保守默认值 |
|
||
| get_time()函数语义错误 | 高 | 低 | 代码审查,验证时间戳 |
|
||
| 边缘情况未处理 | 中 | 中 | 全面测试,防御性编程 |
|
||
| 性能影响 | 低 | 低 | 性能测试 |
|
||
|
||
### 6.2 回滚方案
|
||
|
||
如果方案B出现严重问题,可以快速回滚到保守方案(方案A):
|
||
|
||
```cpp
|
||
// 临时禁用预热逻辑的快速修复
|
||
void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line)
|
||
{
|
||
// ... 解析参数 ...
|
||
|
||
// 简化版:不考虑预热
|
||
float temp_diff = target_temp - current_temp;
|
||
if (temp_diff > 5.0f) {
|
||
float wait_time = temp_diff / m_extruder_heating_rate;
|
||
wait_time = std::clamp(wait_time, 0.0f, 120.0f);
|
||
if (wait_time > 1.0f) {
|
||
simulate_st_synchronize(wait_time);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 七、预期效果
|
||
|
||
### 7.1 时间估算改善
|
||
|
||
**场景1**: 首层打印前加热
|
||
- **现状**: 实际60秒,估算0秒
|
||
- **修复后**: 实际60秒,估算58秒 ✅
|
||
|
||
**场景2**: 工具切换(有预热)
|
||
- **现状**: 实际5秒,估算0秒
|
||
- **简单方案**: 实际5秒,估算30秒 ❌
|
||
- **方案B**: 实际5秒,估算3秒 ✅
|
||
|
||
**场景3**: 50次工具切换的打印
|
||
- **现状**: 实际250秒,估算0秒
|
||
- **简单方案**: 实际250秒,估算1500秒 ❌(多估算20分钟)
|
||
- **方案B**: 实际250秒,估算150秒 ✅(误差±2.5分钟)
|
||
|
||
### 7.2 整体精度提升
|
||
|
||
| 场景 | 现状误差 | 方案A误差 | 方案B误差 |
|
||
|-----|---------|-----------|-----------|
|
||
| 单色打印 | ±20% | ±10% | ±5% |
|
||
| 双色打印(少量切换) | ±30% | ±25% | ±8% |
|
||
| 多色打印(频繁切换) | ±50% | ±70% ⚠️ | ±12% ✅ |
|
||
|
||
---
|
||
|
||
## 八、后续优化方向
|
||
|
||
### 8.1 短期优化(1-2周)
|
||
|
||
1. **GUI配置界面**
|
||
- 在"打印机设置 → 高级选项"中添加加热速率配置
|
||
- 提供"测试加热速率"功能按钮
|
||
|
||
2. **实测数据收集**
|
||
- 收集U1实际打印的加热时间数据
|
||
- 调整默认值以更贴近实际
|
||
|
||
3. **文档完善**
|
||
- 用户手册:如何测试和配置加热速率
|
||
- 开发文档:预热逻辑的实现原理
|
||
|
||
### 8.2 中期优化(1-2个月)
|
||
|
||
1. **动态加热速率**
|
||
- 根据温度区间调整加热速率
|
||
- 如:50-150°C较快,150-250°C较慢
|
||
|
||
2. **降温速率配置**
|
||
- 添加`extruder_cooling_rate`配置
|
||
- 区分被动降温和主动风扇降温
|
||
|
||
3. **遥测数据收集**
|
||
- 收集估算时间 vs 实际时间的对比数据
|
||
- 持续优化算法
|
||
|
||
### 8.3 长期优化(3-6个月)
|
||
|
||
1. **机器学习优化**
|
||
- 基于历史打印数据训练模型
|
||
- 预测更精确的加热时间
|
||
|
||
2. **环境因素考虑**
|
||
- 考虑环境温度对加热速率的影响
|
||
- 根据打印材料调整加热速率
|
||
|
||
---
|
||
|
||
## 九、FAQ
|
||
|
||
### Q1: 为什么不能使用简单方案(方案A)?
|
||
|
||
**A**: 简单方案不考虑预热效果,对U1多头机型会导致时间估算严重高估50-100%。U1的预热逻辑是核心功能,每次工具切换都会预热,如果不考虑预热,会错误地为每次切换添加30-40秒等待时间。
|
||
|
||
### Q2: 预热状态追踪会不会很复杂?
|
||
|
||
**A**: 实现并不复杂:
|
||
1. M104时记录预热开始时间和目标温度
|
||
2. M109时计算已预热时间,扣除已加热的温度
|
||
3. 清除预热状态
|
||
|
||
核心逻辑只需要20-30行代码。
|
||
|
||
### Q3: 如果预热时间设置不合理怎么办?
|
||
|
||
**A**: 预热时间由`preheat_time`配置决定,是用户可调的。即使预热时间设置不合理:
|
||
- 预热时间过短 → M109会计算剩余等待时间,不会低估
|
||
- 预热时间过长 → M109会识别温度已达标,等待时间为0
|
||
|
||
算法具有鲁棒性。
|
||
|
||
### Q4: 加热速率如何测试?
|
||
|
||
**A**: 测试方法:
|
||
```
|
||
1. 从室温启动打印机
|
||
2. 执行 M104 S220(开始加热)
|
||
3. 记录开始时间
|
||
4. 观察温度曲线,记录达到220°C的时间
|
||
5. 计算速率:(220 - 室温) / 时间
|
||
|
||
示例:
|
||
- 室温:25°C
|
||
- 目标:220°C
|
||
- 时间:78秒
|
||
- 速率:(220-25)/78 = 2.5°C/s
|
||
```
|
||
|
||
### Q5: 方案B会影响性能吗?
|
||
|
||
**A**: 几乎不会:
|
||
- 预热状态结构体很小(每个喷头16字节)
|
||
- 只在处理M104/M109时计算,次数很少
|
||
- 计算量微不足道(几次浮点运算)
|
||
|
||
性能影响可忽略不计。
|
||
|
||
---
|
||
|
||
## 十、总结
|
||
|
||
### 核心要点
|
||
|
||
✅ **必须实现方案B**
|
||
对U1多头机型,不考虑预热会导致时间估算严重高估
|
||
|
||
✅ **实现并不复杂**
|
||
核心逻辑只需修改6个函数,添加约100行代码
|
||
|
||
✅ **效果显著**
|
||
时间估算精度从±30-50%提升到±5-15%
|
||
|
||
✅ **风险可控**
|
||
有清晰的测试计划和回滚方案
|
||
|
||
### 实施建议
|
||
|
||
1. **一次性完整实现**:不要分阶段,必须同时实现预热追踪
|
||
2. **充分测试**:特别是多工具切换场景
|
||
3. **实测校准**:根据U1实测数据调整默认参数
|
||
4. **文档完善**:提供用户配置指南
|
||
|
||
### 成功标准
|
||
|
||
- ✅ 多色打印时间估算误差 < 15%
|
||
- ✅ 工具切换等待时间估算接近实际(误差 < 5秒)
|
||
- ✅ 无性能影响
|
||
- ✅ 无回归bug
|
||
|
||
---
|
||
|
||
**文档完成日期**: 2025-12-06
|
||
**待实施状态**: 方案已完成,待开发实施
|