pyside6/pyqt6/pyside/pyqt 构建QGraphicsItem拖拽设置形状的矩形框

pyside6/pyqt6/pyside/pyqt 鼠标调整矩形框尺寸大小

目录

1.需求

2.实际效果

3.实际代码


1.需求

        [1] 采用pyside6(pyqt同样)的QGraphicsView、QGraphicsScene、QGraphicsItem实现ROI矩形框的绘制,类似labelimg等深度学习CV标注工具的矩形框标注功能;

        [2] 由于pyside6中QGraphicsRectItem只支持设置几个属性即可简单实现矩形框选中、平移、删除,但是不直接支持拖拽设置尺寸,因此继承这个类,重写部分函数,以实现句柄拖拽矩形框尺寸形状。

2.实际效果

3.实际代码

        代码摘自https://stackoverflow.com/questions/34429632/resize-a-qgraphicsitem-with-the-mouse

import sys

from PySide6.QtCore import Qt, QRectF, QPointF
from PySide6.QtGui import QBrush, QPainterPath, QPainter, QColor, QPen, QPixmap
from PySide6.QtWidgets import QGraphicsRectItem, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem


class GraphicsRectItem(QGraphicsRectItem):

    handleTopLeft = 1
    handleTopMiddle = 2
    handleTopRight = 3
    handleMiddleLeft = 4
    handleMiddleRight = 5
    handleBottomLeft = 6
    handleBottomMiddle = 7
    handleBottomRight = 8

    handleSize = +8.0
    handleSpace = -4.0

    handleCursors = {
        handleTopLeft: Qt.SizeFDiagCursor,
        handleTopMiddle: Qt.SizeVerCursor,
        handleTopRight: Qt.SizeBDiagCursor,
        handleMiddleLeft: Qt.SizeHorCursor,
        handleMiddleRight: Qt.SizeHorCursor,
        handleBottomLeft: Qt.SizeBDiagCursor,
        handleBottomMiddle: Qt.SizeVerCursor,
        handleBottomRight: Qt.SizeFDiagCursor,
    }

    def __init__(self, *args):
        """
        Initialize the shape.
        """
        super().__init__(*args)
        self.handles = {}
        self.handleSelected = None
        self.mousePressPos = None
        self.mousePressRect = None
        self.setAcceptHoverEvents(True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)
        self.updateHandlesPos()

    def handleAt(self, point):
        """
        Returns the resize handle below the given point.
        """
        for k, v, in self.handles.items():
            if v.contains(point):
                return k
        return None

    def hoverMoveEvent(self, moveEvent):
        """
        Executed when the mouse moves over the shape (NOT PRESSED).
        """
        if self.isSelected():
            handle = self.handleAt(moveEvent.pos())
            cursor = Qt.ArrowCursor if handle is None else self.handleCursors[handle]
            self.setCursor(cursor)
        super().hoverMoveEvent(moveEvent)

    def hoverLeaveEvent(self, moveEvent):
        """
        Executed when the mouse leaves the shape (NOT PRESSED).
        """
        self.setCursor(Qt.ArrowCursor)
        super().hoverLeaveEvent(moveEvent)

    def mousePressEvent(self, mouseEvent):
        """
        Executed when the mouse is pressed on the item.
        """
        self.handleSelected = self.handleAt(mouseEvent.pos())
        if self.handleSelected:
            self.mousePressPos = mouseEvent.pos()
            self.mousePressRect = self.boundingRect()
        super().mousePressEvent(mouseEvent)

    def mouseMoveEvent(self, mouseEvent):
        """
        Executed when the mouse is being moved over the item while being pressed.
        """
        if self.handleSelected is not None:
            self.interactiveResize(mouseEvent.pos())
        else:
            super().mouseMoveEvent(mouseEvent)

    def mouseReleaseEvent(self, mouseEvent):
        """
        Executed when the mouse is released from the item.
        """
        super().mouseReleaseEvent(mouseEvent)
        self.handleSelected = None
        self.mousePressPos = None
        self.mousePressRect = None
        self.update()

    def boundingRect(self):
        """
        Returns the bounding rect of the shape (including the resize handles).
        """
        o = self.handleSize + self.handleSpace
        return self.rect().adjusted(-o, -o, o, o)

    def updateHandlesPos(self):
        """
        Update current resize handles according to the shape size and position.
        """
        s = self.handleSize
        b = self.boundingRect()
        self.handles[self.handleTopLeft] = QRectF(b.left(), b.top(), s, s)
        self.handles[self.handleTopMiddle] = QRectF(b.center().x() - s / 2, b.top(), s, s)
        self.handles[self.handleTopRight] = QRectF(b.right() - s, b.top(), s, s)
        self.handles[self.handleMiddleLeft] = QRectF(b.left(), b.center().y() - s / 2, s, s)
        self.handles[self.handleMiddleRight] = QRectF(b.right() - s, b.center().y() - s / 2, s, s)
        self.handles[self.handleBottomLeft] = QRectF(b.left(), b.bottom() - s, s, s)
        self.handles[self.handleBottomMiddle] = QRectF(b.center().x() - s / 2, b.bottom() - s, s, s)
        self.handles[self.handleBottomRight] = QRectF(b.right() - s, b.bottom() - s, s, s)

    def interactiveResize(self, mousePos):
        """
        Perform shape interactive resize.
        """
        offset = self.handleSize + self.handleSpace
        boundingRect = self.boundingRect()
        rect = self.rect()
        diff = QPointF(0, 0)

        self.prepareGeometryChange()

        if self.handleSelected == self.handleTopLeft:

            fromX = self.mousePressRect.left()
            fromY = self.mousePressRect.top()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            diff.setX(toX - fromX)
            diff.setY(toY - fromY)
            boundingRect.setLeft(toX)
            boundingRect.setTop(toY)
            rect.setLeft(boundingRect.left() + offset)
            rect.setTop(boundingRect.top() + offset)
            self.setRect(rect)

        elif self.handleSelected == self.handleTopMiddle:

            fromY = self.mousePressRect.top()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            diff.setY(toY - fromY)
            boundingRect.setTop(toY)
            rect.setTop(boundingRect.top() + offset)
            self.setRect(rect)

        elif self.handleSelected == self.handleTopRight:

            fromX = self.mousePressRect.right()
            fromY = self.mousePressRect.top()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            diff.setX(toX - fromX)
            diff.setY(toY - fromY)
            boundingRect.setRight(toX)
            boundingRect.setTop(toY)
            rect.setRight(boundingRect.right() - offset)
            rect.setTop(boundingRect.top() + offset)
            self.setRect(rect)

        elif self.handleSelected == self.handleMiddleLeft:

            fromX = self.mousePressRect.left()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            diff.setX(toX - fromX)
            boundingRect.setLeft(toX)
            rect.setLeft(boundingRect.left() + offset)
            self.setRect(rect)

        elif self.handleSelected == self.handleMiddleRight:
            print("MR")
            fromX = self.mousePressRect.right()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            diff.setX(toX - fromX)
            boundingRect.setRight(toX)
            rect.setRight(boundingRect.right() - offset)
            self.setRect(rect)

        elif self.handleSelected == self.handleBottomLeft:

            fromX = self.mousePressRect.left()
            fromY = self.mousePressRect.bottom()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            diff.setX(toX - fromX)
            diff.setY(toY - fromY)
            boundingRect.setLeft(toX)
            boundingRect.setBottom(toY)
            rect.setLeft(boundingRect.left() + offset)
            rect.setBottom(boundingRect.bottom() - offset)
            self.setRect(rect)

        elif self.handleSelected == self.handleBottomMiddle:

            fromY = self.mousePressRect.bottom()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            diff.setY(toY - fromY)
            boundingRect.setBottom(toY)
            rect.setBottom(boundingRect.bottom() - offset)
            self.setRect(rect)

        elif self.handleSelected == self.handleBottomRight:

            fromX = self.mousePressRect.right()
            fromY = self.mousePressRect.bottom()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            diff.setX(toX - fromX)
            diff.setY(toY - fromY)
            boundingRect.setRight(toX)
            boundingRect.setBottom(toY)
            rect.setRight(boundingRect.right() - offset)
            rect.setBottom(boundingRect.bottom() - offset)
            self.setRect(rect)

        self.updateHandlesPos()

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        """
        path = QPainterPath()
        path.addRect(self.rect())
        if self.isSelected():
            for shape in self.handles.values():
                path.addEllipse(shape)
        return path

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the graphic view.
        """
        painter.setBrush(QBrush(QColor(255, 0, 0, 100)))
        painter.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
        painter.drawRect(self.rect())

        painter.setRenderHint(QPainter.Antialiasing)
        painter.setBrush(QBrush(QColor(255, 0, 0, 255)))
        painter.setPen(QPen(QColor(0, 0, 0, 255), 1.0, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        for handle, rect in self.handles.items():
            if self.handleSelected is None or handle == self.handleSelected:
                painter.drawEllipse(rect)


def main():

    app = QApplication(sys.argv)

    grview = QGraphicsView()
    scene = QGraphicsScene()

    item = GraphicsRectItem(0, 0, 300, 150)
    scene.addItem(item)

    grview.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
    grview.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

<think>好的,我现在需要帮助用户解决在PyQtPySide中使用QPainterPath和addEllipse方法的问题。用户之前的问题是关于Profinet通信的Python实现,但当前问题转向了图形界面开发,特别是QPainterPath和椭圆绘制。我需要结合用户提供的引用内容来给出解答。 首先,用户引用了三个资源: 1. 引用[1]提到QGraphicsItem的shape()和collidesWith()方法,用于碰撞检测,这可能与绘制形状后的交互有关。 2. 引用[2]讨论了使用QPainter绘制环形进度条时的问题,如渐变方向和QRect转换问题,这可能与绘图坐标和图形变换相关。 3. 引用[3]涉及QGraphicsView体系下的矩形框绘制,类似标注工具的功能,这可能与如何在场景中添加图形项有关。 用户当前的问题是如何正确使用QPainterPath和addEllipse方法。我需要结合这些引用内容来构建答案。 首先,QPainterPath是用于定义矢量形状的类,addEllipse方法用于向路径中添加椭圆。用户可能在绘制椭圆时遇到了位置、大小或样式的问题。例如,椭圆的坐标原点是否正确,如何设置填充和边框,或者如何将椭圆添加到场景中进行交互。 根据引用[1],如果用户需要椭圆参与碰撞检测,需要正确实现shape()方法返回QPainterPath。引用[2]中的坐标转换问题(QRect和QRectF)提示要注意数据类型的一致性。引用[3]中的QGraphicsView体系说明可能需要将椭圆作为QGraphicsItem添加到场景中。 接下来,我需要分步骤说明如何使用QPainterPath和addEllipse,包括基本使用、自定义图形项、样式设置,以及可能的问题解决。例如,创建路径、添加椭圆、设置画笔和画刷,以及在自定义QGraphicsItem中重写paint和shape方法。同时,需要提到用户可能遇到的常见问题,如坐标原点、路径闭合、性能优化等。 还要结合引用中的案例,比如引用[2]中的视口翻转可能影响椭圆的绘制方向,需要注意坐标系统。此外,如果椭圆需要交互功能(如拖拽),可能需要参考引用[3]中的方法,处理鼠标事件和位置计算。 最后,生成相关问题,帮助用户进一步学习,例如碰撞检测、动态修改形状、动画效果等,这些都与之前引用中的内容相关。 总结步骤: 1. 解释QPainterPath和addEllipse的基本用法。 2. 提供代码示例,展示如何创建椭圆路径并绘制。 3. 说明如何将椭圆作为QGraphicsItem添加到场景中,处理碰撞检测(引用[1])。 4. 讨论样式设置,如渐变和边框(引用[2]的渐变问题)。 5. 常见问题解答,如坐标、闭合路径、性能。 6. 生成相关问题,引导深入学习。</think>### 使用QPainterPath和addEllipse的完整指南 #### 一、核心方法解析 1. **QPainterPath**:矢量绘图路径容器,支持组合基本形状(椭圆、矩形等)形成复杂图形。 2. **addEllipse方法**:向路径中添加椭圆,参数定义方式: - `addEllipse(x, y, width, height)`:椭圆外接矩形左上角坐标`(x,y)`,尺寸`width×height` - `addEllipse(center, rx, ry)`:通过中心点和半径定义椭圆(需配合`QPointF`) --- #### 二、基础使用示例 ```python from PySide6.QtCore import Qt, QRectF, QPointF from PySide6.QtGui import QPainterPath, QPen, QBrush, QColor from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPathItem # 创建路径并添加椭圆 path = QPainterPath() path.addEllipse(QPointF(100, 100), 50, 30) # 中心点(100,100),横轴半径50,纵轴半径30 # 创建图形项并设置样式 ellipse_item = QGraphicsPathItem(path) ellipse_item.setPen(QPen(Qt.blue, 2)) # 边框:蓝色2像素 ellipse_item.setBrush(QBrush(QColor(255, 0, 0, 100))) # 填充:半透明红色 # 添加到场景 scene = QGraphicsScene() scene.addItem(ellipse_item) view = QGraphicsView(scene) view.show() ``` --- #### 三、自定义图形项实现(支持碰撞检测) 参考引用[1],通过重写`shape()`返回精确路径: ```python class CustomEllipseItem(QGraphicsPathItem): def __init__(self, center, rx, ry): super().__init__() path = QPainterPath() path.addEllipse(center, rx, ry) self.setPath(path) def shape(self): # 精确碰撞检测[^1] stroker = QPainterPathStroker() stroker.setWidth(self.pen().widthF()) return stroker.createStroke(self.path()) + self.path() def paint(self, painter, option, widget): painter.setPen(self.pen()) painter.setBrush(self.brush()) painter.drawPath(self.path()) ``` --- #### 四、高级样式设置 结合引用[2]的渐变处理技巧: ```python # 创建径向渐变(解决方向问题) gradient = QRadialGradient(QPointF(100,100), 50) # 中心点与半径 gradient.setColorAt(0, Qt.yellow) gradient.setColorAt(1, Qt.transparent) # 应用渐变填充 ellipse_item.setBrush(QBrush(gradient)) # 坐标转换技巧(避免PyQt隐式转换问题) rect = QRectF(50, 50, 100, 60) # 始终使用QRectF类型[^2] path.addEllipse(rect) ``` --- #### 五、常见问题解决 | 问题现象 | 原因分析 | 解决方案 | |-------------------------|------------------------|----------------------------| | 椭圆显示位置偏移 | 坐标原点理解错误 | 确认使用`QPointF`定义中心点 | | 填充颜色不生效 | 未设置画刷或路径未闭合 | 调用`path.closeSubpath()` | | 鼠标点击检测区域不准确 | 未重写shape()方法 | 实现自定义碰撞检测路径[^1] | | 绘制性能低下 | 复杂路径未优化 | 使用`QGraphicsEllipseItem`替代 | --- ### 动态交互示例(拖拽椭圆) 结合引用[3]的ROI框实现思路: ```python class DraggableEllipseItem(CustomEllipseItem): def mousePressEvent(self, event): self.offset = event.pos() - self.path().boundingRect().center() def mouseMoveEvent(self, event): new_center = event.scenePos() - self.offset self.setPath(QPainterPath().addEllipse(new_center, 50, 30)) self.update() # 强制重绘 ``` --- ### 相关问题 1. 如何实现QGraphicsItem之间的碰撞检测? 2. 如何在椭圆路径上添加渐变透明度效果? 3. 如何通过QTimeLine实现椭圆形状的动画变形?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值