feat(printer): 新增局域网发送3MF至A1打印机功能
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
+1
Submodule code/bambulabs_api added at 5bd1e84a9b
Submodule
+1
Submodule code/ha-bambulab added at 3402861215
Reference in New Issue
Block a user