"""将解压后的 3MF 文件夹重新打包为 .3mf 文件的工具脚本。 用法: python repack_3mf.py "解压目录" "output.3mf" 会自动更新 Metadata/plate_N.gcode.md5 并确保 [Content_Types].xml 为 ZIP 第一条目。 如果省略输出路径,自动在解压目录同级生成同名 .3mf。 """ from __future__ import annotations import hashlib import os import re import sys import zipfile from pathlib import Path GCODE_MEMBER_RE = re.compile(r"^Metadata[\\/]plate_(\d+)\.gcode$") def update_md5(src_dir: Path) -> None: """更新所有 plate_N.gcode 对应的 .md5 文件。""" metadata = src_dir / "Metadata" if not metadata.is_dir(): return for gcode_file in sorted(metadata.glob("plate_*.gcode")): if not GCODE_MEMBER_RE.match(str(gcode_file.relative_to(src_dir)).replace("\\", "/")): continue h = hashlib.md5() with open(gcode_file, "rb") as f: h.update(f.read()) md5_path = Path(str(gcode_file) + ".md5") md5_path.write_text(h.hexdigest(), encoding="ascii") print(f" MD5 已更新: {md5_path.name} → {h.hexdigest()}") def repack(src_dir: Path, output: Path) -> None: """将解压目录重新打包为 3MF。""" ct_path = src_dir / "[Content_Types].xml" if not ct_path.exists(): raise FileNotFoundError(f"未找到 [Content_Types].xml,请确认 {src_dir} 是有效的 3MF 解压目录") print(f"源目录: {src_dir}") update_md5(src_dir) with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as z: z.write(ct_path, "[Content_Types].xml") count = 1 for root, _dirs, files in os.walk(str(src_dir)): for fn in files: fp = Path(root) / fn arcname = fp.relative_to(src_dir).as_posix() if arcname == "[Content_Types].xml": continue z.write(str(fp), arcname) count += 1 size_mb = output.stat().st_size / (1024 * 1024) print(f"输出文件: {output} ({size_mb:.1f} MB, {count} 条目)") print("打包完成。") def main() -> int: if len(sys.argv) < 2: print(__doc__) return 1 src = Path(sys.argv[1]) if not src.is_dir(): print(f"错误: 目录不存在 — {src}") return 1 if len(sys.argv) >= 3: output = Path(sys.argv[2]) else: output = src.parent / (src.name.rstrip("/\\") + ".3mf") try: repack(src, output) except Exception as exc: print(f"错误: {exc}") return 1 return 0 if __name__ == "__main__": raise SystemExit(main())