Add export mapping scheme to image feature
- Реализован метод export_to_image в canvas.py для рендеринга всей сцены маппинга (с учетом кастомного фона, сетки и точек) в файл изображения с полями и масштабированием. - Добавлено действие export_image (Экспортировать в изображение...) в меню Файл с хоткеем Ctrl+Shift+E и вызовом диалога сохранения файла. - Добавлен интеграционный тест test_export_mapping_to_image в qa_smoke.py для проверки экспорта схемы в PNG.
This commit is contained in:
parent
91189d6d37
commit
31b9028fc2
39
canvas.py
39
canvas.py
@ -4,7 +4,7 @@ import math
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QPointF, QRectF, QSize, Qt, Signal
|
||||
from PySide6.QtGui import QColor, QBrush, QImageReader, QPainter, QPainterPath, QPen, QPixmap, QTransform
|
||||
from PySide6.QtGui import QColor, QBrush, QImage, QImageReader, QPainter, QPainterPath, QPen, QPixmap, QTransform
|
||||
from PySide6.QtWidgets import (
|
||||
QGraphicsEllipseItem,
|
||||
QGraphicsItem,
|
||||
@ -1489,3 +1489,40 @@ class LedCanvas(QGraphicsView):
|
||||
@staticmethod
|
||||
def _move_scrollbar(scrollbar: QScrollBar, delta: int) -> None:
|
||||
scrollbar.setValue(scrollbar.value() - delta)
|
||||
|
||||
def export_to_image(self, file_path: str) -> bool:
|
||||
scene = self.scene()
|
||||
if scene is None:
|
||||
return False
|
||||
|
||||
rect = scene.itemsBoundingRect()
|
||||
if rect.isEmpty():
|
||||
return False
|
||||
|
||||
padding = max(rect.width(), rect.height()) * 0.05
|
||||
rect.adjust(-padding, -padding, padding, padding)
|
||||
|
||||
target_width = 2048
|
||||
aspect_ratio = rect.height() / rect.width()
|
||||
target_height = int(target_width * aspect_ratio)
|
||||
|
||||
if target_height > 4096:
|
||||
target_height = 4096
|
||||
target_width = int(target_height / aspect_ratio)
|
||||
|
||||
image = QImage(target_width, target_height, QImage.Format.Format_ARGB32)
|
||||
|
||||
bg_brush = scene.backgroundBrush()
|
||||
if bg_brush.style() != Qt.BrushStyle.NoBrush:
|
||||
image.fill(bg_brush.color())
|
||||
else:
|
||||
image.fill(QColor("#2b2b2b" if self._theme == "dark" else "#ffffff"))
|
||||
|
||||
painter = QPainter(image)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
|
||||
|
||||
scene.render(painter, QRectF(0, 0, target_width, target_height), rect)
|
||||
painter.end()
|
||||
|
||||
return image.save(file_path)
|
||||
|
||||
@ -337,6 +337,14 @@ class MainWindow(QMainWindow):
|
||||
add_action("open", "Открыть", self.open_file, "folder-open", QKeySequence.Open)
|
||||
add_action("save", "Сохранить", self.save_file, "save", QKeySequence.Save)
|
||||
add_action("save_as", "Сохранить как", self.save_file_as, "save-all", QKeySequence.SaveAs)
|
||||
add_action(
|
||||
"export_image",
|
||||
"Экспортировать в изображение...",
|
||||
self.export_mapping_image,
|
||||
"camera",
|
||||
QKeySequence("Ctrl+Shift+E"),
|
||||
"Экспортировать текущую схему маппинга в изображение (PNG, JPEG)"
|
||||
)
|
||||
add_action("settings", "Параметры", self.open_settings, "settings", QKeySequence("Ctrl+,"))
|
||||
add_action(
|
||||
"generate_script",
|
||||
@ -439,6 +447,7 @@ class MainWindow(QMainWindow):
|
||||
file_menu.addAction(self.actions["open"])
|
||||
file_menu.addAction(self.actions["save"])
|
||||
file_menu.addAction(self.actions["save_as"])
|
||||
file_menu.addAction(self.actions["export_image"])
|
||||
file_menu.addSeparator()
|
||||
file_menu.addAction(self.actions["generate_script"])
|
||||
file_menu.addSeparator()
|
||||
@ -939,6 +948,38 @@ class MainWindow(QMainWindow):
|
||||
if path:
|
||||
self._save_to(Path(path))
|
||||
|
||||
def export_mapping_image(self) -> None:
|
||||
if not self.document.points:
|
||||
self.status.setText("Схема пуста. Экспорт отменён.")
|
||||
self._refresh_ui_state()
|
||||
return
|
||||
|
||||
default_name = "mapping.png"
|
||||
if self.document.path is not None:
|
||||
default_name = self.document.path.stem + ".png"
|
||||
|
||||
self._disable_shortcuts()
|
||||
try:
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Экспортировать маппинг в изображение",
|
||||
default_name,
|
||||
"PNG Image (*.png);;JPEG Image (*.jpg *.jpeg)"
|
||||
)
|
||||
finally:
|
||||
self._enable_shortcuts()
|
||||
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
success = self.canvas.export_to_image(file_path)
|
||||
if success:
|
||||
self.status.setText(f"Схема успешно экспортирована в: {file_path}")
|
||||
else:
|
||||
show_warning(self, "Ошибка экспорта", "Не удалось экспортировать схему в изображение.")
|
||||
self.status.setText("Ошибка экспорта изображения.")
|
||||
self._refresh_ui_state()
|
||||
|
||||
def generate_script(self, output_dir: str | Path | None = None) -> None:
|
||||
if isinstance(output_dir, bool):
|
||||
output_dir = None
|
||||
@ -2137,6 +2178,7 @@ class MainWindow(QMainWindow):
|
||||
state = {
|
||||
"save": has_points and self._dirty,
|
||||
"save_as": has_points,
|
||||
"export_image": has_points,
|
||||
"generate_script": has_points,
|
||||
"undo": bool(self._undo_stack),
|
||||
"redo": bool(self._redo_stack),
|
||||
|
||||
21
qa_smoke.py
21
qa_smoke.py
@ -121,6 +121,7 @@ def main() -> None:
|
||||
test_panel_mapping_generation_with_strategies,
|
||||
test_block_renaming_and_naming_strategy,
|
||||
test_line_renaming_and_tree_shortcuts,
|
||||
test_export_mapping_to_image,
|
||||
test_save_preserves_unknown_source_fields,
|
||||
test_ordered_json_preserves_document_and_remaps_uids,
|
||||
test_background_load_save_roundtrip,
|
||||
@ -1132,6 +1133,26 @@ def test_line_renaming_and_tree_shortcuts() -> None:
|
||||
window.close()
|
||||
|
||||
|
||||
def test_export_mapping_to_image() -> None:
|
||||
if MainWindow is None or QApplication is None:
|
||||
return
|
||||
app = QApplication.instance() or QApplication([])
|
||||
window = MainWindow()
|
||||
|
||||
window.document.add_manual_point(100.0, 100.0)
|
||||
window.canvas.set_document(window.document)
|
||||
window._refresh_ui_state()
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
image_path = Path(temp_dir) / "output.png"
|
||||
success = window.canvas.export_to_image(str(image_path))
|
||||
assert success
|
||||
assert image_path.exists()
|
||||
assert image_path.stat().st_size > 0
|
||||
|
||||
window.close()
|
||||
|
||||
|
||||
def test_save_preserves_unknown_source_fields() -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
source = Path(temp_dir) / "custom.json"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user