Enhance export mapping to image with block badges and sidebar legend

- Добавлен детальный рендеринг маппинга при экспорте:
  - Рисуются информационные плашки (badges) для первого и последнего светодиода в каждом блоке, показывающие ID точки и её локальный индекс в линии.
  - Справа от маппинга выводится боковая панель с легендой линий и блоков.
  - В легенде под цветом каждой линии выводятся блоки и диапазоны индексов их светодиодов (например, Блок: 0-186).
This commit is contained in:
Данил Ярош 2026-07-31 13:11:04 +03:00
parent 31b9028fc2
commit 59586baca1

173
canvas.py
View File

@ -1499,30 +1499,167 @@ class LedCanvas(QGraphicsView):
if rect.isEmpty():
return False
# 1. Add temporary badges for start/end points of each block
from PySide6.QtGui import QFont, QPen, QBrush, QColor
from PySide6.QtWidgets import QGraphicsRectItem, QGraphicsSimpleTextItem
def create_badge(text: str, bg_color: QColor, pos: QPointF, offset_y: float) -> QGraphicsRectItem:
text_item = QGraphicsSimpleTextItem(text)
font = QFont("Outfit", 8)
font.setBold(True)
text_item.setFont(font)
text_item.setBrush(QBrush(Qt.white))
rect = text_item.boundingRect()
px, py = 6, 4
w = rect.width() + px * 2
h = rect.height() + py * 2
rect_item = QGraphicsRectItem(-w/2, -h/2, w, h)
rect_item.setBrush(QBrush(bg_color))
rect_item.setPen(QPen(Qt.white, 1.2))
rect_item.setZValue(100)
text_item.setParentItem(rect_item)
text_item.setPos(-rect.width()/2 - rect.left(), -rect.height()/2 - rect.top())
rect_item.setPos(pos.x(), pos.y() + offset_y)
return rect_item
temp_items = []
for line in self.document.lines:
current_offset = 0
for block in line.blocks:
if not block.point_ids:
continue
block_len = len(block.point_ids)
first_pid = block.point_ids[0]
last_pid = block.point_ids[-1]
start_idx = current_offset
end_idx = current_offset + block_len - 1
current_offset += block_len
# Get scene positions
if first_pid in self.items_by_id:
pos_start = self.items_by_id[first_pid].pos()
point_start = self.document.points[first_pid]
if last_pid == first_pid:
text_start = f"S=E: ID {point_start.source_id} (#{start_idx})"
badge_start = create_badge(text_start, QColor("#3b82f6"), pos_start, offset_y=-18) # Blue badge
else:
text_start = f"S: ID {point_start.source_id} (#{start_idx})"
badge_start = create_badge(text_start, QColor("#10b981"), pos_start, offset_y=-18) # Green badge
scene.addItem(badge_start)
temp_items.append(badge_start)
if last_pid in self.items_by_id and last_pid != first_pid:
pos_end = self.items_by_id[last_pid].pos()
point_end = self.document.points[last_pid]
text_end = f"E: ID {point_end.source_id} (#{end_idx})"
badge_end = create_badge(text_end, QColor("#ef4444"), pos_end, offset_y=18) # Red badge
scene.addItem(badge_end)
temp_items.append(badge_end)
# 2. Render setup
target_height = 2000
mapping_width = 2000
sidebar_width = 800
total_width = mapping_width + sidebar_width
image = QImage(total_width, target_height, QImage.Format.Format_ARGB32)
# Draw background for the mapping area
bg_brush = scene.backgroundBrush()
mapping_bg = bg_brush.color() if bg_brush.style() != Qt.NoBrush else QColor("#2b2b2b" if self._theme == "dark" else "#ffffff")
painter = QPainter(image)
painter.setRenderHint(QPainter.Antialiasing)
painter.setRenderHint(QPainter.TextAntialiasing)
# Fill the entire image with mapping background initially
image.fill(mapping_bg)
# Adjust scene rect with padding for mapping render
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)
# Render the scene mapping area on the left
scene.render(painter, QRectF(0, 0, mapping_width, target_height), rect)
if target_height > 4096:
target_height = 4096
target_width = int(target_height / aspect_ratio)
# 3. Draw sidebar legend on the right
sidebar_rect = QRectF(mapping_width, 0, sidebar_width, target_height)
# Fill sidebar background
painter.setPen(Qt.NoPen)
sidebar_bg = QColor("#1e293b" if self._theme == "dark" else "#f8fafc")
painter.setBrush(QBrush(sidebar_bg))
painter.drawRect(sidebar_rect)
# Draw vertical divider line
painter.setPen(QPen(QColor("#475569" if self._theme == "dark" else "#cbd5e1"), 2))
painter.drawLine(mapping_width, 0, mapping_width, target_height)
# Fonts
font_title = QFont("Outfit", 20)
font_title.setBold(True)
font_line = QFont("Outfit", 14)
font_line.setBold(True)
font_block = QFont("Outfit", 11)
# Title
painter.setFont(font_title)
painter.setPen(QColor("#ffffff" if self._theme == "dark" else "#0f172a"))
title_rect = QRectF(mapping_width + 40, 40, sidebar_width - 80, 50)
painter.drawText(title_rect, Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft, "ЛЕГЕНДА МАППИНГА")
# Draw lines and blocks
y = 120
for line in self.document.lines:
if y > target_height - 60:
break
# Line swatch
swatch_rect = QRectF(mapping_width + 40, y + 4, 24, 24)
painter.setPen(Qt.NoPen)
painter.setBrush(QBrush(QColor(line.color)))
painter.drawRoundedRect(swatch_rect, 4, 4)
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"))
# Line name
painter.setFont(font_line)
painter.setPen(QColor("#f8fafc" if self._theme == "dark" else "#1e293b"))
line_text_rect = QRectF(mapping_width + 80, y, sidebar_width - 120, 32)
painter.drawText(line_text_rect, Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft, line.name)
y += 40
# Blocks under line
painter.setFont(font_block)
painter.setPen(QColor("#94a3b8" if self._theme == "dark" else "#64748b"))
current_offset = 0
for block in line.blocks:
if y > target_height - 40:
break
block_len = len(block.point_ids)
if block_len == 0:
block_range_str = "empty"
else:
start_idx = current_offset
end_idx = current_offset + block_len - 1
block_range_str = f"{start_idx}-{end_idx}"
current_offset += block_len
block_text = f"• {block.name}: {block_range_str}"
block_text_rect = QRectF(mapping_width + 100, y, sidebar_width - 140, 24)
painter.drawText(block_text_rect, Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft, block_text)
y += 28
y += 12
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()
# 4. Remove temporary badges from the scene
for item in temp_items:
scene.removeItem(item)
return image.save(file_path)