Files
A1SwapModPacker/a1_swap_mod_packer/cli.py
T
yw1573 c5849af14e feat(gui,cli): 界面全面中文化并优化打包选项为紧凑网格布局
- gui.py: 全部用户可见文本直接写中文,选项区从 QFormLayout 改为 QGridLayout 两列布局,缩小窗口默认高度至720px,收紧控件间距
- cli.py: argparse 帮助文本和输出消息全部中文化
- docs: 新增 A1 SwapMod 换板 G-code 内容分析文档,按三类策略(基础/双轮抖板/安全换板)对比5个模板差异
- swap_gcode: 新增 LX_0.01.01 基础弹板模板
2026-07-27 11:22:40 +08:00

119 lines
5.6 KiB
Python
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.
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
def parse_item(values: list[str]) -> PlateJob:
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:
jobs: list[PlateJob] = []
for item in args.item or []:
jobs.append(parse_item(item))
for input_path in args.inputs or []:
jobs.append(PlateJob(Path(input_path), args.copies))
if not jobs:
raise SystemExit("未提供输入 3MF 文件。")
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=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")
return 0
def list_swap_gcode_command(args: argparse.Namespace) -> int:
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:
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 = subparsers.add_parser("build", help="构建一个打包的 3MF 文件。")
build.add_argument("inputs", nargs="*", help="输入 3MF 文件。使用 --copies 为所有文件设置相同的份数。")
build.add_argument("--item", nargs=2, action="append", metavar=("PATH", "COPIES"), help="添加一个带有独立份数的输入 3MF 文件。可重复使用。")
build.add_argument("-o", "--output", required=True, help="输出 3MF 路径。")
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("--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="source", 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.set_defaults(func=build_command)
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:
parser = create_parser()
args = parser.parse_args(argv)
try:
return int(args.func(args))
except Exception as exc:
print(f"错误:{exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())