def paintEvent(self, event: QPaintEvent):
     outerRadius = min(self.width(), self.height())
     baseRect = QRectF(1, 1, outerRadius - 2, outerRadius - 2)
     buffer = QImage(outerRadius, outerRadius, QImage.Format_ARGB32_Premultiplied)
     p = QPainter(buffer)
     p.setRenderHint(QPainter.Antialiasing)
     self.rebuildDataBrushIfNeeded()
     self.drawBackground(p, buffer.rect())
     self.drawBase(p, baseRect)
     if self.m_value > 0:
         delta = (self.m_max - self.m_min) / (self.m_value - self.m_min)
     else:
         delta = 0
     self.drawValue(p, baseRect, self.m_value, delta)
     innerRect, innerRadius = self.calculateInnerRect(outerRadius)
     self.drawInnerBackground(p, innerRect)
     self.drawText(p, innerRect, innerRadius, self.m_value)
     p.end()
     painter = QPainter(self)
     painter.fillRect(baseRect, self.palette().window())
     painter.drawImage(0, 0, buffer)
Example #2
0
class BaseCanvas(QWidget, metaclass=QABCMeta):
    """The subclass can draw a blank canvas more easier."""
    @abstractmethod
    def __init__(self, parent: QWidget):
        """Set the parameters for drawing."""
        super(BaseCanvas, self).__init__(parent)
        self.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
        self.setFocusPolicy(Qt.StrongFocus)
        self.setMouseTracking(True)
        self.painter = QPainter()
        # Origin coordinate
        self.ox = self.width() / 2
        self.oy = self.height() / 2
        # Canvas zoom rate
        self.zoom = 1.
        # Joint size
        self.joint_size = 5
        # Canvas line width
        self.link_width = 3
        self.path_width = 3
        # Font size
        self.font_size = 15
        # Show point mark or dimension
        self.show_ticks = _TickMark.SHOW
        self.show_point_mark = True
        self.show_dimension = True
        # Path track
        self.path = _PathOption()
        # Path solving
        self.ranges: Dict[str, QRectF] = {}
        self.target_path: Dict[int, Sequence[_Coord]] = {}
        self.show_target_path = False
        # Background
        self.background = QImage()
        self.background_opacity = 1.
        self.background_scale = 1.
        self.background_offset = QPointF(0, 0)
        # Monochrome mode
        self.monochrome = False
        # Grab mode
        self.__grab_mode = False

    def switch_grab(self) -> None:
        """Start grab mode."""
        self.__grab_mode = not self.__grab_mode

    @staticmethod
    def zoom_factor(width: int, height: int, x_right: float, x_left: float,
                    y_top: float, y_bottom: float) -> float:
        """Calculate the zoom factor."""
        x_diff = x_left - x_right
        y_diff = y_top - y_bottom
        x_diff = x_diff if x_diff else 1.
        y_diff = y_diff if y_diff else 1.
        if width / x_diff < height / y_diff:
            return width / x_diff
        else:
            return height / y_diff

    @abstractmethod
    def paintEvent(self, event: QPaintEvent) -> None:
        """Using a QPainter under 'self',
        so just change QPen or QBrush before painting.
        """
        if not self.__grab_mode:
            self.painter.begin(self)
            self.painter.fillRect(event.rect(), QBrush(Qt.white))
        # Translation
        self.painter.translate(self.ox, self.oy)
        # Background
        if not self.background.isNull():
            rect = self.background.rect()
            self.painter.setOpacity(self.background_opacity)
            self.painter.drawImage(
                QRectF(
                    self.background_offset * self.zoom,
                    QSizeF(rect.width(), rect.height()) *
                    self.background_scale * self.zoom), self.background,
                QRectF(rect))
            self.painter.setOpacity(1)
        # Show frame
        pen = QPen(Qt.blue)
        pen.setWidth(1)
        self.painter.setPen(pen)
        self.painter.setFont(QFont("Arial", self.font_size))
        # Draw origin lines
        if self.show_ticks not in {_TickMark.SHOW, _TickMark.SHOW_NUM}:
            return
        pen.setColor(Qt.gray)
        self.painter.setPen(pen)
        x_l = -self.ox
        x_r = self.width() - self.ox
        self.painter.drawLine(QPointF(x_l, 0), QPointF(x_r, 0))
        y_t = self.height() - self.oy
        y_b = -self.oy
        self.painter.drawLine(QPointF(0, y_b), QPointF(0, y_t))

        def indexing(v: float) -> int:
            """Draw tick."""
            return int(v / self.zoom - v / self.zoom % 5)

        # Draw tick
        for x in range(indexing(x_l), indexing(x_r) + 1, 5):
            if x == 0:
                continue
            is_ten = x % 10 == 0
            end = QPointF(x * self.zoom, -10 if is_ten else -5)
            self.painter.drawLine(QPointF(x, 0) * self.zoom, end)
            if self.show_ticks == _TickMark.SHOW_NUM and is_ten:
                self.painter.drawText(end + QPointF(0, 3), f"{x}")
        for y in range(indexing(y_b), indexing(y_t) + 1, 5):
            if y == 0:
                continue
            is_ten = y % 10 == 0
            end = QPointF(10 if is_ten else 5, y * self.zoom)
            self.painter.drawLine(QPointF(0, y) * self.zoom, end)
            if self.show_ticks == _TickMark.SHOW_NUM and is_ten:
                self.painter.drawText(end + QPointF(3, 0), f"{-y}")
        # Please to call the "end" method when ending paint event.

    def draw_circle(self, p: QPointF, r: float) -> None:
        """Draw circle."""
        self.painter.drawEllipse(p, r, r)

    def draw_point(self,
                   i: int,
                   cx: float,
                   cy: float,
                   fixed: bool,
                   color: Optional[Tuple[int, int, int]],
                   mul: int = 1) -> None:
        """Draw a joint."""
        if self.monochrome or color is None:
            color = Qt.black
        else:
            color = QColor(*color)
        pen = QPen(color)
        pen.setWidth(2)
        self.painter.setPen(pen)
        x = cx * self.zoom
        y = cy * -self.zoom
        if fixed:
            # Draw a triangle below
            self.painter.drawPolygon(
                QPointF(x, y),
                QPointF(x - self.joint_size, y + 2 * self.joint_size),
                QPointF(x + self.joint_size, y + 2 * self.joint_size))
        r = self.joint_size
        for _ in range(1 if mul < 1 else mul):
            self.draw_circle(QPointF(x, y), r)
            r += 5
        if not self.show_point_mark:
            return
        pen.setColor(Qt.darkGray)
        pen.setWidth(2)
        self.painter.setPen(pen)
        text = f"[Point{i}]"
        if self.show_dimension:
            text += f":({cx:.02f}, {cy:.02f})"
        self.painter.drawText(QPointF(x, y) + QPointF(6, -6), text)

    def draw_ranges(self) -> None:
        """Draw rectangle ranges."""
        pen = QPen()
        pen.setWidth(5)
        for i, (tag, rect) in enumerate(self.ranges.items()):
            range_color = QColor(color_num(i + 1))
            range_color.setAlpha(30)
            self.painter.setBrush(range_color)
            range_color.setAlpha(255)
            pen.setColor(range_color)
            self.painter.setPen(pen)
            cx = rect.x() * self.zoom
            cy = rect.y() * -self.zoom
            if rect.width():
                self.painter.drawRect(
                    QRectF(QPointF(cx, cy),
                           QSizeF(rect.width(), rect.height()) * self.zoom))
            else:
                self.draw_circle(QPointF(cx, cy), 3)
            range_color.setAlpha(255)
            pen.setColor(range_color)
            self.painter.setPen(pen)
            self.painter.drawText(QPointF(cx, cy) + QPointF(6, -6), tag)
            self.painter.setBrush(Qt.NoBrush)

    def draw_target_path(self) -> None:
        """Draw solving path."""
        pen = QPen()
        pen.setWidth(self.path_width)
        for i, n in enumerate(sorted(self.target_path)):
            path = self.target_path[n]
            if self.monochrome:
                line, dot = target_path_style(0)
            else:
                line, dot = target_path_style(i + 1)
            pen.setColor(line)
            self.painter.setPen(pen)
            if len(path) == 1:
                x, y = path[0]
                p = QPointF(x, -y) * self.zoom
                self.painter.drawText(p + QPointF(6, -6), f"P{n}")
                pen.setColor(dot)
                self.painter.setPen(pen)
                self.draw_circle(p, self.joint_size)
            else:
                painter_path = QPainterPath()
                for j, (x, y) in enumerate(path):
                    p = QPointF(x, -y) * self.zoom
                    self.draw_circle(p, self.joint_size)
                    if j == 0:
                        self.painter.drawText(p + QPointF(6, -6), f"P{n}")
                        painter_path.moveTo(p)
                    else:
                        x2, y2 = path[j - 1]
                        self.__draw_arrow(x, -y, x2, -y2, zoom=True)
                        painter_path.lineTo(p)
                pen.setColor(line)
                self.painter.setPen(pen)
                self.painter.drawPath(painter_path)
                for x, y in path:
                    pen.setColor(dot)
                    self.painter.setPen(pen)
                    self.draw_circle(
                        QPointF(x, -y) * self.zoom, self.joint_size)
        self.painter.setBrush(Qt.NoBrush)

    def __draw_arrow(self,
                     x1: float,
                     y1: float,
                     x2: float,
                     y2: float,
                     *,
                     zoom: bool = False,
                     text: str = '') -> None:
        """Front point -> Back point"""
        if zoom:
            x1 *= self.zoom
            y1 *= self.zoom
            x2 *= self.zoom
            y2 *= self.zoom
        a = atan2(y2 - y1, x2 - x1)
        x1 = (x1 + x2) / 2 - 7.5 * cos(a)
        y1 = (y1 + y2) / 2 - 7.5 * sin(a)
        first_point = QPointF(x1, y1)
        self.painter.drawLine(
            first_point,
            QPointF(x1 + 15 * cos(a + radians(20)),
                    y1 + 15 * sin(a + radians(20))))
        self.painter.drawLine(
            first_point,
            QPointF(x1 + 15 * cos(a - radians(20)),
                    y1 + 15 * sin(a - radians(20))))
        if not text:
            return
        # Font
        font = self.painter.font()
        font_copy = QFont(font)
        font.setBold(True)
        font.setPointSize(font.pointSize() + 8)
        self.painter.setFont(font)
        # Color
        pen = self.painter.pen()
        color = pen.color()
        pen.setColor(color.darker())
        self.painter.setPen(pen)
        self.painter.drawText(first_point, text)
        pen.setColor(color)
        self.painter.setPen(pen)
        self.painter.setFont(font_copy)

    def draw_curve(self, path: Sequence[_Coord]) -> None:
        """Draw path as curve."""
        if len(set(path)) < 2:
            return
        painter_path = QPainterPath()
        error = False
        for i, (x, y) in enumerate(path):
            if isnan(x):
                error = True
                self.painter.drawPath(painter_path)
                painter_path = QPainterPath()
            else:
                p = QPointF(x, -y) * self.zoom
                if i == 0:
                    painter_path.moveTo(p)
                    self.draw_circle(p, 2)
                    continue
                if error:
                    painter_path.moveTo(p)
                    error = False
                else:
                    painter_path.lineTo(p)
        self.painter.drawPath(painter_path)

    def draw_dot(self, path: Sequence[_Coord]) -> None:
        """Draw path as dots."""
        if len(set(path)) < 2:
            return
        for i, (x, y) in enumerate(path):
            if isnan(x):
                continue
            p = QPointF(x, -y) * self.zoom
            if i == 0:
                self.draw_circle(p, 2)
            else:
                self.painter.drawPoint(p)

    def solution_polygon(
            self, func: str, args: Sequence[str], target: str,
            pos: Sequence[VPoint]) -> Tuple[List[QPointF], QColor]:
        """Get solution polygon."""
        if func == 'PLLP':
            color = QColor(121, 171, 252)
            params = [args[0], args[-1]]
        elif func == 'PLAP':
            color = QColor(249, 84, 216)
            params = [args[0]]
        else:
            if func == 'PLPP':
                color = QColor(94, 255, 185)
            else:
                # PXY
                color = QColor(249, 175, 27)
            params = [args[0]]
        params.append(target)
        tmp_list = []
        for name in params:
            try:
                index = int(name.replace('P', ''))
            except ValueError:
                continue
            else:
                vpoint = pos[index]
                tmp_list.append(QPointF(vpoint.cx, -vpoint.cy) * self.zoom)
        return tmp_list, color

    def draw_solution(self, func: str, args: Sequence[str], target: str,
                      pos: Sequence[VPoint]) -> None:
        """Draw the solution triangle."""
        points, color = self.solution_polygon(func, args, target, pos)
        color.setAlpha(150)
        pen = QPen(color)
        pen.setWidth(self.joint_size)
        self.painter.setPen(pen)

        def draw_arrow(index: int, text: str) -> None:
            """Draw arrow."""
            self.__draw_arrow(points[-1].x(),
                              points[-1].y(),
                              points[index].x(),
                              points[index].y(),
                              text=text)

        draw_arrow(0, args[1])
        if func == 'PLLP':
            draw_arrow(1, args[2])
        color.setAlpha(30)
        self.painter.setBrush(QBrush(color))
        self.painter.drawPolygon(QPolygonF(points))
        self.painter.setBrush(Qt.NoBrush)

    @Slot(int)
    def set_show_ticks(self, show: int):
        """Set the appearance of tick mark."""
        self.show_ticks = _TickMark(show + 1)
        self.update()

    @Slot(bool)
    def set_monochrome_mode(self, monochrome: bool) -> None:
        """Set monochrome mode."""
        self.monochrome = monochrome
        self.update()
Example #3
0
class UEyeView(QQuickPaintedItem):
    runningChanged = Signal(bool)
    camIdChanged = Signal(int)
    imageWidthChanged = Signal(int)
    imageHeightChanged = Signal(int)
    devicesChanged = Signal()

    def __init__(self, parent=None):
        super(UEyeView, self).__init__(parent)
        self._frame_image = QImage()
        self._running = False
        self._update_thread = None
        self._cam_id = 0
        self._image_width = 2048
        self._image_height = 1088
        self._devices = []

        self.destroyed.connect(lambda: self._stop_thread)

    def _on_camera_stopped(self):
        self._stop_thread()

    def _on_camera_errored(self, msg):
        logger.error(f"Error loading camera {msg}")
        self._running = False
        self.runningChanged.emit(self._running)

    @Property(list, notify=devicesChanged)
    def devices(self):
        return self._devices

    @Property(bool, notify=runningChanged)
    def running(self):
        return self._running

    @running.setter
    def running(self, value):
        if value == self._running:
            return
        self._running = value

        if self._running:
            self._start_thread()
        else:
            self._stop_thread()

        self.runningChanged.emit(value)

    @Property(int, notify=camIdChanged)
    def camId(self):
        return self._cam_id

    @camId.setter
    def camId(self, value):
        if value == self._cam_id:
            return
        self._cam_id = value
        self.camIdChanged.emit(value)

    @Property(int, notify=imageWidthChanged)
    def imageWidth(self):
        return self._image_width

    @imageWidth.setter
    def imageWidth(self, value):
        if value == self._image_width:
            return
        self._image_width = value
        self.imageWidthChanged.emit(value)

    @Property(int, notify=imageHeightChanged)
    def imageHeight(self):
        return self._image_height

    @imageHeight.setter
    def imageHeight(self, value):
        if value == self._image_height:
            return
        self._image_height = value
        self.imageHeightChanged.emit(value)

    @Property(bool, constant=True)
    def driverLoaded(self):
        return pue is not None

    def _start_thread(self):
        if self._update_thread:
            return
        self._update_thread = UpdateThread(
            self._cam_id, self._image_width, self._image_height
        )
        self._update_thread.pixmapReady.connect(self._update_pixmap)
        self._update_thread.stopped.connect(self._on_camera_stopped)
        self._update_thread.errored.connect(self._on_camera_errored)
        self._update_size()
        self._update_thread.start()

    def _stop_thread(self):
        if not self._update_thread:
            return
        self._update_thread.stop()
        self._update_thread.pixmapReady.disconnect(self._update_pixmap)
        self._update_thread.stopped.disconnect(self._on_camera_stopped)
        self._update_thread.errored.disconnect(self._on_camera_errored)
        self._update_thread = None

    def geometryChanged(self, new_geometry, old_geometry):
        super(UEyeView, self).geometryChanged(new_geometry, old_geometry)
        if self._update_thread:
            self._update_size()

    def paint(self, painter: QPainter):
        bounding_rect = QRect(0, 0, self.width(), self.height())
        if not self._frame_image.isNull():
            draw_rect = self._frame_image.rect()
            draw_rect.moveCenter(bounding_rect.center())
            painter.drawImage(draw_rect, self._frame_image, self._frame_image.rect())

    def _update_pixmap(self, image):
        self._frame_image = image
        self.update()

    def _update_size(self):
        self._update_thread.width = self.width()
        self._update_thread.height = self.height()