feat: 新增repack_3mf脚本,解压目录重新打包为3MF文件
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
"""将解压后的 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())
|
||||||
Reference in New Issue
Block a user