Files
A1SwapModPacker/docs/how_it_works.md
T

7.1 KiB
Raw Blame History

A1 Swap Mod Packer 工作原理

版本: v0.6.0 | 日期: 2026-07-27

总览

给一个源 .3mf 文件,设好份数和换盘 G-code,输出一个打包好的新 .3mf

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 文本,只做一遍以下处理,结果按 (源文件路径, 面板成员名) 键缓存:

换行统一

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========= 这一行之前插入换盘代码块:

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.pngplate_1_small.png

如果源文件中没有预览图 → 保持原样,仍然尝试盖标签。

压缩

zlib-ng 替换标准库的 zipfile.zlib,按你选的级别(1-9,默认 7)做 ZIP Deflate 压缩。

原子写入

整个写入过程分两步:

  1. 先写到临时文件temp.N.3mf
  2. 写入成功后 shutil.move 到目标路径

防止构建中断导致输出文件损坏。


⑦ 返回

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 在调用它之前做不同的准备工作。

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

构建前:

  • 每行自动算各自的输出路径(文件名规则 + 去重防覆盖)
  • 弹出进度日志,记录每个文件的构建结果
  • 有关闭全局预览标签的选项