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())