* 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
23 KiB
打印时间预估分析文档
概述
本文档详细分析了OrcaSlicer中所有影响打印时间预估的工艺配置项,以及温度相关的预估逻辑。
时间预估核心机制
时间预估的核心实现在 src/libslic3r/GCode/GCodeProcessor.cpp 中的 TimeMachine 类。它通过以下方式计算时间:
- 梯形速度曲线(Trapezoid):每个移动块(TimeBlock)被分解为加速、巡航、减速三个阶段
- 加速度限制:根据配置的加速度值计算加速/减速所需的时间和距离
- 速度限制:根据配置的速度值计算巡航阶段的时间
- 同步等待:某些命令(如M400、G4、M191等)会调用
simulate_st_synchronize()添加固定等待时间
影响时间预估的配置项
1. 速度相关配置(Speed Settings)
这些配置直接影响移动速度,从而影响时间预估:
| 配置名称 | 配置解释 | 代码位置 | 如何影响时间预估 |
|---|---|---|---|
initial_layer_speed |
首层打印速度 | GCodeProcessor::process_line_G1() |
首层所有移动使用此速度,速度越低时间越长 |
initial_layer_infill_speed |
首层填充速度 | GCodeProcessor::process_line_G1() |
首层填充移动使用此速度 |
outer_wall_speed |
外壁速度 | GCodeProcessor::process_line_G1() |
外壁移动使用此速度,影响外壁打印时间 |
inner_wall_speed |
内壁速度 | GCodeProcessor::process_line_G1() |
内壁移动使用此速度,影响内壁打印时间 |
top_surface_speed |
顶面速度 | GCodeProcessor::process_line_G1() |
顶面移动使用此速度 |
internal_solid_infill_speed |
内部实心填充速度 | GCodeProcessor::process_line_G1() |
内部实心填充移动使用此速度 |
sparse_infill_speed |
稀疏填充速度 | GCodeProcessor::process_line_G1() |
稀疏填充移动使用此速度 |
gap_infill_speed |
间隙填充速度 | GCodeProcessor::process_line_G1() |
间隙填充移动使用此速度 |
travel_speed |
空走速度 | GCodeProcessor::process_line_G1() |
空走移动使用此速度,影响空走时间 |
small_perimeter_speed |
小周长速度 | GCodeProcessor::process_line_G1() |
小周长移动使用此速度 |
代码级描述:
- 在
GCodeProcessor::process_line_G1()中,根据移动类型(ExtrusionRole)选择对应的速度配置 - 速度值直接用于计算
TimeBlock的cruise_feedrate - 时间计算公式:
time = acceleration_time + cruise_time + deceleration_time - 其中
cruise_time = cruise_distance / cruise_feedrate
2. 加速度相关配置(Acceleration Settings)
这些配置影响加速/减速过程,从而影响时间预估:
| 配置名称 | 配置解释 | 代码位置 | 如何影响时间预估 |
|---|---|---|---|
default_acceleration |
默认加速度 | GCodeProcessor::apply_config() |
作为默认加速度值,影响所有移动的加速/减速时间 |
initial_layer_acceleration |
首层加速度 | GCodeProcessor::process_line_G1() |
首层移动使用此加速度,加速度越低加速/减速时间越长 |
outer_wall_acceleration |
外壁加速度 | GCodeProcessor::process_line_G1() |
外壁移动使用此加速度 |
inner_wall_acceleration |
内壁加速度 | GCodeProcessor::process_line_G1() |
内壁移动使用此加速度 |
top_surface_acceleration |
顶面加速度 | GCodeProcessor::process_line_G1() |
顶面移动使用此加速度 |
internal_solid_infill_acceleration |
内部实心填充加速度 | GCodeProcessor::process_line_G1() |
内部实心填充移动使用此加速度 |
sparse_infill_acceleration |
稀疏填充加速度 | GCodeProcessor::process_line_G1() |
稀疏填充移动使用此加速度 |
travel_acceleration |
空走加速度 | GCodeProcessor::set_travel_acceleration() |
空走移动使用此加速度 |
bridge_acceleration |
桥接加速度 | GCodeProcessor::process_line_G1() |
桥接移动使用此加速度 |
代码级描述:
- 在
GCodeProcessor::apply_config()中,加速度值被设置到TimeMachine::acceleration - 在
TimeBlock::calculate_trapezoid()中,使用加速度计算加速/减速距离:float accelerate_distance = estimated_acceleration_distance(entry_feedrate, cruise_feedrate, acceleration); float decelerate_distance = estimated_acceleration_distance(cruise_feedrate, exit_feedrate, -acceleration); - 加速度越低,加速/减速距离越长,巡航距离越短,但总时间可能更长(取决于移动距离)
3. 机器限制配置(Machine Limits)
这些配置限制了速度和加速度的上限:
| 配置名称 | 配置解释 | 代码位置 | 如何影响时间预估 |
|---|---|---|---|
machine_max_acceleration_extruding |
挤出时最大加速度 | GCodeProcessor::apply_config() |
限制所有挤出移动的加速度上限 |
machine_max_acceleration_retracting |
回抽时最大加速度 | GCodeProcessor::apply_config() |
限制回抽移动的加速度上限 |
machine_max_acceleration_travel |
空走时最大加速度 | GCodeProcessor::apply_config() |
限制空走移动的加速度上限 |
machine_max_speed_x |
X轴最大速度 | GCodeProcessor::process_M203() |
限制X轴移动速度上限 |
machine_max_speed_y |
Y轴最大速度 | GCodeProcessor::process_M203() |
限制Y轴移动速度上限 |
machine_max_speed_z |
Z轴最大速度 | GCodeProcessor::process_M203() |
限制Z轴移动速度上限 |
machine_max_speed_e |
E轴最大速度 | GCodeProcessor::process_M203() |
限制挤出速度上限 |
代码级描述:
- 在
GCodeProcessor::apply_config()中,机器限制被设置到TimeMachine::max_acceleration - 在
TimeBlock::calculate_trapezoid()中,加速度会被限制在最大值内 - 在
GCodeProcessor::process_M203()中,速度限制被应用到各轴
4. 温度相关配置(Temperature Settings)
4.1 预热配置
| 配置名称 | 配置解释 | 代码位置 | 如何影响时间预估 |
|---|---|---|---|
preheat_time |
预热时间 | GCodeProcessor::apply_config() |
不直接影响时间预估,仅用于计算何时插入M104预热命令 |
delta_temperature |
预热温度差 | GCodeProcessor::apply_config() |
不直接影响时间预估,仅用于计算预热温度 |
preheat_steps |
预热步数 | GCodeProcessor::apply_config() |
不直接影响时间预估,用于预热回溯的步数 |
代码级描述:
- 在
GCodeProcessor::apply_config()中,这些值被存储为成员变量 - 在
GCodeProcessor::process_line_T()中,使用preheat_time和preheat_steps计算何时插入M104命令 - 重要:预热时间本身不被添加到时间预估中,因为M104命令是异步的(不等待)
4.2 温度等待命令
| G代码命令 | 配置解释 | 代码位置 | 如何影响时间预估 |
|---|---|---|---|
M109 |
设置温度并等待 | GCodeProcessor::process_M109() |
当前实现有问题:只更新温度值,没有添加等待时间 |
M190 |
等待热床温度 | GCodeProcessor::process_M190() |
当前实现有问题:只更新温度值,没有添加等待时间 |
M191 |
等待腔室温度 | GCodeProcessor::process_M191() |
如果温度>40°C,添加硬编码的720秒等待时间 |
代码级描述:
M109问题(src/libslic3r/GCode/GCodeProcessor.cpp:3640-3655):
void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line)
{
float new_temp;
if (line.has_value('R', new_temp)) {
// ... 更新温度值 ...
}
else if (line.has_value('S', new_temp))
m_extruder_temps[m_extruder_id] = new_temp;
// ❌ 问题:没有调用 simulate_st_synchronize() 添加等待时间
}
M190问题(src/libslic3r/GCode/GCodeProcessor.cpp:3696-3701):
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;
// ❌ 问题:没有调用 simulate_st_synchronize() 添加等待时间
}
M191实现(src/libslic3r/GCode/GCodeProcessor.cpp:3703-3710):
void GCodeProcessor::process_M191(const GCodeReader::GCodeLine& line)
{
float chamber_temp = 0;
const float wait_chamber_temp_time = 720.0; // 硬编码720秒
if (line.has_value('S', chamber_temp) && chamber_temp > 40)
simulate_st_synchronize(wait_chamber_temp_time); // ✅ 正确添加了等待时间
}
问题分析:
- 所有M109命令都没有计算等待时间:
process_M109()只更新温度值,没有调用simulate_st_synchronize() - 所有M190命令都没有计算等待时间:
process_M190()只更新温度值,没有调用simulate_st_synchronize() - 这导致时间预估显著偏短,因为忽略了所有温度等待时间
- 实际打印中,M109/M190会阻塞直到温度达到,但预估中没有计算这部分时间
- 影响最严重的情况:
- 首层打印前:如果开始G-code中有M109,等待时间可能达到30-60秒
- 工具切换时:如果切换工具需要加热,等待时间可能达到20-40秒
- 温度变化大时:从低温(如150°C)加热到高温(如280°C),等待时间可能达到50-80秒
5. 工具切换相关配置(Tool Change Settings)
| 配置名称 | 配置解释 | 代码位置 | 如何影响时间预估 |
|---|---|---|---|
machine_tool_change_time |
工具切换时间 | GCodeProcessor::apply_config() |
在 process_T() 中,每次工具切换添加此时间 |
machine_load_filament_time |
加载耗材时间 | GCodeProcessor::apply_config() |
在 process_T() 中,工具切换时添加此时间 |
machine_unload_filament_time |
卸载耗材时间 | GCodeProcessor::apply_config() |
在 process_M702() 中,卸载时添加此时间 |
代码级描述:
- 在
GCodeProcessor::process_T()中(src/libslic3r/GCode/GCodeProcessor.cpp:3964-4012):float extra_time = 0.0f; if (m_time_processor.extruder_unloaded) { m_time_processor.extruder_unloaded = false; extra_time += get_filament_load_time(static_cast<size_t>(m_extruder_id)); extra_time += m_time_processor.machine_tool_change_time; simulate_st_synchronize(extra_time); }
6. 延迟/等待命令(Delay/Wait Commands)
| G代码命令 | 配置解释 | 代码位置 | 如何影响时间预估 |
|---|---|---|---|
G4 |
延迟命令 | GCodeProcessor::process_G4() |
添加指定的延迟时间(S参数为秒,P参数为毫秒) |
M1 |
暂停等待 | GCodeProcessor::process_M1() |
添加同步等待时间(无限等待,但预估中可能不计算) |
M400 |
等待移动完成 | GCodeProcessor::process_M400() |
添加同步等待时间 |
代码级描述:
G4命令(src/libslic3r/GCode/GCodeProcessor.cpp:3457-3462):float value_s = 0.0; float value_p = 0.0; if (line.has_value('S', value_s) || line.has_value('P', value_p)) { value_s += value_p * 0.001; // P参数转换为秒 simulate_st_synchronize(value_s); }
7. 其他影响时间预估的因素
| 配置/因素 | 配置解释 | 代码位置 | 如何影响时间预估 |
|---|---|---|---|
slow_down_layer_time |
层冷却时间 | CoolingBuffer |
如果层时间太短,会降低速度以增加层时间 |
fan_cooling_layer_time |
风扇冷却层时间 | CoolingBuffer |
影响冷却逻辑,可能降低速度 |
G29 (自动调平) |
自动调平 | GCodeProcessor::process_G29() |
硬编码添加260秒等待时间 |
retraction_speed |
回抽速度 | GCodeProcessor::process_line_G1() |
影响回抽移动的时间 |
deretraction_speed |
回退速度 | GCodeProcessor::process_line_G1() |
影响回退移动的时间 |
温度相关时间预估问题总结
问题1:M109没有添加等待时间
位置:src/libslic3r/GCode/GCodeProcessor.cpp:3640-3655
问题:M109命令应该等待温度达到目标值,但当前实现只更新了温度值,没有添加等待时间。
影响:时间预估会偏短,因为忽略了温度等待时间。对于需要从低温加热到高温的情况(如从室温到220°C),实际等待时间可能达到30-60秒,但预估中没有计算。
建议修复:
void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line)
{
float new_temp;
float current_temp = m_extruder_temps[m_extruder_id];
if (line.has_value('R', new_temp)) {
// ... 处理R参数 ...
}
else if (line.has_value('S', new_temp)) {
m_extruder_temps[m_extruder_id] = new_temp;
}
// 计算等待时间:根据温度差估算
if (new_temp > current_temp) {
float temp_diff = new_temp - current_temp;
// 假设加热速度约为 2-3°C/秒(可根据实际情况调整)
float wait_time = temp_diff / 2.5f; // 保守估计
// 最小等待时间5秒,最大等待时间120秒
wait_time = std::clamp(wait_time, 5.0f, 120.0f);
simulate_st_synchronize(wait_time);
}
}
问题2:M190没有添加等待时间
位置:src/libslic3r/GCode/GCodeProcessor.cpp:3696-3701
问题:M190命令应该等待热床温度达到目标值,但当前实现只更新了温度值,没有添加等待时间。
影响:时间预估会偏短,特别是首层打印前的热床预热时间。
建议修复:
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;
// 计算等待时间:热床加热速度较慢,约为 0.5-1°C/秒
if (new_temp > current_bed_temp) {
float temp_diff = new_temp - current_bed_temp;
float wait_time = temp_diff / 0.75f; // 保守估计
// 最小等待时间10秒,最大等待时间300秒
wait_time = std::clamp(wait_time, 10.0f, 300.0f);
simulate_st_synchronize(wait_time);
}
}
}
问题3:M191使用硬编码时间
位置:src/libslic3r/GCode/GCodeProcessor.cpp:3703-3710
问题:M191使用硬编码的720秒(12分钟)等待时间,没有根据实际温度差计算。
影响:对于不同的温度目标,等待时间可能不准确。
建议修复:
void GCodeProcessor::process_M191(const GCodeReader::GCodeLine& line)
{
float chamber_temp = 0;
if (line.has_value('S', chamber_temp) && chamber_temp > 40) {
// 腔室加热速度很慢,约为 0.1-0.2°C/秒
// 假设从室温(~25°C)加热到目标温度
float temp_diff = chamber_temp - 25.0f;
float wait_time = temp_diff / 0.15f; // 保守估计
// 最小等待时间60秒,最大等待时间1800秒(30分钟)
wait_time = std::clamp(wait_time, 60.0f, 1800.0f);
simulate_st_synchronize(wait_time);
}
}
问题4:预热时间不参与时间预估
位置:src/libslic3r/GCode/GCodeProcessor.cpp:4594-4668
问题:preheat_time 配置仅用于计算何时插入M104命令,但不参与时间预估。
影响:如果预热时间设置较长,实际打印中工具切换时的等待时间可能被缩短,但预估中没有考虑这个优化。
分析:
- 预热机制的目的是减少工具切换时的等待时间
- 如果预热成功,M109的等待时间应该减少(因为工具已经预热)
- 当前实现中,预热时间不参与预估是合理的,因为:
- M104是异步命令,不阻塞打印
- 预热是否成功取决于实际打印进度
- 如果预热失败,仍然需要等待M109
建议:保持当前实现,但可以考虑在M109中添加逻辑,如果检测到之前有M104预热命令,可以减少等待时间。
补充问题解答
1. 打印准备时间(prepare_time)是怎么来的?
位置:src/libslic3r/GCode/GCodeProcessor.cpp:375-376, 2774, 3228
机制:
-
标记准备阶段:在
process_line_G1()中,当处理G1/G0命令时,会创建TimeBlock并设置:block.flags.prepare_stage = m_processing_start_custom_gcode; -
设置准备阶段标志:
m_processing_start_custom_gcode在process_gcode_line()中设置:// 当遇到自定义G代码(erCustom)且是第一条G1命令时 m_processing_start_custom_gcode = (m_extrusion_role == erCustom && m_g1_line_id == 0);这意味着开始G代码(start_gcode)中的移动会被标记为准备阶段。
-
累加准备时间:在
TimeMachine::calculate_time()中:if (block.flags.prepare_stage) prepare_time += block_time;
包含的内容:
- 开始G代码(
machine_start_gcode)中的所有移动时间 - 开始G代码中的温度设置、回零等操作时间
- 首层打印前的所有准备操作时间
注意:准备阶段中的空走(Travel)移动不计入空走时间统计,但计入准备时间:
//BBS: don't calculate travel of start gcode into travel time
if (!block.flags.prepare_stage || block.move_type != EMoveType::Travel)
moves_time[static_cast<size_t>(block.move_type)] += block_time;
用途:
- 在UI中显示"首层打印时间"时,会从总时间中减去准备时间
- 用于区分"准备时间"和"实际打印时间"
2. simulate_st_synchronize 是干什么的?
位置:src/libslic3r/GCode/GCodeProcessor.cpp:257-263
功能:模拟固件(如Marlin)的 st_synchronize() 函数调用,表示等待所有移动完成并添加额外时间。
实现:
void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time)
{
if (!enabled)
return;
calculate_time(0, additional_time);
}
作用:
- 处理时间块队列:调用
calculate_time()处理blocks队列中累积的移动块 - 添加额外时间:将
additional_time添加到第一个待处理块的时间中 - 更新统计:累加到总时间、层时间、角色时间等统计中
使用场景:
- G4延迟命令:
process_G4()调用simulate_st_synchronize(value_s)添加延迟时间 - M400等待完成:
process_M400()调用simulate_st_synchronize()等待移动完成 - M1暂停:
process_M1()调用simulate_st_synchronize()添加暂停时间 - M191等待腔室温度:
process_M191()调用simulate_st_synchronize(720.0)添加等待时间 - 工具切换:
process_T()调用simulate_st_synchronize(extra_time)添加切换和加载时间 - G29自动调平:
process_G29()调用simulate_st_synchronize(260.0)添加调平时间
为什么需要:
- 固件的
st_synchronize()会阻塞直到所有移动完成 - 某些命令(如M109、M190)需要等待操作完成
- 需要在时间预估中反映这些等待时间
问题:当前实现中,M109和M190没有调用 simulate_st_synchronize(),导致等待时间没有被计算。
3. 算时间是所有G-code都生成了之后后处理的还是生成一条G-code就算一行?
答案:混合模式 - 大部分是实时处理,最终计算是后处理。
详细流程:
3.1 实时处理阶段(G-code生成/解析时)
位置:src/libslic3r/GCode/GCodeProcessor.cpp:1550-2074 (process_gcode_line)
过程:
- 解析G-code行:每解析一行G-code,调用
process_gcode_line() - 处理G1/G0命令:遇到G1/G0命令时,调用
store_move_vertex()→process_line_G1() - 创建时间块:在
process_line_G1()中创建TimeBlock并添加到blocks队列:TimeBlock block; block.move_type = type; block.role = m_extrusion_role; block.distance = distance; // ... 设置其他属性 ... machine.blocks.push_back(block); - 立即计算(部分):某些命令会立即调用
simulate_st_synchronize():- G4延迟:立即添加延迟时间
- M400等待:立即处理队列
- 工具切换:立即添加切换时间
特点:
- 时间块(TimeBlock)是实时创建的
- 但不立即计算最终时间(因为需要前后块的信息来优化速度曲线)
3.2 后处理阶段(所有G-code处理完成后)
位置:src/libslic3r/GCode/GCodeProcessor.cpp:1309-1361 (finalize)
过程:
-
最终计算:在
finalize()中调用calculate_time():for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; machine.calculate_time(); // 处理所有剩余的blocks } -
速度曲线优化:
calculate_time()执行:- 前向传递(forward_pass):从前往后优化入口速度
- 反向传递(reverse_pass):从后往前优化出口速度
- 重新计算梯形:根据优化后的速度重新计算加速/巡航/减速阶段
-
累加统计:计算每个块的时间并累加到:
time:总时间prepare_time:准备时间layers_time:每层时间roles_time:每种角色时间moves_time:每种移动类型时间
为什么需要后处理:
- 速度曲线优化:需要知道后续块的速度才能优化当前块的出口速度
- 加速度限制:需要确保相邻块之间的速度转换符合加速度限制
- 全局优化:需要反向传递来确保从后往前的速度优化
3.3 混合模式的原因
实时处理:
- 创建时间块(TimeBlock)
- 处理立即生效的命令(G4、M400等)
- 累积到队列中
后处理:
- 优化速度曲线(需要全局信息)
- 计算最终时间(需要所有块的信息)
- 更新统计信息
性能考虑:
- 如果每条G-code都完整计算,性能会很差(需要重新计算所有块)
- 采用批量处理 + 最终优化的方式,平衡了实时性和准确性
总结
影响时间预估的主要因素:
- 速度配置:直接影响移动时间
- 加速度配置:影响加速/减速时间
- 机器限制:限制速度和加速度上限
- 温度等待:当前实现有问题,M109/M190没有添加等待时间
- 工具切换:添加固定的切换和加载时间
- 延迟命令:G4、M400等添加固定延迟
- 准备时间:开始G代码中的操作时间,单独统计
- 时间计算模式:混合模式 - 实时创建时间块,后处理优化和计算
最严重的问题:M109和M190没有添加等待时间,这会导致时间预估显著偏短,特别是对于需要从低温加热到高温的打印任务。
时间计算流程:
- 实时:解析G-code → 创建TimeBlock → 添加到队列
- 实时(部分):某些命令立即调用
simulate_st_synchronize()处理队列 - 后处理:所有G-code处理完成后,调用
finalize()→calculate_time()优化速度曲线并计算最终时间