【问题标题】:Simplifying a QPainterPath to an outline将 QPainterPath 简化为轮廓
【发布时间】:2013-10-31 22:20:14
【问题描述】:

我正在使用 PySide 中的 QGraphicsScene 创建一个自定义绘图应用程序 - 我希望能够绘制复杂的形状并让用户通过鼠标与它们进行交互。为此,我创建了一个名为Node 的自定义QGraphicsItem,它通过返回QPainterPath 对象来定义其形状。图形场景使用此形状来决定鼠标何时进入对象。在对象paint 方法中,我只是使用QPainter 绘制路径。

import math
import sys
import weakref
from numpy import *
from PySide.QtGui import *
from PySide.QtCore import *


class Node(QGraphicsItem):
    Type = QGraphicsItem.UserType+1

    def __init__(self, graphWidget, line):

        super(self.__class__, self).__init__()
        self.line = line
        self.graph = weakref.ref(graphWidget)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.newPos = QPointF()
        self.setZValue(-1)

    def boundingRect(self):
        adjust = 10.0
        return QRectF(self.line[0][0]-adjust, self.line[0][1]-adjust, 2*adjust + self.line[1][0]-self.line[0][0]+100,2*adjust+self.line[1][2]-self.line[0][3]+100)

    def shape(self):
        (x0,y0), (xn,yn) = p0, pn = self.line
        dx,dy = xn-x0, yn-y0
        dV = array([dx,dy])
        mag_dV = linalg.norm(dV)
        radius = 10
        rotation = array( [[0,-1],[1,0]])

        v = dot(rotation, dV) * radius / mag_dV

        startAngle = arctan2(*v) * 180/pi + 90


        path = QPainterPath()
        path.setFillRule(Qt.WindingFill)
        path.moveTo(*p0 - v)
        path.addEllipse( x0-radius, y0-radius, 2*radius, 2*radius)
        path.moveTo(*p0+v)

        path.lineTo( QPoint(*pn+v))

        path.arcTo( xn - radius, yn-radius, 2*radius, 2*radius, startAngle+180, 180)


        path.lineTo(QPoint(*p0-v))

        return path.simplified()

    def paint(self, painter, option, widget):
        painter.setPen(QPen(Qt.black))
        painter.setBrush(Qt.darkGray)
        painter.drawPath(self.shape())


class GraphWidget(QGraphicsView):
    def __init__(self):
        super(self.__class__, self).__init__()

        self.timerId = 0

        scene = QGraphicsScene(self)
        scene.setItemIndexMethod(QGraphicsScene.NoIndex)
        scene.setSceneRect(-200,-200,400,400)
        self.setScene(scene)
        self.setCacheMode(QGraphicsView.CacheBackground)
        self.setRenderHint(QPainter.Antialiasing)

        self.centerNode = Node(self, [[-100,-100],[0,-70]])
        scene.addItem(self.centerNode)

        self.centerNode.setPos(0,0)
        self.scale(0.8,0.8)
        self.setMinimumSize(400,400)
        self.setWindowTitle(self.tr("Elastic Nodes"))



if __name__ == "__main__":
    app = QApplication(sys.argv)
    qsrand(QTime(0,0,0).secsTo(QTime.currentTime()))

    widget = GraphWidget()
    widget.show()

    sys.exit(app.exec_())

我遇到的问题是如何将多边形变成一个实体形状。一个示例形状是这个带有圆角的矩形:

当我用椭圆而不是圆弧绘制圆形末端时,我得到了:

我想要实现的是多边形形状内包含的路径消失了,我所拥有的只是整个形状的轮廓,填充了纯色。我还想摆脱你看到的交替填充。从表面上看,这将允许我创建任意多边形,而无需计算每个精确的角度和交点。

到目前为止我所尝试的:

我尝试的第一件事是使用path.setFillRule(Qt.WindingFill),它应该填充所有内部空间,而不是使用交替颜色。然而,这似乎对改变结果没有任何作用。

我尝试的第二件事是使用“path.simplified()”,它应该完全符合我的要求。该命令的结果是:

我该如何克服这个问题?

【问题讨论】:

  • 不用画椭圆,你可以使用弧线,就像画形状的右端一样
  • 你说得对,我可以用弧线。问题是如果我想制作一个更复杂的形状:例如,假设我想制作第二个与第一个相关联的形状,这样每个元素末端的椭圆都是同心的。为了绘制轮廓,我现在需要考虑 2 个方向,我需要计算线交点,并且需要测量两倍矢量之间的角度。使用省略号会更简洁。我将编辑问题以表明这不是我想要绘制的唯一形状。

标签: python qt user-interface pyqt pyside


【解决方案1】:

好的,所以这个问题的解决方案实际上非常简单:绕着形状走哪条路很重要。

上面绘制的形状基本上是一个沿线段​​定向的矩形,两端各有一个圆。要绘制矩形,您必须使用单独的线段。在原题中,矩形轮廓是逆时针绘制的:

    path.lineTo(QPointF(*p0+v))
    path.lineTo(QPointF(*pn+v))
    path.lineTo(QPointF(*pn-v))
    path.lineTo(QPointF(*p0-v))

这导致重叠部分没有被填充:

如果我改为顺时针绘制矩形轮廓,我就没有这个问题。

    path.lineTo(QPointF(*pn-v))
    path.lineTo(QPointF(*pn+v))
    path.lineTo(QPointF(*p0+v))
    path.lineTo(QPointF(*p0-v))

结果:

所以你有它。下面是我的最终代码,生成了一个很好的复合线段,可以点击和操作。

from PySide import QtGui, QtCore
import math
import sys
import weakref
from numpy import *
from PySide.QtGui import *
from PySide.QtCore import *



class Node(QGraphicsItem):
    Type = QGraphicsItem.UserType+1

    def __init__(self, graphWidget, line):

        super(self.__class__, self).__init__()
        self.line = line
        self.radius = 8

        if graphWidget != None:
            self.graph = weakref.ref(graphWidget)

        self.newPos = QPointF()
        self.setCacheMode(self.DeviceCoordinateCache)
        self.setZValue(-1)



    def boundingRect(self):
        adjust = 10.0
        x,y = array(self.line).transpose()
        rect = [min(x)-self.radius, min(y)-self.radius, max(x)-min(x)+2*self.radius, max(y)-min(y)+2*self.radius]

        return QRectF(*rect)

    def shape(self):
        path = QPainterPath()
        path.setFillRule(Qt.WindingFill)

        radius =  self.radius
        rotation = array( [[0,-1],[1,0]])

        points = zip(self.line[0:-1], self.line[1:])

        for p0, pn in points:
            (x0,y0),(xn,yn) = p0,pn
            dx,dy = array(pn) - array(p0)

            dV = array([dx,dy])
            mag_dV = linalg.norm(dV)

            v = dot(rotation, dV) * radius / mag_dV

            #starting circle
            path.addEllipse(QRectF(x0-radius, y0-radius, 2*radius, 2*radius))
            #rectangular part
            path.moveTo(QPointF(*p0-v))
            path.lineTo(QPointF(*pn-v))
            path.lineTo(QPointF(*pn+v))
            path.lineTo(QPointF(*p0+v))

        path.moveTo(QPointF(*pn))
        path.addEllipse(QRectF(xn-radius, yn-radius, 2*radius, 2*radius))

        return path.simplified()


    def paint(self, painter, option, widget):
        painter.setPen(Qt.black)
        painter.setBrush(Qt.darkGray)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.drawPath(self.shape())

    def setGraph(self, graph):
        self.graph = weakref.ref(graph)

class GraphWidget(QGraphicsView):
    def __init__(self):
        super(self.__class__, self).__init__()

        self.timerId = 0

        scene = QGraphicsScene(self)
        scene.setItemIndexMethod(QGraphicsScene.NoIndex)
        scene.setSceneRect(0,0,880,880)
        self.setScene(scene)
        self.setCacheMode(QGraphicsView.CacheBackground)
        self.setRenderHint(QPainter.Antialiasing)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorViewCenter)

        self.centerNode = Node(self, [[50,50],[200,100], [500, 100], [700,700]])
        scene.addItem(self.centerNode)


        self.centerNode.setPos(0,0)
        self.setGeometry(100,100,900,900)
        self.setWindowTitle(self.tr("Elastic Nodes"))



    def addItem(self, item):
        self.item = item
        self.scene().addItem(item)
        item.setGraph(self)
        item.show()



if __name__ == "__main__":
    app = QApplication(sys.argv)
    qsrand(QTime(0,0,0).secsTo(QTime.currentTime()))

    widget = GraphWidget()
    widget.show()

    sys.exit(app.exec_())  

【讨论】:

    猜你喜欢
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 2013-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-18
    相关资源
    最近更新 更多