diff --git a/a1_swap_mod_packer/gui.py b/a1_swap_mod_packer/gui.py index 67ba492..c1cdea5 100644 --- a/a1_swap_mod_packer/gui.py +++ b/a1_swap_mod_packer/gui.py @@ -59,7 +59,6 @@ except ImportError as exc: # pragma: no cover from . import APP_NAME, APP_TITLE from .i18n import t, switch as i18n_switch, current_locale -from .printer import PrinterConfig, send_to_printer from .batch import IndividualBuildTask, individual_batch_worker_count, run_individual_batch_builds from .core import ( BuildOptions, @@ -689,35 +688,6 @@ class MainWindow(QMainWindow): self.output_preview_label_title = QLabel(t("gui.output.preview")) output_layout.addRow(self.output_preview_label_title, self.output_preview_label) - # ---- 发送至打印机 ---- - self.send_to_printer_check = QCheckBox(t("gui.output.printer.send")) - self.send_to_printer_check.setChecked(False) - self.send_to_printer_check.setToolTip(t("gui.output.printer.send.tooltip")) - self.printer_label = QLabel(t("gui.output.printer.send")) - output_layout.addRow(self.printer_label, self.send_to_printer_check) - - # 打印机配置行(IP、访问码、序列号) - printer_config_widget = QWidget() - printer_config_layout = QHBoxLayout(printer_config_widget) - printer_config_layout.setContentsMargins(0, 0, 0, 0) - printer_config_layout.setSpacing(6) - - self.printer_ip_edit = QLineEdit() - self.printer_ip_edit.setPlaceholderText(t("gui.output.printer.ip")) - self.printer_ip_edit.setMaximumWidth(140) - self.printer_access_code_edit = QLineEdit() - self.printer_access_code_edit.setPlaceholderText(t("gui.output.printer.access_code")) - self.printer_access_code_edit.setMaximumWidth(120) - self.printer_serial_edit = QLineEdit() - self.printer_serial_edit.setPlaceholderText(t("gui.output.printer.serial")) - self.printer_serial_edit.setMaximumWidth(180) - - printer_config_layout.addWidget(self.printer_ip_edit) - printer_config_layout.addWidget(self.printer_access_code_edit) - printer_config_layout.addWidget(self.printer_serial_edit) - printer_config_layout.addStretch(1) - output_layout.addRow("", printer_config_widget) - root.addWidget(self.output_group) # ===================================================================== @@ -778,10 +748,6 @@ class MainWindow(QMainWindow): self.individual_batch_check.stateChanged.connect(self.on_individual_batch_toggled) self.clear_after_build_check.stateChanged.connect(self.save_current_settings) self.skip_duplicates_check.stateChanged.connect(self.save_current_settings) - self.send_to_printer_check.stateChanged.connect(self.save_current_settings) - self.printer_ip_edit.textChanged.connect(self.save_current_settings) - self.printer_access_code_edit.textChanged.connect(self.save_current_settings) - self.printer_serial_edit.textChanged.connect(self.save_current_settings) self.output_dir_edit.textChanged.connect(self.on_output_rule_changed) self.output_name_edit.textChanged.connect(self.on_output_rule_changed) self.language_combo.currentIndexChanged.connect(self.on_language_changed) @@ -806,10 +772,6 @@ class MainWindow(QMainWindow): locale_index = self.language_combo.findData(saved_locale) if locale_index >= 0: self.language_combo.setCurrentIndex(locale_index) - self.send_to_printer_check.setChecked(bool(options.get("send_to_printer_enabled", False))) - self.printer_ip_edit.setText(str(options.get("printer_ip", ""))) - self.printer_access_code_edit.setText(str(options.get("printer_access_code", ""))) - self.printer_serial_edit.setText(str(options.get("printer_serial", ""))) self.output_dir_edit.setText(str(options.get("output_directory", ""))) self.output_name_edit.setText(str(options.get("output_filename_rule", DEFAULT_OUTPUT_PATTERN))) metadata_mode = options.get("metadata_mode", "sum") @@ -849,10 +811,6 @@ class MainWindow(QMainWindow): "clear_after_build": self.clear_after_build_check.isChecked(), "skip_duplicates": self.skip_duplicates_check.isChecked(), "locale": self.language_combo.currentData() or "zh_CN", - "send_to_printer_enabled": self.send_to_printer_check.isChecked(), - "printer_ip": self.printer_ip_edit.text().strip(), - "printer_access_code": self.printer_access_code_edit.text().strip(), - "printer_serial": self.printer_serial_edit.text().strip(), "output_directory": self.output_dir_edit.text().strip(), "output_filename_rule": self.output_name_edit.text().strip() or DEFAULT_OUTPUT_PATTERN, } @@ -1402,21 +1360,6 @@ class MainWindow(QMainWindow): """显示构建成功的浮动提示条。""" self.success_toast.show_message(message) - def _send_to_printer(self, file_path: Path) -> None: - """将构建好的 3MF 文件发送至打印机。""" - config = PrinterConfig( - ip=self.printer_ip_edit.text().strip(), - access_code=self.printer_access_code_edit.text().strip(), - serial=self.printer_serial_edit.text().strip(), - ) - try: - send_to_printer(file_path, config) - self.log.append(t("gui.toast.send_success", name=file_path.name)) - except ImportError: - self.log.append(t("gui.log.printer_missing")) - except Exception as exc: - self.log.append(t("gui.toast.send_failed", error=str(exc))) - def build_output(self) -> None: """主构建入口:根据批处理模式分发到合并构建或独立构建。""" jobs = self.collect_jobs() @@ -1440,8 +1383,6 @@ class MainWindow(QMainWindow): options = self.build_options_for_output(output_path) result = build_packed_3mf(jobs, options) self.log_build_result(result) - if self.send_to_printer_check.isChecked(): - self._send_to_printer(result.output_3mf) self.show_success_toast(t("gui.toast.combined_success")) if self.clear_after_build_check.isChecked(): self.remove_all() @@ -1471,8 +1412,6 @@ class MainWindow(QMainWindow): batch_result = run_individual_batch_builds(tasks, max_workers=worker_count) for result in batch_result.results: self.log_build_result(result) - if self.send_to_printer_check.isChecked(): - self._send_to_printer(result.output_3mf) success_count = len(batch_result.results) for failure in batch_result.failures: errors.append(f"{failure.job.source_3mf.name}: {failure.error}") @@ -1589,12 +1528,6 @@ class MainWindow(QMainWindow): self.output_dir_label.setText(t("gui.output.dir.label")) self.output_filename_rule_label.setText(t("gui.output.filename_rule")) self.output_preview_label_title.setText(t("gui.output.preview")) - self.printer_label.setText(t("gui.output.printer.send")) - self.send_to_printer_check.setText(t("gui.output.printer.send")) - self.send_to_printer_check.setToolTip(t("gui.output.printer.send.tooltip")) - self.printer_ip_edit.setPlaceholderText(t("gui.output.printer.ip")) - self.printer_access_code_edit.setPlaceholderText(t("gui.output.printer.access_code")) - self.printer_serial_edit.setPlaceholderText(t("gui.output.printer.serial")) # 语言标签 self.language_label.setText(t("gui.options.language") + ":") diff --git a/a1_swap_mod_packer/i18n/en.json b/a1_swap_mod_packer/i18n/en.json index 9dcfd45..dee5998 100644 --- a/a1_swap_mod_packer/i18n/en.json +++ b/a1_swap_mod_packer/i18n/en.json @@ -71,11 +71,6 @@ "gui.output.filename_rule.help.tooltip": "Show filename token help", "gui.output.preview": "Preview", "gui.output.batch_preview": "{first} | {count} output file(s)", - "gui.output.printer.send": "Send to printer after build", - "gui.output.printer.ip": "IP Address", - "gui.output.printer.access_code": "Access Code", - "gui.output.printer.serial": "Serial", - "gui.output.printer.send.tooltip": "Send the generated 3MF to an A1 printer over LAN and start printing (LAN only — computer and printer must be on the same local network)", "gui.dialog.individual_batch.title": "Individual Batch Mode", "gui.dialog.individual_batch.text": "Individual batch mode treats every input row as a separate build.\n\nExample: if you add 20 single-plate 3MF files and set copies to 5, Build 3MF will create 20 separate packed files. Each output contains only that source file repeated 5 times.\n\nThis is useful for quickly batch-converting many independent 3MF jobs into multi-copy SwapMod packs. It does not combine all input files into one 3MF.", @@ -93,8 +88,6 @@ "gui.toast.combined_success": "The packed 3MF file was created.", "gui.toast.batch_success": "Created {count} packed 3MF file(s).", - "gui.toast.send_success": "Sent to printer: {name}", - "gui.toast.send_failed": "Failed to send to printer: {error}", "gui.log.added_files": "Added {added} input file(s). Skipped {skipped}.", "gui.log.skipped_file": "Skipped {path}: {reason}", @@ -106,7 +99,6 @@ "gui.log.filament": "Estimated source filament: {filament}", "gui.log.md5": "G-code MD5: {md5}", "gui.log.build_error": "Error building {path}: {error}", - "gui.log.printer_missing": "Error: bambulabs_api not installed, run: pip install bambulabs_api", "format.unknown": "Unknown" } diff --git a/a1_swap_mod_packer/i18n/zh_CN.json b/a1_swap_mod_packer/i18n/zh_CN.json index 76864f9..57969bc 100644 --- a/a1_swap_mod_packer/i18n/zh_CN.json +++ b/a1_swap_mod_packer/i18n/zh_CN.json @@ -71,11 +71,6 @@ "gui.output.filename_rule.help.tooltip": "显示文件名标记帮助", "gui.output.preview": "预览", "gui.output.batch_preview": "{first} | {count} 个输出文件", - "gui.output.printer.send": "构建后发送至打印机", - "gui.output.printer.ip": "IP 地址", - "gui.output.printer.access_code": "访问码", - "gui.output.printer.serial": "序列号", - "gui.output.printer.send.tooltip": "通过局域网将生成的 3MF 发送到 A1 打印机并开始打印(仅支持本地局域网,电脑与打印机需在同一网络下)", "gui.dialog.individual_batch.title": "独立批处理模式", "gui.dialog.individual_batch.text": "独立批处理模式将每一行输入作为独立的构建任务。\n\n示例:如果你添加了 20 个单板 3MF 文件并设置份数为 5,点击\"构建 3MF\"将创建 20 个独立的打包文件。每个输出文件仅包含该源文件重复 5 次的内容。\n\n这个功能适用于快速将大量独立的 3MF 作业批量转换为多份数的 SwapMod 包。它不会将所有输入文件合并到一个 3MF 中。", @@ -93,8 +88,6 @@ "gui.toast.combined_success": "已创建打包的 3MF 文件。", "gui.toast.batch_success": "已创建 {count} 个打包的 3MF 文件。", - "gui.toast.send_success": "已发送至打印机:{name}", - "gui.toast.send_failed": "发送至打印机失败:{error}", "gui.log.added_files": "已添加 {added} 个输入文件。跳过 {skipped} 个。", "gui.log.skipped_file": "已跳过 {path}:{reason}", @@ -106,7 +99,6 @@ "gui.log.filament": "预估源文件耗材:{filament}", "gui.log.md5": "G-code MD5:{md5}", "gui.log.build_error": "构建 {path} 时出错:{error}", - "gui.log.printer_missing": "错误:未安装 bambulabs_api,请执行 pip install bambulabs_api", "format.unknown": "未知" } diff --git a/a1_swap_mod_packer/printer.py b/a1_swap_mod_packer/printer.py deleted file mode 100644 index 847d117..0000000 --- a/a1_swap_mod_packer/printer.py +++ /dev/null @@ -1,105 +0,0 @@ -"""打印机发送模块:通过局域网将 3MF 发送到 Bambu A1 并开始打印。""" - -from __future__ import annotations -from pathlib import Path -from dataclasses import dataclass - - -@dataclass -class PrinterConfig: - """打印机连接配置。""" - ip: str = "" # 打印机 IP 地址 - access_code: str = "" # 局域网访问码 - serial: str = "" # 打印机序列号 - - -def check_printer_ready(config: PrinterConfig) -> bool: - """检查打印机是否空闲可接收新任务。 - - 返回 True 表示打印机空闲,False 表示正忙或离线。 - """ - from bambulabs_api import Printer as BambuPrinter - from bambulabs_api.states_info import GcodeState - printer = BambuPrinter(config.ip, config.access_code, config.serial) - try: - printer.connect() - state = printer.get_state() - return state == GcodeState.IDLE - except Exception: - return False - finally: - printer.disconnect() - - -def _start_print_for_a1(printer, file_name: str, plate_number: int = 1) -> None: - """发送打印机开始打印的 MQTT 指令,使用 A1 专用的 sdcard URL 格式。 - - bambulabs_api 的 start_print 方法硬编码了 ftp:/// 前缀, - 这对 A1 不生效。A1 需要 file:///sdcard/ 前缀。 - 本函数绕过 start_print,直接构造正确的 MQTT 载荷。 - """ - plate_location = f"Metadata/plate_{int(plate_number)}.gcode" - payload = { - "print": { - "command": "project_file", - "param": plate_location, - "file": file_name, - "bed_leveling": True, - "bed_type": "textured_plate", - "flow_cali": True, - "vibration_cali": True, - "url": f"file:///sdcard/{file_name}", - "layer_inspect": False, - "sequence_id": "10000000", - "use_ams": True, - "ams_mapping": [0], - } - } - # 调用名称改写后的私有方法发送原始 JSON 荷载 - printer.mqtt_client._PrinterMQTTClient__publish_command(payload) - - -def send_to_printer(file_path: Path, config: PrinterConfig) -> bool: - """将 3MF 文件上传至 A1 打印机并通过局域网开始打印。 - - 发送前会检查打印机状态;正忙则拒绝发送。 - ⚠️ 仅支持本地局域网,电脑与打印机必须在同一网络下。 - - Args: - file_path: 要发送的 3MF 文件路径 - config: 打印机连接配置 - - Returns: - True 表示成功 - - Raises: - ImportError: bambulabs_api 未安装 - RuntimeError: 打印机正忙或上传/打印失败 - """ - from bambulabs_api import Printer as BambuPrinter - from bambulabs_api.states_info import GcodeState - - printer = BambuPrinter(config.ip, config.access_code, config.serial) - try: - printer.connect() - - # 检查打印机是否空闲 - state = printer.get_state() - if state != GcodeState.IDLE: - raise RuntimeError( - f"打印机当前状态为 {state.value}," - f"无法开始新任务。请等待当前打印完成。" - ) - - file_name = file_path.name - with open(file_path, "rb") as f: - import io - result = printer.upload_file(io.BytesIO(f.read()), file_name) - if "226" not in result: - raise RuntimeError(f"上传文件失败: {result}") - - # 使用 A1 专用的 MQTT 指令开始打印,而非内置的 start_print - _start_print_for_a1(printer, file_name, plate_number=1) - return True - finally: - printer.disconnect()