feat(printer): 新增局域网发送3MF至A1打印机功能

This commit is contained in:
2026-07-28 14:45:11 +08:00
parent 51c105b8bb
commit f6b118293c
5 changed files with 126 additions and 0 deletions
+21
View File
@@ -19,6 +19,7 @@ from .core import (
list_swap_gcode_files,
)
from .paths import default_patch_config_path, default_swap_gcode_dir
from .printer import PrinterConfig, send_to_printer
def parse_item(values: list[str]) -> PlateJob:
@@ -90,6 +91,21 @@ def build_command(args: argparse.Namespace) -> int:
print(f"源文件打印时间:{int(result.total_prediction_seconds)}")
if result.total_weight_grams is not None:
print(f"源文件耗材重量:{result.total_weight_grams:.2f} g")
if args.send_to_printer:
config = PrinterConfig(
ip=args.printer_ip,
access_code=args.printer_access_code,
serial=args.printer_serial,
)
try:
send_to_printer(result.output_3mf, config)
print(f"已发送至打印机:{result.output_3mf}")
except ImportError:
print("错误:未安装 bambulabs_api,请执行 pip install bambulabs_api", file=sys.stderr)
return 1
except Exception as exc:
print(f"发送至打印机失败:{exc}", file=sys.stderr)
return 1
return 0
@@ -156,6 +172,11 @@ def create_parser() -> argparse.ArgumentParser:
build.add_argument("--zip-level", type=int, choices=range(1, 10), default=DEFAULT_ZIP_COMPRESS_LEVEL, metavar="1-9", help="输出 3MF 的 zlib-ng Deflate 压缩级别。默认值:7。")
build.add_argument("--no-preview-label", action="store_true", help="不重写预览图标签/合成图。")
build.add_argument("--no-gcode-patches", action="store_true", help=f"不应用来自 {default_patch_config_path()} 的可编辑补丁。")
# 发送至打印机
build.add_argument("--send-to-printer", action="store_true", help="构建后将 3MF 发送至打印机并开始打印。")
build.add_argument("--printer-ip", default="", help="打印机 IP 地址(配合 --send-to-printer 使用)。")
build.add_argument("--printer-access-code", default="", help="打印机访问码(配合 --send-to-printer 使用)。")
build.add_argument("--printer-serial", default="", help="打印机序列号(配合 --send-to-printer 使用)。")
# 绑定命令处理函数
build.set_defaults(func=build_command)
+61
View File
@@ -58,6 +58,7 @@ except ImportError as exc: # pragma: no cover
raise SystemExit("PySide6 is required to run the GUI. Install it with: pip install PySide6") from exc
from . import APP_NAME, APP_TITLE
from .printer import PrinterConfig, send_to_printer
from .batch import IndividualBuildTask, individual_batch_worker_count, run_individual_batch_builds
from .core import (
BuildOptions,
@@ -663,6 +664,35 @@ class MainWindow(QMainWindow):
# 输出预览
self.output_preview_label = QLabel("-")
output_layout.addRow("预览", self.output_preview_label)
# ---- 发送至打印机 ----
self.send_to_printer_check = QCheckBox("构建后发送至打印机")
self.send_to_printer_check.setChecked(False)
self.send_to_printer_check.setToolTip("通过局域网将生成的 3MF 发送到 A1 打印机并开始打印")
output_layout.addRow("打印机", 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("IP 地址")
self.printer_ip_edit.setMaximumWidth(140)
self.printer_access_code_edit = QLineEdit()
self.printer_access_code_edit.setPlaceholderText("访问码")
self.printer_access_code_edit.setMaximumWidth(120)
self.printer_serial_edit = QLineEdit()
self.printer_serial_edit.setPlaceholderText("序列号")
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(output_group)
# =====================================================================
@@ -723,6 +753,10 @@ 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)
@@ -742,6 +776,10 @@ class MainWindow(QMainWindow):
self.individual_batch_check.setChecked(bool(options.get("individual_batch_mode", False)))
self.clear_after_build_check.setChecked(bool(options.get("clear_after_build", False)))
self.skip_duplicates_check.setChecked(bool(options.get("skip_duplicates", True)))
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")
@@ -780,6 +818,10 @@ class MainWindow(QMainWindow):
"individual_batch_mode": self.individual_batch_check.isChecked(),
"clear_after_build": self.clear_after_build_check.isChecked(),
"skip_duplicates": self.skip_duplicates_check.isChecked(),
"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,
}
@@ -1343,6 +1385,21 @@ 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(f"已发送至打印机:{file_path.name}")
except ImportError:
self.log.append("错误:未安装 bambulabs_api,请执行 pip install bambulabs_api")
except Exception as exc:
self.log.append(f"发送至打印机失败:{exc}")
def build_output(self) -> None:
"""主构建入口:根据批处理模式分发到合并构建或独立构建。"""
jobs = self.collect_jobs()
@@ -1366,6 +1423,8 @@ 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("已创建打包的 3MF 文件。")
if self.clear_after_build_check.isChecked():
self.remove_all()
@@ -1395,6 +1454,8 @@ 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}")
+42
View File
@@ -0,0 +1,42 @@
"""打印机发送模块:通过局域网将 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 send_to_printer(file_path: Path, config: PrinterConfig) -> bool:
"""将 3MF 文件上传至打印机并开始打印。
Args:
file_path: 要发送的 3MF 文件路径
config: 打印机连接配置
Returns:
True 表示成功False 表示失败
Raises:
ImportError: bambulabs_api 未安装
Exception: 连接或上传失败
"""
# 延迟导入,避免在没有 bambulabs_api 时报错
from bambulabs_api import Printer as BambuPrinter
printer = BambuPrinter(config.ip, config.access_code, config.serial)
try:
printer.connect()
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)
return True
finally:
printer.disconnect()
Submodule code/bambulabs_api added at 5bd1e84a9b
+1
Submodule code/ha-bambulab added at 3402861215