diff --git a/a1_swap_mod_packer/gui.py b/a1_swap_mod_packer/gui.py index ab564d6..9c89a73 100644 --- a/a1_swap_mod_packer/gui.py +++ b/a1_swap_mod_packer/gui.py @@ -499,6 +499,10 @@ class MainWindow(QMainWindow): self.skip_duplicates_check = QCheckBox(t("gui.options.skip_duplicates")) self.skip_duplicates_check.setChecked(True) + # 重新打包解压的 3MF + self.repack_3mf_button = QPushButton(t("gui.options.repack_3mf")) + self.repack_3mf_button.clicked.connect(self.repack_3mf_folder) + # 标签列固定宽度(左对齐),控件列自动对齐(stretch=1 平分空间) grid.setColumnMinimumWidth(0, 120) grid.setColumnMinimumWidth(2, 90) @@ -552,6 +556,9 @@ class MainWindow(QMainWindow): # 第 6 行:构建成功后清空输入列表(独立一行) grid.addWidget(self.clear_after_build_check, 6, 1, 1, 3) + # 第 7 行:重新打包 3MF + grid.addWidget(self.repack_3mf_button, 7, 1, 1, 3) + root.addWidget(self.options_group) # ===================================================================== @@ -809,6 +816,57 @@ class MainWindow(QMainWindow): return self.open_path(path) + def repack_3mf_folder(self) -> None: + """选择解压后的3MF文件夹,自动更新MD5并重新打包为.3mf""" + import hashlib + import re + import os + import zipfile + + folder = QFileDialog.getExistingDirectory(self, t("gui.dialog.repack_select_folder")) + if not folder: + return + + src_dir = Path(folder) + ct_path = src_dir / "[Content_Types].xml" + if not ct_path.exists(): + QMessageBox.warning(self, t("gui.dialog.warning.title"), t("gui.dialog.repack_no_content_types")) + return + + try: + # 更新所有 plate_N.gcode.md5 + gcode_re = re.compile(r"^(.+[/\\])?Metadata[/\\]plate_(\d+)\.gcode$") + metadata_dir = src_dir / "Metadata" + if metadata_dir.is_dir(): + for gcode_file in sorted(metadata_dir.glob("plate_*.gcode")): + if not gcode_re.match(str(gcode_file)): + 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") + + # 打包:[Content_Types].xml 必须是第一个条目 + output = src_dir.parent / (src_dir.name.rstrip("/\\") + ".3mf") + with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as z: + z.write(ct_path, "[Content_Types].xml") + 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) + + QMessageBox.information( + self, + t("gui.dialog.repack_success.title"), + t("gui.dialog.repack_success.text", path=str(output)), + ) + except Exception as exc: + QMessageBox.critical(self, t("gui.dialog.warning.title"), str(exc)) + # ========================================================================= # 文件输入管理(表格行操作) # ========================================================================= @@ -1421,6 +1479,7 @@ class MainWindow(QMainWindow): self.skip_duplicates_check.setText(t("gui.options.skip_duplicates")) self.bed_cooldown_suffix_label.setText(t("gui.options.bed_cooldown.suffix")) self.wait_suffix_label.setText(t("gui.options.wait_suffix")) + self.repack_3mf_button.setText(t("gui.options.repack_3mf")) # 元数据下拉框 self.metadata_combo.setItemText(0, t("gui.options.metadata.source")) diff --git a/a1_swap_mod_packer/i18n/en.json b/a1_swap_mod_packer/i18n/en.json index ecff6fb..72da061 100644 --- a/a1_swap_mod_packer/i18n/en.json +++ b/a1_swap_mod_packer/i18n/en.json @@ -95,5 +95,10 @@ "gui.button.close": "Close", "gui.dialog.view_gcode.title": "View Swap G-code", "gui.options.view_gcode": "View", - "gui.dialog.build_result.title": "Build Result" + "gui.dialog.build_result.title": "Build Result", + "gui.options.repack_3mf": "Repack 3MF", + "gui.dialog.repack_select_folder": "Select unpacked 3MF folder", + "gui.dialog.repack_success.title": "Repack Complete", + "gui.dialog.repack_success.text": "3MF has been repacked:\n{path}", + "gui.dialog.repack_no_content_types": "[Content_Types].xml not found. Not a valid unpacked 3MF directory." } \ No newline at end of file diff --git a/a1_swap_mod_packer/i18n/zh_CN.json b/a1_swap_mod_packer/i18n/zh_CN.json index 8256ccf..8c969f6 100644 --- a/a1_swap_mod_packer/i18n/zh_CN.json +++ b/a1_swap_mod_packer/i18n/zh_CN.json @@ -95,5 +95,10 @@ "format.unknown": "未知", "gui.button.close": "关闭", "gui.dialog.view_gcode.title": "查看换盘 G-code", - "gui.dialog.build_result.title": "构建结果" + "gui.dialog.build_result.title": "构建结果", + "gui.options.repack_3mf": "重新打包 3MF", + "gui.dialog.repack_select_folder": "选择解压后的 3MF 文件夹", + "gui.dialog.repack_success.title": "重新打包完成", + "gui.dialog.repack_success.text": "已重新打包为 3MF 文件:\n{path}", + "gui.dialog.repack_no_content_types": "未找到 [Content_Types].xml,不是有效的 3MF 解压目录。" } \ No newline at end of file