Files
A1SwapModPacker/docs/how_it_works.md
T

242 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# A1 Swap Mod Packer 工作原理
**版本**: v0.6.0 | **日期**: 2026-07-27
## 总览
给一个源 `.3mf` 文件,设好份数和换盘 G-code,输出一个打包好的新 `.3mf`
```mermaid
flowchart LR
A["源 3MF × N 份"] --> B["① 解压读取"]
B --> C["② 按份数展开"]
C --> D["③ 预处理<br/>(换行/补丁/M73模板)"]
D --> E["④ 逐份加工<br/>(M73偏移/插入换盘块)"]
E --> F["⑤ 拼接总G-code"]
F --> G["⑥ 写入新3MF<br/>(删除旧gcode/更新元数据/合成预览/压缩)"]
G --> H["输出 3MF"]
```
---
## ① 解压读取
`.3mf` 本质是 ZIP 包。`load_plate_sources()` 打开它,提取三样东西:
| 文件名 | 内容 |
|--------|------|
| `Metadata/plate_N.gcode` | 面板 G-code 文本 |
| `Metadata/slice_info.config` | 打印时间(prediction)、重量(weight |
| `Metadata/filament` 节点 | 耗材长度(used_m)、耗材重量(used_g |
同时记下 G-code 成员的名称(比如 `Metadata/plate_1.gcode`),后面写回时要用。
---
## ② 按份数展开
`expand_jobs()` 遍历所有输入文件,每个文件按设定的份数重复展开。
例:`A.3mf` 里有 1 块板,设 5 份 → 展开成 5 个 `PlateSource`
- 都指向 `A.3mf`
- 都引用 `Metadata/plate_1.gcode`
- 各自携带同样的预测时间、耗材信息
实际上 G-code 文本是**按需读取**的——相同 `(源文件, 面板名)` 组合只读取一次,后面的副本引用已有的 `PlateSource`
---
## ③ 预处理
对每个唯一的 G-code 文本,只做一遍以下处理,结果按 `(源文件路径, 面板成员名)` 键缓存:
### 换行统一
```python
text.replace("\r\n", "\n").replace("\r", "\n")
```
### G-code 补丁(可选)
如果启用了"应用可编辑的 G-code 补丁",按 `gcode_patches.ini` 的规则做查找替换:
```
[patch.a1_start_y]
flag = G0 X128 F30000 ← 从这行之后开始找
find = G0 Y254 F3000 ← 找到这行
replace = G0 Y250 F3000 ← 替换为这行
max_count = 1 ← 最多替换 1 次
```
### M73 模板拆解(可选)
如果启用了"剩余时间板号",用正则把 G-code 中所有 `M73 P0 R10` 拆成三段:
```
前缀 "M73 P0 R" + 数字 10 + 后缀 "\n"
```
存为 `M73OffsetTemplate`,后面逐份加工时直接拼接不用再扫一遍正则。
---
## ④ 逐份加工
对展开后的每一份,依次做两件事:
### M73 偏移
第 i 份的 `M73 R` 值加上 `i × 6000` 分钟(= 100 小时):
```
第1份: M73 P0 R6010 (+6000)
第2份: M73 P0 R12010 (+12000)
第3份: M73 P0 R18010 (+18000)
```
打印机显示剩余时间时,**百位数字正好是板号**。
### 插入换盘块
`;=====printer finish sound=========` 这一行**之前**插入换盘代码块:
```gcode
M190 S45 ; 等热床降温到 45°C(可选)
[你选的换盘G-code] ; 弹射/换板动作
G4 P45000 ; 等 45 秒(可选)
```
最后一份:
- 如果关了"最后换盘" → 不插换盘块
- 如果关了"最后换盘"但前面都插了 → 最后一份正常结束
---
## ⑤ 拼接总 G-code
把所有份已经加工好的 G-code 尾行 `\n` 拼成一个大字符串。
按你选的换行符编码:
- `crlf`(默认)→ `\n` 全部替换为 `\r\n`
- `lf` → 保持 `\n`
---
## ⑥ 写入新 3MF
`write_output_3mf()` 是最后一步。它不从头创建 ZIP——而是**以第一个源 3MF 为模板**,部分覆盖:
### 删除旧的
- 所有 `Metadata/plate_N.gcode`
- 所有 `Metadata/plate_N.gcode.md5`
### 写入新的
- 把拼接好的总 G-code 写到原来的 `plate_1.gcode` 成员名(或其 MD5 对应的成员)
- 同时写入 `plate_1.gcode.md5`(总 G-code 的 MD5 哈希)
### 更新元数据(默认:累加模式)
默认累加所有重复份的预测和耗材用量,修改 `Metadata/slice_info.config`
| 字段 | 修改方式 |
|------|----------|
| prediction | 所有重复份的总和 |
| weight | 所有重复份的总和 |
| filament.used_m | 所有重复份的总长度 |
| filament.used_g | 所有重复份的总重量 |
如果选了"保留原始预测和重量",此文件原样保留。
### 合成预览图(可选)
如果启用了预览:
1. 从最多 9 个**不同输入文件**中各取一张 `plate_N.png`(优先取活动面板的预览图)
2. 按网格拼接(1/2/4/6/9 宫格)
3. 在左下角用绿色粗体字盖一个标签,如 `5 P`
合成后的 PNG 覆盖 ZIP 中原来的 `plate_1.png``plate_1_small.png`
如果源文件中没有预览图 → 保持原样,仍然尝试盖标签。
### 压缩
**zlib-ng** 替换标准库的 `zipfile.zlib`,按你选的级别(1-9,默认 7)做 ZIP Deflate 压缩。
### 原子写入
整个写入过程分两步:
1. 先写到**临时文件**`temp.N.3mf`
2. 写入成功后 `shutil.move` 到目标路径
防止构建中断导致输出文件损坏。
---
## ⑦ 返回
```python
BuildResult(
output_3mf=Path(...), # 输出文件路径
plate_count=42, # 总板数
total_prediction_seconds=36000.0, # 总预估打印时间
total_weight_grams=150.0, # 总耗材重量
gcode_md5="a1b2c3..." # 总 G-code MD5
)
```
---
## 使用方式
核心构建函数只有一个:`build_packed_3mf(jobs, options)`,GUI 在调用它之前做不同的准备工作。
```mermaid
flowchart TB
subgraph GUI["GUI 入口: run_gui.py → gui.py"]
G1["拖放/添加文件 → 表格"] --> G2["每行: 3MF路径 + 份数"]
G2 --> G3["控件值 → BuildOptions"]
G3 --> G4{"批处理模式?"}
G4 -- 合并 --> BUILD["build_packed_3mf()"]
G4 -- 独立 --> G5["逐行调 build_packed_3mf()<br/>ProcessPoolExecutor 并行"]
end
```
### GUI
窗口操作,参数靠控件选、文件靠拖放。最终也是调 `build_packed_3mf()`,准备工作包括:
#### GUI 独有的准备阶段
1. **文件管理** — 表格里每行一个源文件 + 份数,可拖放、排序、移除
2. **元数据预读** — 添加文件时就解析 `slice_info.config`,表格直接显示时间和耗材
3. **缩略图预览** — 选中行 3MF → 右侧显示 `plate_N.png` 缩略图
4. **输出路径自动算** — 默认留空,自动写到第一个输入文件旁边。文件名按规则 `{plates} Plates - {sources}.3mf` 生成
5. **设置持久化** — 控件值自动存 `settings.json`,重启恢复
#### 合并模式(默认)
所有行合并调一次 `build_packed_3mf()`
```
A.3mf 份数 3
B.3mf 份数 2 → build_packed_3mf([A,A,A,B,B]) → 5 Plates - A_and_1_more.3mf
```
#### 独立批处理模式
每行独立构建,多输出并行:
```
A.3mf 份数 3 → build_packed_3mf([A]) → 3 Plates - A.3mf
B.3mf 份数 2 → build_packed_3mf([B]) → 2 Plates - B.3mf 并行(ProcessPoolExecutor,最多 8 个进程)
C.3mf 份数 5 → build_packed_3mf([C]) → 5 Plates - C.3mf
```
构建前:
- 每行自动算各自的输出路径(文件名规则 + 去重防覆盖)
- 弹出进度日志,记录每个文件的构建结果
- 有关闭全局预览标签的选项