diff --git a/a1_swap_mod_packer/i18n/en.json b/a1_swap_mod_packer/i18n/en.json index c57ccae..9dcfd45 100644 --- a/a1_swap_mod_packer/i18n/en.json +++ b/a1_swap_mod_packer/i18n/en.json @@ -75,7 +75,7 @@ "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", + "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.", diff --git a/a1_swap_mod_packer/i18n/zh_CN.json b/a1_swap_mod_packer/i18n/zh_CN.json index 5157076..76864f9 100644 --- a/a1_swap_mod_packer/i18n/zh_CN.json +++ b/a1_swap_mod_packer/i18n/zh_CN.json @@ -75,7 +75,7 @@ "gui.output.printer.ip": "IP 地址", "gui.output.printer.access_code": "访问码", "gui.output.printer.serial": "序列号", - "gui.output.printer.send.tooltip": "通过局域网将生成的 3MF 发送到 A1 打印机并开始打印", + "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 中。", diff --git a/a1_swap_mod_packer/printer.py b/a1_swap_mod_packer/printer.py index 08fc4f7..847d117 100644 --- a/a1_swap_mod_packer/printer.py +++ b/a1_swap_mod_packer/printer.py @@ -4,6 +4,7 @@ from __future__ import annotations from pathlib import Path from dataclasses import dataclass + @dataclass class PrinterConfig: """打印机连接配置。""" @@ -11,32 +12,94 @@ class PrinterConfig: 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 文件上传至打印机并开始打印。 + """将 3MF 文件上传至 A1 打印机并通过局域网开始打印。 + + 发送前会检查打印机状态;正忙则拒绝发送。 + ⚠️ 仅支持本地局域网,电脑与打印机必须在同一网络下。 Args: file_path: 要发送的 3MF 文件路径 config: 打印机连接配置 Returns: - True 表示成功,False 表示失败 + True 表示成功 Raises: ImportError: bambulabs_api 未安装 - Exception: 连接或上传失败 + RuntimeError: 打印机正忙或上传/打印失败 """ - # 延迟导入,避免在没有 bambulabs_api 时报错 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}") - printer.start_print(file_name, plate_number=1) + + # 使用 A1 专用的 MQTT 指令开始打印,而非内置的 start_print + _start_print_for_a1(printer, file_name, plate_number=1) return True finally: printer.disconnect()