refactor: 移除CLI模块,项目改为纯GUI桌面应用
This commit is contained in:
@@ -17,3 +17,4 @@ Thumbs.db
|
||||
/settings.json
|
||||
/reference
|
||||
codec.py/code/
|
||||
code
|
||||
|
||||
@@ -19,7 +19,7 @@ A1 Swap Mod Packer 是一个开源的 3MF 打包工具,专为 Bambu Lab A1 Swa
|
||||
## 主要功能
|
||||
|
||||
- 拖放式批量打包 GUI。
|
||||
- 可重复执行的自动化 CLI。
|
||||
- 简洁高效的桌面 GUI。
|
||||
- 支持多个 `.3mf` 输入文件。
|
||||
- 每个文件独立份数。
|
||||
- 自动从 `Metadata/slice_info.config` 读取并汇总时间和耗材。
|
||||
@@ -65,24 +65,6 @@ python -m a1_swap_mod_packer.gui
|
||||
python run_gui.py
|
||||
```
|
||||
|
||||
启动 CLI:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli --help
|
||||
```
|
||||
|
||||
或
|
||||
|
||||
```bash
|
||||
python run_cli.py
|
||||
```
|
||||
|
||||
查看版本:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli --version
|
||||
```
|
||||
|
||||
## 构建 Windows 可执行文件
|
||||
|
||||
仓库包含 Windows 构建脚本:
|
||||
@@ -102,7 +84,6 @@ build/onefile/
|
||||
```text
|
||||
build/onefile/
|
||||
a1packer.exe
|
||||
a1packer-cli.exe
|
||||
gcode_patches.ini
|
||||
swap_gcode/
|
||||
x.png 输出预览图示例
|
||||
@@ -145,10 +126,9 @@ py -3.13 -m venv .venv
|
||||
build_win.cmd
|
||||
```
|
||||
|
||||
构建脚本执行两次 Nuitka 编译:
|
||||
构建脚本执行一次 Nuitka 编译:
|
||||
|
||||
- GUI:`run_gui.py` → `build/onefile/a1packer.exe`
|
||||
- CLI:`run_cli.py` → `build/onefile/a1packer-cli.exe`
|
||||
|
||||
GUI 编译启用 PySide6 插件并包含当前界面所需的 Qt 插件组:
|
||||
|
||||
@@ -158,22 +138,7 @@ platforms,imageformats,styles,iconengines
|
||||
|
||||
### 构建后验证
|
||||
|
||||
发布目录前,先确认 CLI 能识别外部资源:
|
||||
|
||||
```cmd
|
||||
build\onefile\a1packer-cli.exe --version
|
||||
build\onefile\a1packer-cli.exe list-swap-gcode
|
||||
```
|
||||
|
||||
第二条命令应列出:
|
||||
|
||||
```text
|
||||
build/onefile/swap_gcode/
|
||||
```
|
||||
|
||||
中的文件。
|
||||
|
||||
同时启动一次 `build/onefile/a1packer.exe`,确认:
|
||||
启动一次 `build/onefile/a1packer.exe`,确认:
|
||||
|
||||
- 换盘 G-code 下拉框列出已复制的模板。
|
||||
- 修改 GUI 选项会创建或更新 `build/onefile/settings.json`。
|
||||
@@ -206,7 +171,7 @@ GUI 中使用方法:
|
||||
|
||||
## 可编辑 G-code 补丁
|
||||
|
||||
GUI 和 CLI 都读取固定补丁文件:
|
||||
GUI 读取固定补丁文件:
|
||||
|
||||
```text
|
||||
gcode_patches.ini
|
||||
@@ -375,12 +340,6 @@ gcode_patches.ini
|
||||
|
||||
如果预览图缺失或无法读取,打包器保留可用基础预览,仍尝试施加面板标签。
|
||||
|
||||
CLI 可通过以下选项禁用预览重写:
|
||||
|
||||
```bash
|
||||
--no-preview-label
|
||||
```
|
||||
|
||||
### 批处理模式
|
||||
|
||||
#### 合并模式
|
||||
@@ -506,112 +465,3 @@ settings.json
|
||||
- 构建成功后清空输入列表。
|
||||
- 输出目录。
|
||||
- 输出文件名规则。
|
||||
|
||||
## CLI 使用示例
|
||||
|
||||
列出可用的换盘 G-code 文件:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli list-swap-gcode
|
||||
```
|
||||
|
||||
单源文件 × 5 份:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
--item "SC05720_01.gcode(1).3mf" 5 \
|
||||
--swap-gcode "a1_swap.gcode" \
|
||||
--cool-bed 45 \
|
||||
--wait 45 \
|
||||
--show-plate-number \
|
||||
-o "5 Plates - SC05720_01.gcode(1).3mf"
|
||||
```
|
||||
|
||||
多源文件合并输出:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
--item "A.3mf" 2 \
|
||||
--item "B.3mf" 3 \
|
||||
--swap-gcode "a1_swap.gcode" \
|
||||
-o "5 Plates - A_and_B.3mf"
|
||||
```
|
||||
|
||||
使用累加元数据模式:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
--item "A.3mf" 5 \
|
||||
--swap-gcode "a1_swap.gcode" \
|
||||
--metadata-mode sum \
|
||||
-o "5 Plates - A.3mf"
|
||||
```
|
||||
|
||||
禁用 G-code 补丁:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
--item "A.3mf" 5 \
|
||||
--swap-gcode "a1_swap.gcode" \
|
||||
--no-gcode-patches \
|
||||
-o "5 Plates - A.3mf"
|
||||
```
|
||||
|
||||
位置参数输入 + 统一份数:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
"A.3mf" "B.3mf" \
|
||||
--copies 2 \
|
||||
--swap-gcode "a1_swap.gcode" \
|
||||
-o "4 Plates - A_and_B.3mf"
|
||||
```
|
||||
|
||||
指定换盘 G-code 目录和 ZIP 压缩级别:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
--item "A.3mf" 5 \
|
||||
--swap-gcode "a1_swap.gcode" \
|
||||
--swap-gcode-dir "D:\SwapMod\swap_gcode" \
|
||||
--zip-level 9 \
|
||||
-o "5 Plates - A.3mf"
|
||||
```
|
||||
|
||||
禁用最后换盘和预览标签重写:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
--item "A.3mf" 5 \
|
||||
--swap-gcode "a1_swap.gcode" \
|
||||
--no-swap-after-final \
|
||||
--no-preview-label \
|
||||
-o "5 Plates - A.3mf"
|
||||
```
|
||||
|
||||
禁用换盘后等待时间:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
--item "A.3mf" 5 \
|
||||
--swap-gcode "a1_swap.gcode" \
|
||||
--no-eject-wait \
|
||||
-o "5 Plates - A.3mf"
|
||||
```
|
||||
|
||||
CLI 常用选项速查:
|
||||
|
||||
| 选项 | 含义 |
|
||||
|---|---|
|
||||
| `--version` | 打印当前版本 |
|
||||
| `list-swap-gcode` | 列出换盘 G-code 目录中的文件 |
|
||||
| `--item PATH COPIES` | 添加一个带独立份数的输入;可多次使用 |
|
||||
| 位置参数 `inputs` + `--copies N` | 添加多个使用相同份数的输入 |
|
||||
| `--swap-gcode-dir DIR` | 使用自定义换盘 G-code 目录 |
|
||||
| `--no-bed-cooldown` | 不在换盘块前插入 `M190` |
|
||||
| `--no-swap-after-final` | 最后一块盘后不执行换盘块 |
|
||||
| `--no-eject-wait` | 不插入换盘后等待时间(`G4 P...` 行) |
|
||||
| `--line-ending lf\|crlf` | 选择生成 G-code 的换行符;默认 `crlf` |
|
||||
| `--zip-level 1-9` | zlib-ng Deflate 压缩级别;默认 `7` |
|
||||
| `--no-preview-label` | 不重写预览图标签/合成图 |
|
||||
| `--no-gcode-patches` | 不应用 `gcode_patches.ini` 中的规则 |
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
"""命令行接口模块:定义参数解析器和命令处理函数。
|
||||
|
||||
提供 `build`(构建打包 3MF)和 `list-swap-gcode`(列出换盘 G-code 文件)
|
||||
两个子命令,以及 `main` 入口函数。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from . import APP_NAME, APP_TITLE, __version__
|
||||
from .core import (
|
||||
BuildOptions,
|
||||
DEFAULT_ZIP_COMPRESS_LEVEL,
|
||||
PlateJob,
|
||||
build_packed_3mf,
|
||||
list_swap_gcode_files,
|
||||
)
|
||||
from .paths import default_patch_config_path, default_swap_gcode_dir
|
||||
from .printer import PrinterConfig, send_to_printer
|
||||
|
||||
|
||||
def parse_item(values: list[str]) -> PlateJob:
|
||||
"""解析 --item 参数的键值对,生成一个 PlateJob 实例。
|
||||
|
||||
Args:
|
||||
values: 包含两个字符串的列表 [路径, 份数]
|
||||
|
||||
Returns:
|
||||
PlateJob: 对应的板作业对象
|
||||
|
||||
Raises:
|
||||
argparse.ArgumentTypeError: 参数格式或类型不合法时抛出
|
||||
"""
|
||||
# --item 必须提供恰好两个值:路径和份数
|
||||
if len(values) != 2:
|
||||
raise argparse.ArgumentTypeError("每个 --item 需要一个路径和一个份数。")
|
||||
path = Path(values[0])
|
||||
# 份数必须可以解析为整数
|
||||
try:
|
||||
copies = int(values[1])
|
||||
except ValueError as exc:
|
||||
raise argparse.ArgumentTypeError(f"无效的份数:{values[1]}") from exc
|
||||
return PlateJob(path, copies)
|
||||
|
||||
|
||||
def build_command(args: argparse.Namespace) -> int:
|
||||
"""执行 'build' 子命令:收集输入、组装选项、调用构建。
|
||||
|
||||
Args:
|
||||
args: 解析后的命令行命名空间
|
||||
|
||||
Returns:
|
||||
int: 退出码,成功为 0
|
||||
"""
|
||||
jobs: list[PlateJob] = []
|
||||
# 处理 --item 参数(每个可独立指定份数)
|
||||
for item in args.item or []:
|
||||
jobs.append(parse_item(item))
|
||||
# 处理位置参数输入(共用 --copies 份数)
|
||||
for input_path in args.inputs or []:
|
||||
jobs.append(PlateJob(Path(input_path), args.copies))
|
||||
# 没有输入文件时直接报错退出
|
||||
if not jobs:
|
||||
raise SystemExit("未提供输入 3MF 文件。")
|
||||
# 根据 --no-bed-cooldown 决定是否写入床温指令
|
||||
cool_bed_temp = None if args.no_bed_cooldown else args.cool_bed
|
||||
options = BuildOptions(
|
||||
swap_gcode=args.swap_gcode,
|
||||
output_3mf=Path(args.output),
|
||||
cool_bed_temp=cool_bed_temp,
|
||||
wait_after_eject_seconds=0 if args.no_eject_wait else args.wait,
|
||||
show_plate_number=args.show_plate_number,
|
||||
swap_after_final=not args.no_swap_after_final,
|
||||
metadata_mode=args.metadata_mode,
|
||||
line_ending=args.line_ending,
|
||||
add_preview_label=not args.no_preview_label,
|
||||
apply_gcode_patches=not args.no_gcode_patches,
|
||||
swap_gcode_dir=Path(args.swap_gcode_dir) if args.swap_gcode_dir else None,
|
||||
zip_compress_level=args.zip_level,
|
||||
)
|
||||
# 调用核心构建函数
|
||||
result = build_packed_3mf(jobs, options)
|
||||
# 输出构建结果摘要
|
||||
print(f"输出:{result.output_3mf}")
|
||||
print(f"板数:{result.plate_count}")
|
||||
print(f"G-code MD5:{result.gcode_md5}")
|
||||
if result.total_prediction_seconds is not None:
|
||||
print(f"源文件打印时间:{int(result.total_prediction_seconds)} 秒")
|
||||
if result.total_weight_grams is not None:
|
||||
print(f"源文件耗材重量:{result.total_weight_grams:.2f} g")
|
||||
if args.send_to_printer:
|
||||
config = PrinterConfig(
|
||||
ip=args.printer_ip,
|
||||
access_code=args.printer_access_code,
|
||||
serial=args.printer_serial,
|
||||
)
|
||||
try:
|
||||
send_to_printer(result.output_3mf, config)
|
||||
print(f"已发送至打印机:{result.output_3mf}")
|
||||
except ImportError:
|
||||
print("错误:未安装 bambulabs_api,请执行 pip install bambulabs_api", file=sys.stderr)
|
||||
return 1
|
||||
except Exception as exc:
|
||||
print(f"发送至打印机失败:{exc}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def list_swap_gcode_command(args: argparse.Namespace) -> int:
|
||||
"""执行 'list-swap-gcode' 子命令:列出可用的换盘 G-code 文件。
|
||||
|
||||
Args:
|
||||
args: 解析后的命令行命名空间
|
||||
|
||||
Returns:
|
||||
int: 退出码,成功为 0
|
||||
"""
|
||||
# 解析目录:优先使用命令行参数,否则使用默认目录
|
||||
directory = Path(args.swap_gcode_dir) if args.swap_gcode_dir else default_swap_gcode_dir()
|
||||
files = list_swap_gcode_files(directory)
|
||||
# 目录为空时给出提示
|
||||
if not files:
|
||||
print(f"在 {directory} 中未找到换盘 G-code 文件")
|
||||
return 0
|
||||
for path in files:
|
||||
print(path.name)
|
||||
return 0
|
||||
|
||||
|
||||
def create_parser() -> argparse.ArgumentParser:
|
||||
"""创建并配置命令行参数解析器。
|
||||
|
||||
包含两个子命令:
|
||||
- build: 构建打包的 3MF 文件
|
||||
- list-swap-gcode: 列出换盘 G-code 文件
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: 配置完成的参数解析器
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="a1-swap-mod-packer",
|
||||
description=f"{APP_TITLE} - 将重复的 Bambu A1 SwapMod 面板打包为一个 3MF 作业。",
|
||||
)
|
||||
parser.add_argument("--version", action="version", version=f"{APP_NAME} {__version__}")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
# ---- build 子命令 ----
|
||||
build = subparsers.add_parser("build", help="构建一个打包的 3MF 文件。")
|
||||
# 位置参数:输入文件路径(共用统一的份数)
|
||||
build.add_argument("inputs", nargs="*", help="输入 3MF 文件。使用 --copies 为所有文件设置相同的份数。")
|
||||
# --item 参数:每个文件可独立指定份数
|
||||
build.add_argument("--item", nargs=2, action="append", metavar=("PATH", "COPIES"), help="添加一个带有独立份数的输入 3MF 文件。可重复使用。")
|
||||
build.add_argument("-o", "--output", required=True, help="输出 3MF 路径。")
|
||||
# 换盘 G-code 相关
|
||||
build.add_argument("--swap-gcode", required=True, help="换盘 G-code 文件名,位于 swap_gcode 中,或显式文件路径。")
|
||||
build.add_argument("--swap-gcode-dir", default=None, help=f"模板目录。默认值:{default_swap_gcode_dir()}")
|
||||
# 份数与温度
|
||||
build.add_argument("--copies", type=int, default=1, help="位置参数输入文件的复制份数。")
|
||||
build.add_argument("--cool-bed", type=int, default=45, help="执行换盘 G-code 前等待的床温。")
|
||||
build.add_argument("--no-bed-cooldown", action="store_true", help="不在换料代码前插入 M190。")
|
||||
# 时间与编号
|
||||
build.add_argument("--wait", type=int, default=45, help="板换盘后等待的秒数。")
|
||||
build.add_argument("--no-eject-wait", action="store_true", help="不插入换盘后等待指令 G4。")
|
||||
build.add_argument("--show-plate-number", action="store_true", help="每板号向 M73 R 值增加 100 小时。")
|
||||
# 行为开关
|
||||
build.add_argument("--no-swap-after-final", action="store_true", help="最后一块盘后不执行换盘 G-code。")
|
||||
build.add_argument("--metadata-mode", choices=("source", "sum"), default="sum", help="如何写入 slice_info 的 prediction 和 weight。")
|
||||
build.add_argument("--line-ending", choices=("lf", "crlf"), default="crlf", help="生成 G-code 的换行符。")
|
||||
build.add_argument("--zip-level", type=int, choices=range(1, 10), default=DEFAULT_ZIP_COMPRESS_LEVEL, metavar="1-9", help="输出 3MF 的 zlib-ng Deflate 压缩级别。默认值:7。")
|
||||
build.add_argument("--no-preview-label", action="store_true", help="不重写预览图标签/合成图。")
|
||||
build.add_argument("--no-gcode-patches", action="store_true", help=f"不应用来自 {default_patch_config_path()} 的可编辑补丁。")
|
||||
# 发送至打印机
|
||||
build.add_argument("--send-to-printer", action="store_true", help="构建后将 3MF 发送至打印机并开始打印。")
|
||||
build.add_argument("--printer-ip", default="", help="打印机 IP 地址(配合 --send-to-printer 使用)。")
|
||||
build.add_argument("--printer-access-code", default="", help="打印机访问码(配合 --send-to-printer 使用)。")
|
||||
build.add_argument("--printer-serial", default="", help="打印机序列号(配合 --send-to-printer 使用)。")
|
||||
# 绑定命令处理函数
|
||||
build.set_defaults(func=build_command)
|
||||
|
||||
# ---- list-swap-gcode 子命令 ----
|
||||
list_cmd = subparsers.add_parser("list-swap-gcode", help="列出 swap_gcode 目录中的文件。")
|
||||
list_cmd.add_argument("--swap-gcode-dir", default=None, help=f"模板目录。默认值:{default_swap_gcode_dir()}")
|
||||
list_cmd.set_defaults(func=list_swap_gcode_command)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
"""命令行入口函数:解析参数并分发到对应的子命令处理函数。
|
||||
|
||||
Args:
|
||||
argv: 命令行参数列表,为 None 时使用 sys.argv
|
||||
|
||||
Returns:
|
||||
int: 程序退出码,0 表示成功,1 表示失败
|
||||
"""
|
||||
# 构建解析器并解析参数
|
||||
parser = create_parser()
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
# 根据子命令调用绑定的处理函数
|
||||
return int(args.func(args))
|
||||
except Exception as exc:
|
||||
# 异常信息输出到 stderr,返回非零退出码
|
||||
print(f"错误:{exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -21,16 +21,6 @@ if errorlevel 1 exit /b %ERRORLEVEL%
|
||||
|
||||
echo.
|
||||
echo GUI build done.
|
||||
echo Next step is building CLI.
|
||||
|
||||
"%PY%" -m nuitka ^
|
||||
--mode=onefile ^
|
||||
--msvc=latest ^
|
||||
--output-dir="%DIST%" ^
|
||||
--output-filename=a1packer-cli.exe ^
|
||||
--remove-output ^
|
||||
run_cli.py
|
||||
if errorlevel 1 exit /b %ERRORLEVEL%
|
||||
|
||||
robocopy "swap_gcode" "%DIST%\swap_gcode" /E
|
||||
if %ERRORLEVEL% GEQ 8 exit /b %ERRORLEVEL%
|
||||
|
||||
+8
-52
@@ -151,7 +151,7 @@ G4 P45000 ; 等 45 秒(可选)
|
||||
|
||||
### 合成预览图(可选)
|
||||
|
||||
如果启用了预览(CLI 中默认开启):
|
||||
如果启用了预览:
|
||||
|
||||
1. 从最多 9 个**不同输入文件**中各取一张 `plate_N.png`(优先取活动面板的预览图)
|
||||
2. 按网格拼接(1/2/4/6/9 宫格)
|
||||
@@ -189,66 +189,36 @@ BuildResult(
|
||||
|
||||
---
|
||||
|
||||
## 两种使用方式
|
||||
## 使用方式
|
||||
|
||||
核心构建函数只有一个:`build_packed_3mf(jobs, options)`,CLI 和 GUI 会在调用它之前做不同的准备工作。
|
||||
核心构建函数只有一个:`build_packed_3mf(jobs, options)`,GUI 在调用它之前做不同的准备工作。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph CLI["CLI 入口: run_cli.py → cli.py"]
|
||||
C1["argparse 解析参数"] --> C2["收集 --item / 位置参数"]
|
||||
C2 --> C3["构造 BuildOptions"]
|
||||
C3 --> BUILD["build_packed_3mf()"]
|
||||
end
|
||||
subgraph GUI["GUI 入口: run_gui.py → gui.py"]
|
||||
G1["拖放/添加文件 → 表格"] --> G2["每行: 3MF路径 + 份数"]
|
||||
G2 --> G3["控件值 → BuildOptions"]
|
||||
G3 --> G4{"批处理模式?"}
|
||||
G4 -- 合并 --> BUILD
|
||||
G4 -- 合并 --> BUILD["build_packed_3mf()"]
|
||||
G4 -- 独立 --> G5["逐行调 build_packed_3mf()<br/>ProcessPoolExecutor 并行"]
|
||||
end
|
||||
```
|
||||
|
||||
### CLI
|
||||
|
||||
一条命令搞定。指定输入文件、份数、输出路径和各项参数:
|
||||
|
||||
```bash
|
||||
python -m a1_swap_mod_packer.cli build \
|
||||
--item "A.3mf" 3 \
|
||||
--swap-gcode "LX_1.02.26.gcode" \
|
||||
--cool-bed 45 \
|
||||
--wait 45 \
|
||||
--show-plate-number \
|
||||
-o "3 Plates - A.3mf"
|
||||
```
|
||||
|
||||
CLI 做了这些:
|
||||
|
||||
1. `argparse` 解析参数(文件、份数、换盘模板、冷却温度、等待秒数...)
|
||||
2. 拼成 `PlateJob` 列表和 `BuildOptions`
|
||||
3. 调一次 `build_packed_3mf()`(合并模式)
|
||||
4. 把结果打印到 stdout
|
||||
|
||||
新增 `--no-eject-wait` 选项可禁用换盘后等待时间(`G4 P...` 行),与 GUI 中的勾选框对应。
|
||||
|
||||
**所有输入合并成 1 个输出**。参数靠命令行传,没有交互、没有预览。
|
||||
|
||||
### GUI
|
||||
|
||||
窗口操作,参数靠控件选、文件靠拖放。最终也是调 `build_packed_3mf()`,但准备阶段和 CLI 不同:
|
||||
窗口操作,参数靠控件选、文件靠拖放。最终也是调 `build_packed_3mf()`,准备工作包括:
|
||||
|
||||
#### GUI 独有的准备阶段
|
||||
|
||||
1. **文件管理** — 表格里每行一个源文件 + 份数,可拖放、排序、移除
|
||||
2. **元数据预读** — 添加文件时就解析 `slice_info.config`,表格直接显示时间和耗材(CLI 不显示这个,只在构建后输出)
|
||||
2. **元数据预读** — 添加文件时就解析 `slice_info.config`,表格直接显示时间和耗材
|
||||
3. **缩略图预览** — 选中行 3MF → 右侧显示 `plate_N.png` 缩略图
|
||||
4. **输出路径自动算** — 默认留空,自动写到第一个输入文件旁边。文件名按规则 `{plates} Plates - {sources}.3mf` 生成(CLI 必须手动用 `-o`)
|
||||
4. **输出路径自动算** — 默认留空,自动写到第一个输入文件旁边。文件名按规则 `{plates} Plates - {sources}.3mf` 生成
|
||||
5. **设置持久化** — 控件值自动存 `settings.json`,重启恢复
|
||||
|
||||
#### 合并模式(默认)
|
||||
|
||||
和 CLI 一样,所有行合并调一次 `build_packed_3mf()`:
|
||||
所有行合并调一次 `build_packed_3mf()`:
|
||||
|
||||
```
|
||||
A.3mf 份数 3
|
||||
@@ -269,17 +239,3 @@ C.3mf 份数 5 → build_packed_3mf([C]) → 5 Plates - C.3mf
|
||||
- 每行自动算各自的输出路径(文件名规则 + 去重防覆盖)
|
||||
- 弹出进度日志,记录每个文件的构建结果
|
||||
- 有关闭全局预览标签的选项
|
||||
|
||||
#### GUI vs CLI 能力速查
|
||||
|
||||
| 能力 | CLI | GUI |
|
||||
|------|:---:|:---:|
|
||||
| 合并模式 | ✅ | ✅ |
|
||||
| 独立批处理 | ❌ | ✅ |
|
||||
| 拖放添加文件 | ❌ | ✅ |
|
||||
| 表格编辑份数 | ❌ | ✅(每行 SpinBox) |
|
||||
| 元数据预显示 | ❌ | ✅(表格直接看时间/耗材) |
|
||||
| 缩略图预览 | ❌ | ✅ |
|
||||
| 输出路径自动生成 | ❌ | ✅(文件名规则) |
|
||||
| 设置持久化 | ❌ | ✅(settings.json) |
|
||||
| 脚本/自动化友好 | ✅ | ❌ |
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from a1_swap_mod_packer.cli import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+1
-19
@@ -2,39 +2,21 @@
|
||||
|
||||
验证内容:
|
||||
- APP_TITLE 使用统一的 __version__ 构建
|
||||
- CLI 的 --help 和 --version 输出均使用共享的版本号
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import unittest
|
||||
|
||||
from a1_swap_mod_packer import APP_NAME, APP_TITLE, __version__
|
||||
from a1_swap_mod_packer.cli import create_parser
|
||||
|
||||
|
||||
class VersionDisplayTest(unittest.TestCase):
|
||||
"""测试版本信息在应用标题和 CLI 中的一致性。"""
|
||||
"""测试版本信息的一致性。"""
|
||||
|
||||
def test_app_title_uses_shared_version(self) -> None:
|
||||
"""验证 APP_TITLE 由 APP_NAME 与 __version__ 拼接而成。"""
|
||||
self.assertEqual(APP_TITLE, f"{APP_NAME} v{__version__}")
|
||||
|
||||
def test_cli_help_and_version_use_shared_version(self) -> None:
|
||||
"""验证 CLI 帮助信息和 --version 输出中均包含统一版本号。"""
|
||||
parser = create_parser()
|
||||
# 帮助信息应包含 APP_TITLE
|
||||
self.assertIn(APP_TITLE, parser.format_help())
|
||||
|
||||
stdout = io.StringIO()
|
||||
# --version 参数触发 SystemExit(0) 并打印版本信息
|
||||
with self.assertRaises(SystemExit) as caught, contextlib.redirect_stdout(stdout):
|
||||
parser.parse_args(["--version"])
|
||||
|
||||
self.assertEqual(caught.exception.code, 0) # 正常退出码
|
||||
self.assertEqual(stdout.getvalue().strip(), f"{APP_NAME} {__version__}") # 版本信息匹配
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user