From 59586baca14995475e2ca587f0b96bf7ac6d5d4a Mon Sep 17 00:00:00 2001 From: Danil Iarosh Date: Fri, 31 Jul 2026 13:11:04 +0300 Subject: [PATCH] Enhance export mapping to image with block badges and sidebar legend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен детальный рендеринг маппинга при экспорте: - Рисуются информационные плашки (badges) для первого и последнего светодиода в каждом блоке, показывающие ID точки и её локальный индекс в линии. - Справа от маппинга выводится боковая панель с легендой линий и блоков. - В легенде под цветом каждой линии выводятся блоки и диапазоны индексов их светодиодов (например, Блок: 0-186). --- canvas.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 155 insertions(+), 18 deletions(-) diff --git a/canvas.py b/canvas.py index 00a9597..0de835e 100644 --- a/canvas.py +++ b/canvas.py @@ -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)