沿 QPainterPath 获取点击点
Get Clicked Point Along QPainterPath
如何获得表示沿 QPainterPath 单击的点的百分比。例如,假设我有一条线,如下图所示,用户点击了用红点表示的 QPainterPath。我想记录该点沿路径下降的百分比。在这种情况下,它将打印 0.75,因为该点位于 75% 左右。
这些是已知变量:
# QPainterPath
path = QPainterPath()
path.moveTo( QPointF(10.00, -10.00) )
path.cubicTo(
QPointF(114.19, -10.00),
QPointF(145.80, -150.00),
QPointF(250.00, -150.00)
)
# User Clicked Point
QPointF(187.00, -130.00)
已更新!
我的目标是让用户能够单击路径并插入一个点。以下是我到目前为止的代码。您会在视频中看到,在点之间添加点时似乎失败了。只需单击路径即可插入一个点。
视频 Link 观看错误:
import sys
from PySide.QtGui import *
from PySide.QtCore import *
import random, math
class MyGraphicsView(QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setCacheMode(QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff )
self.setVerticalScrollBarPolicy( Qt.ScrollBarAlwaysOff )
def mousePressEvent(self, event):
item = self.itemAt(event.pos())
if event.button() == Qt.LeftButton and isinstance(item, ConnectionItem):
percentage = self.percentageByPoint(item.shape(), self.mapToScene(event.pos()))
item.addKnotByPercent(percentage)
event.accept()
elif event.button() == Qt.MiddleButton:
super(MyGraphicsView, self).mousePressEvent(event)
# connection methods
def percentageByPoint(self, path, point, precision=0.5, width=3.0):
percentage = -1.0
if path.contains(point):
t = 0.0
d = []
while t <=100.0:
d.append(QVector2D(point - path.pointAtPercent(t/100.0)).length())
t += precision
percentage = d.index(min(d))*precision
return percentage
class MyGraphicsScene(QGraphicsScene):
def __init__(self, parent):
super(MyGraphicsScene, self).__init__()
self.setBackgroundBrush(QBrush(QColor(50,50,50)))
class KnotItem(QGraphicsEllipseItem):
def __init__(self, parent=None,):
super(self.__class__, self).__init__(parent)
self.setAcceptHoverEvents(True)
self.setFlag(self.ItemSendsScenePositionChanges, True)
self.setFlag(self.ItemIsSelectable, True) # false
self.setFlag(self.ItemIsMovable, True) # false
self.setRect(-6, -6, 12, 12)
# Overrides
def paint(self, painter, option, widget=None):
painter.save()
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(QColor(30,30,30), 2, Qt.SolidLine))
painter.setBrush(QBrush(QColor(255,30,30)))
painter.drawEllipse(self.rect())
painter.restore()
def itemChange(self, change, value):
if change == self.ItemScenePositionHasChanged:
if self.parentItem():
self.parentItem().update()
return super(self.__class__, self).itemChange(change, value)
def boundingRect(self):
rect = self.rect()
rect.adjust(-1,-1,1,1)
return rect
class ConnectionItem(QGraphicsPathItem):
def __init__(self, startPoint, endPoint, parent=None):
super(ConnectionItem, self).__init__()
self._hover = False
self.setAcceptHoverEvents(True)
self.setFlag( QGraphicsItem.ItemIsSelectable )
self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True)
self.setZValue(-100)
self.startPoint = startPoint
self.endPoint = endPoint
self.knots = []
self.update()
def getBezierPath(self, points=[], curving=1.0):
# Calculate Bezier Line
path = QPainterPath()
curving = 1.0 # range 0-1
if len(points) < 2:
return path
path.moveTo(points[0])
for i in range(len(points)-1):
startPoint = points[i]
endPoint = points[i+1]
# use distance as mult, closer the nodes less the bezier
dist = math.hypot(endPoint.x() - startPoint.x(), endPoint.y() - startPoint.y())
# multiply distance by 0.375
offset = dist * 0.375 * curving
ctrlPt1 = startPoint + QPointF(offset,0);
ctrlPt2 = endPoint + QPointF(-offset,0);
# print startPoint, ctrlPt1, ctrlPt2, endPoint
path.cubicTo(ctrlPt1, ctrlPt2, endPoint)
return path
def drawPath(self, pos=None):
# Calculate Bezier Line
points = [self.startPoint]
for k in self.knots:
points.append(k.scenePos())
points.append(self.endPoint)
path = self.getBezierPath(points)
self.setPath(path)
def update(self):
super(self.__class__, self).update()
self.drawPath()
def paint(self, painter, option, widget):
painter.setRenderHints( QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing, True )
pen = QPen(QColor(170,170,170), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
if self.isSelected():
pen.setColor(QColor(255, 255, 255))
elif self.hover:
pen.setColor(QColor(255, 30, 30))
painter.setPen(pen)
painter.drawPath(self.path())
def shape(self):
'''
Description:
This is super important for creating a more accurate path used for
collision detection by cursor.
'''
qp = QPainterPathStroker()
qp.setWidth(15)
qp.setCapStyle(Qt.SquareCap)
return qp.createStroke(self.path())
def hoverEnterEvent(self, event):
self.hover = True
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self.hover = False
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def addKnot(self, pos=QPointF(0,0)):
'''
Description:
Add not based on current location of cursor or inbetween points on path.
'''
knotItem = KnotItem(parent=self)
knotItem.setPos(pos)
self.knots.append(knotItem)
self.update()
def addKnotByPercent(self, percentage=0.0):
'''
Description:
The percentage value should be between 0.0 and 100.0. This value
determines the location of the point and it's index in the knots list.
'''
if percentage < 0.0 or percentage > 100.0:
return
# add item
pos = self.shape().pointAtPercent(percentage*.01)
knotItem = KnotItem(parent=self)
knotItem.setPos(pos)
index = int(len(self.knots) * (percentage*.01))
print len(self.knots), (percentage), index
self.knots.insert(index, knotItem)
self.update()
# properties
@property
def hover(self):
return self._hover
@hover.setter
def hover(self, value=False):
self._hover = value
self.update()
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.setWindowTitle("Test")
self.resize(800,600)
self.gv = MyGraphicsView()
self.gv.setScene(MyGraphicsScene(self))
self.btnReset = QPushButton('Reset')
lay_main = QVBoxLayout()
lay_main.addWidget(self.btnReset)
lay_main.addWidget(self.gv)
widget_main = QWidget()
widget_main.setLayout(lay_main)
self.setCentralWidget(widget_main)
self.populate()
# connect
self.btnReset.clicked.connect(self.populate)
def populate(self):
scene = self.gv.scene()
for x in scene.items():
scene.removeItem(x)
del x
con = ConnectionItem(QPointF(-150,150), QPointF(250,-150))
scene.addItem(con)
def main():
app = QApplication(sys.argv)
ex = MyMainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
一个可能的解决方案是使用pointAtPercent() returns给定点一个百分比并计算到该点的距离并找到最小索引并将其乘以步长。但是为此必须改进搜索,因为以前的算法适用于任何点,即使它在路径之外。在这种情况下,想法是使用 QPainterPathStroker 将 QPainterPath 与特定区域一起使用,并验证该点是否属于,如果不属于,则该值在 QPainterPath 之外。
C++
#include <QtGui>
static qreal percentageByPoint(const QPainterPath & path, const QPointF & p, qreal precision=0.5, qreal width=3.0){
qreal percentage = -1;
QPainterPathStroker stroker;
stroker.setWidth(width);
QPainterPath strokepath = stroker.createStroke(path);
if(strokepath.contains(p)){
std::vector<qreal> d;
qreal t=0.0;
while(t<=100.0){
d.push_back(QVector2D(p - path.pointAtPercent(t/100)).length());
t+= precision;
}
std::vector<qreal>::iterator result = std::min_element(d.begin(), d.end());
int j= std::distance(d.begin(), result);
percentage = j*precision;
}
return percentage;
}
int main(int argc, char *argv[])
{
Q_UNUSED(argc)
Q_UNUSED(argv)
QPainterPath path;
path.moveTo( QPointF(10.00, -10.00) );
path.cubicTo(
QPointF(114.19, -10.00),
QPointF(145.80, -150.00),
QPointF(250.00, -150.00)
);
// User Clicked Point
QPointF p(187.00, -130.00);
qreal percentage = percentageByPoint(path, p);
qDebug() << percentage;
return 0;
}
python:
def percentageByPoint(path, point, precision=0.5, width=3.0):
percentage = -1.0
stroker = QtGui.QPainterPathStroker()
stroker.setWidth(width)
strokepath = stroker.createStroke(path)
if strokepath.contains(point):
t = 0.0
d = []
while t <=100.0:
d.append(QtGui.QVector2D(point - path.pointAtPercent(t/100)).length())
t += precision
percentage = d.index(min(d))*precision
return percentage
if __name__ == '__main__':
path = QtGui.QPainterPath()
path.moveTo(QtCore.QPointF(10.00, -10.00) )
path.cubicTo(
QtCore.QPointF(114.19, -10.00),
QtCore.QPointF(145.80, -150.00),
QtCore.QPointF(250.00, -150.00)
)
point = QtCore.QPointF(187.00, -130.00)
percentage = percentageByPoint(path, point)
print(percentage)
输出:
76.5
不是在 QGraphicsView 中实现逻辑,而是必须在项目中执行,然后在更新路径时,必须根据百分比对点进行排序。
import math
from PySide import QtCore, QtGui
from functools import partial
class MyGraphicsView(QtGui.QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
self.setCacheMode(QtGui.QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
scene = QtGui.QGraphicsScene(self)
scene.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(50,50,50)))
self.setScene(scene)
class KnotItem(QtGui.QGraphicsEllipseItem):
def __init__(self, parent=None,):
super(self.__class__, self).__init__(parent)
self.setAcceptHoverEvents(True)
self.setFlag(self.ItemSendsScenePositionChanges, True)
self.setFlag(self.ItemIsSelectable, True)
self.setFlag(self.ItemIsMovable, True)
self.setRect(-6, -6, 12, 12)
self.setPen(QtGui.QPen(QtGui.QColor(30,30,30), 2, QtCore.Qt.SolidLine))
self.setBrush(QtGui.QBrush(QtGui.QColor(255,30,30)))
def itemChange(self, change, value):
if change == self.ItemScenePositionHasChanged:
if isinstance(self.parentItem(), ConnectionItem):
self.parentItem().updatePath()
# QtCore.QTimer.singleShot(60, partial(self.parentItem().setSelected,False))
return super(self.__class__, self).itemChange(change, value)
class ConnectionItem(QtGui.QGraphicsPathItem):
def __init__(self, startPoint, endPoint, parent=None):
super(ConnectionItem, self).__init__(parent)
self._start_point = startPoint
self._end_point = endPoint
self._hover = False
self.setAcceptHoverEvents(True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable )
self.setFlag(QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
self.setZValue(-100)
self.updatePath()
def updatePath(self):
p = [self._start_point]
for children in self.childItems():
if isinstance(children, KnotItem):
p.append(children.pos())
p.append(self._end_point)
v = sorted(p, key=partial(ConnectionItem.percentageByPoint, self.path()))
self.setPath(ConnectionItem.getBezierPath(v))
def paint(self, painter, option, widget):
painter.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform | QtGui.QPainter.HighQualityAntialiasing, True )
pen = QtGui.QPen(QtGui.QColor(170,170,170), 2, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
if self.isSelected():
pen.setColor(QtGui.QColor(255, 255, 255))
elif self._hover:
pen.setColor(QtGui.QColor(255, 30, 30))
painter.setPen(pen)
painter.drawPath(self.path())
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
item = KnotItem(parent=self)
item.setPos(event.pos())
def hoverEnterEvent(self, event):
self._hover = True
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self._hover = False
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def shape(self):
qp = QtGui.QPainterPathStroker()
qp.setWidth(15)
qp.setCapStyle(QtCore.Qt.SquareCap)
return qp.createStroke(self.path())
@staticmethod
def getBezierPath(points=[], curving=1.0):
# Calculate Bezier Line
path = QtGui.QPainterPath()
curving = 1.0 # range 0-1
if len(points) < 2:
return path
path.moveTo(points[0])
for i in range(len(points)-1):
startPoint = points[i]
endPoint = points[i+1]
# use distance as mult, closer the nodes less the bezier
dist = math.hypot(endPoint.x() - startPoint.x(), endPoint.y() - startPoint.y())
# multiply distance by 0.375
offset = dist * 0.375 * curving
ctrlPt1 = startPoint + QtCore.QPointF(offset,0);
ctrlPt2 = endPoint + QtCore.QPointF(-offset,0);
# print startPoint, ctrlPt1, ctrlPt2, endPoint
path.cubicTo(ctrlPt1, ctrlPt2, endPoint)
return path
@staticmethod
def percentageByPoint(path, point, precision=0.5):
t = 0.0
d = []
while t <=100.0:
d.append(QtGui.QVector2D(point - path.pointAtPercent(t/100.0)).length())
t += precision
percentage = d.index(min(d))*precision
return percentage
class MyMainWindow(QtGui.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
central_widget = QtGui.QWidget()
self.setCentralWidget(central_widget)
button = QtGui.QPushButton("Reset")
self._view = MyGraphicsView()
button.clicked.connect(self.reset)
lay = QtGui.QVBoxLayout(central_widget)
lay.addWidget(button)
lay.addWidget(self._view)
self.resize(640, 480)
self.reset()
@QtCore.Slot()
def reset(self):
self._view.scene().clear()
it = ConnectionItem(QtCore.QPointF(-150,150), QtCore.QPointF(250,-150))
self._view.scene().addItem(it)
def main():
import sys
app =QtGui.QApplication(sys.argv)
ex = MyMainWindow()
ex.show()
sys.exit(app.exec_())
main()
如何获得表示沿 QPainterPath 单击的点的百分比。例如,假设我有一条线,如下图所示,用户点击了用红点表示的 QPainterPath。我想记录该点沿路径下降的百分比。在这种情况下,它将打印 0.75,因为该点位于 75% 左右。
这些是已知变量:
# QPainterPath
path = QPainterPath()
path.moveTo( QPointF(10.00, -10.00) )
path.cubicTo(
QPointF(114.19, -10.00),
QPointF(145.80, -150.00),
QPointF(250.00, -150.00)
)
# User Clicked Point
QPointF(187.00, -130.00)
已更新!
我的目标是让用户能够单击路径并插入一个点。以下是我到目前为止的代码。您会在视频中看到,在点之间添加点时似乎失败了。只需单击路径即可插入一个点。
视频 Link 观看错误:
import sys
from PySide.QtGui import *
from PySide.QtCore import *
import random, math
class MyGraphicsView(QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setCacheMode(QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff )
self.setVerticalScrollBarPolicy( Qt.ScrollBarAlwaysOff )
def mousePressEvent(self, event):
item = self.itemAt(event.pos())
if event.button() == Qt.LeftButton and isinstance(item, ConnectionItem):
percentage = self.percentageByPoint(item.shape(), self.mapToScene(event.pos()))
item.addKnotByPercent(percentage)
event.accept()
elif event.button() == Qt.MiddleButton:
super(MyGraphicsView, self).mousePressEvent(event)
# connection methods
def percentageByPoint(self, path, point, precision=0.5, width=3.0):
percentage = -1.0
if path.contains(point):
t = 0.0
d = []
while t <=100.0:
d.append(QVector2D(point - path.pointAtPercent(t/100.0)).length())
t += precision
percentage = d.index(min(d))*precision
return percentage
class MyGraphicsScene(QGraphicsScene):
def __init__(self, parent):
super(MyGraphicsScene, self).__init__()
self.setBackgroundBrush(QBrush(QColor(50,50,50)))
class KnotItem(QGraphicsEllipseItem):
def __init__(self, parent=None,):
super(self.__class__, self).__init__(parent)
self.setAcceptHoverEvents(True)
self.setFlag(self.ItemSendsScenePositionChanges, True)
self.setFlag(self.ItemIsSelectable, True) # false
self.setFlag(self.ItemIsMovable, True) # false
self.setRect(-6, -6, 12, 12)
# Overrides
def paint(self, painter, option, widget=None):
painter.save()
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(QColor(30,30,30), 2, Qt.SolidLine))
painter.setBrush(QBrush(QColor(255,30,30)))
painter.drawEllipse(self.rect())
painter.restore()
def itemChange(self, change, value):
if change == self.ItemScenePositionHasChanged:
if self.parentItem():
self.parentItem().update()
return super(self.__class__, self).itemChange(change, value)
def boundingRect(self):
rect = self.rect()
rect.adjust(-1,-1,1,1)
return rect
class ConnectionItem(QGraphicsPathItem):
def __init__(self, startPoint, endPoint, parent=None):
super(ConnectionItem, self).__init__()
self._hover = False
self.setAcceptHoverEvents(True)
self.setFlag( QGraphicsItem.ItemIsSelectable )
self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True)
self.setZValue(-100)
self.startPoint = startPoint
self.endPoint = endPoint
self.knots = []
self.update()
def getBezierPath(self, points=[], curving=1.0):
# Calculate Bezier Line
path = QPainterPath()
curving = 1.0 # range 0-1
if len(points) < 2:
return path
path.moveTo(points[0])
for i in range(len(points)-1):
startPoint = points[i]
endPoint = points[i+1]
# use distance as mult, closer the nodes less the bezier
dist = math.hypot(endPoint.x() - startPoint.x(), endPoint.y() - startPoint.y())
# multiply distance by 0.375
offset = dist * 0.375 * curving
ctrlPt1 = startPoint + QPointF(offset,0);
ctrlPt2 = endPoint + QPointF(-offset,0);
# print startPoint, ctrlPt1, ctrlPt2, endPoint
path.cubicTo(ctrlPt1, ctrlPt2, endPoint)
return path
def drawPath(self, pos=None):
# Calculate Bezier Line
points = [self.startPoint]
for k in self.knots:
points.append(k.scenePos())
points.append(self.endPoint)
path = self.getBezierPath(points)
self.setPath(path)
def update(self):
super(self.__class__, self).update()
self.drawPath()
def paint(self, painter, option, widget):
painter.setRenderHints( QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing, True )
pen = QPen(QColor(170,170,170), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
if self.isSelected():
pen.setColor(QColor(255, 255, 255))
elif self.hover:
pen.setColor(QColor(255, 30, 30))
painter.setPen(pen)
painter.drawPath(self.path())
def shape(self):
'''
Description:
This is super important for creating a more accurate path used for
collision detection by cursor.
'''
qp = QPainterPathStroker()
qp.setWidth(15)
qp.setCapStyle(Qt.SquareCap)
return qp.createStroke(self.path())
def hoverEnterEvent(self, event):
self.hover = True
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self.hover = False
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def addKnot(self, pos=QPointF(0,0)):
'''
Description:
Add not based on current location of cursor or inbetween points on path.
'''
knotItem = KnotItem(parent=self)
knotItem.setPos(pos)
self.knots.append(knotItem)
self.update()
def addKnotByPercent(self, percentage=0.0):
'''
Description:
The percentage value should be between 0.0 and 100.0. This value
determines the location of the point and it's index in the knots list.
'''
if percentage < 0.0 or percentage > 100.0:
return
# add item
pos = self.shape().pointAtPercent(percentage*.01)
knotItem = KnotItem(parent=self)
knotItem.setPos(pos)
index = int(len(self.knots) * (percentage*.01))
print len(self.knots), (percentage), index
self.knots.insert(index, knotItem)
self.update()
# properties
@property
def hover(self):
return self._hover
@hover.setter
def hover(self, value=False):
self._hover = value
self.update()
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.setWindowTitle("Test")
self.resize(800,600)
self.gv = MyGraphicsView()
self.gv.setScene(MyGraphicsScene(self))
self.btnReset = QPushButton('Reset')
lay_main = QVBoxLayout()
lay_main.addWidget(self.btnReset)
lay_main.addWidget(self.gv)
widget_main = QWidget()
widget_main.setLayout(lay_main)
self.setCentralWidget(widget_main)
self.populate()
# connect
self.btnReset.clicked.connect(self.populate)
def populate(self):
scene = self.gv.scene()
for x in scene.items():
scene.removeItem(x)
del x
con = ConnectionItem(QPointF(-150,150), QPointF(250,-150))
scene.addItem(con)
def main():
app = QApplication(sys.argv)
ex = MyMainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
一个可能的解决方案是使用pointAtPercent() returns给定点一个百分比并计算到该点的距离并找到最小索引并将其乘以步长。但是为此必须改进搜索,因为以前的算法适用于任何点,即使它在路径之外。在这种情况下,想法是使用 QPainterPathStroker 将 QPainterPath 与特定区域一起使用,并验证该点是否属于,如果不属于,则该值在 QPainterPath 之外。
C++
#include <QtGui>
static qreal percentageByPoint(const QPainterPath & path, const QPointF & p, qreal precision=0.5, qreal width=3.0){
qreal percentage = -1;
QPainterPathStroker stroker;
stroker.setWidth(width);
QPainterPath strokepath = stroker.createStroke(path);
if(strokepath.contains(p)){
std::vector<qreal> d;
qreal t=0.0;
while(t<=100.0){
d.push_back(QVector2D(p - path.pointAtPercent(t/100)).length());
t+= precision;
}
std::vector<qreal>::iterator result = std::min_element(d.begin(), d.end());
int j= std::distance(d.begin(), result);
percentage = j*precision;
}
return percentage;
}
int main(int argc, char *argv[])
{
Q_UNUSED(argc)
Q_UNUSED(argv)
QPainterPath path;
path.moveTo( QPointF(10.00, -10.00) );
path.cubicTo(
QPointF(114.19, -10.00),
QPointF(145.80, -150.00),
QPointF(250.00, -150.00)
);
// User Clicked Point
QPointF p(187.00, -130.00);
qreal percentage = percentageByPoint(path, p);
qDebug() << percentage;
return 0;
}
python:
def percentageByPoint(path, point, precision=0.5, width=3.0):
percentage = -1.0
stroker = QtGui.QPainterPathStroker()
stroker.setWidth(width)
strokepath = stroker.createStroke(path)
if strokepath.contains(point):
t = 0.0
d = []
while t <=100.0:
d.append(QtGui.QVector2D(point - path.pointAtPercent(t/100)).length())
t += precision
percentage = d.index(min(d))*precision
return percentage
if __name__ == '__main__':
path = QtGui.QPainterPath()
path.moveTo(QtCore.QPointF(10.00, -10.00) )
path.cubicTo(
QtCore.QPointF(114.19, -10.00),
QtCore.QPointF(145.80, -150.00),
QtCore.QPointF(250.00, -150.00)
)
point = QtCore.QPointF(187.00, -130.00)
percentage = percentageByPoint(path, point)
print(percentage)
输出:
76.5
不是在 QGraphicsView 中实现逻辑,而是必须在项目中执行,然后在更新路径时,必须根据百分比对点进行排序。
import math
from PySide import QtCore, QtGui
from functools import partial
class MyGraphicsView(QtGui.QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
self.setCacheMode(QtGui.QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
scene = QtGui.QGraphicsScene(self)
scene.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(50,50,50)))
self.setScene(scene)
class KnotItem(QtGui.QGraphicsEllipseItem):
def __init__(self, parent=None,):
super(self.__class__, self).__init__(parent)
self.setAcceptHoverEvents(True)
self.setFlag(self.ItemSendsScenePositionChanges, True)
self.setFlag(self.ItemIsSelectable, True)
self.setFlag(self.ItemIsMovable, True)
self.setRect(-6, -6, 12, 12)
self.setPen(QtGui.QPen(QtGui.QColor(30,30,30), 2, QtCore.Qt.SolidLine))
self.setBrush(QtGui.QBrush(QtGui.QColor(255,30,30)))
def itemChange(self, change, value):
if change == self.ItemScenePositionHasChanged:
if isinstance(self.parentItem(), ConnectionItem):
self.parentItem().updatePath()
# QtCore.QTimer.singleShot(60, partial(self.parentItem().setSelected,False))
return super(self.__class__, self).itemChange(change, value)
class ConnectionItem(QtGui.QGraphicsPathItem):
def __init__(self, startPoint, endPoint, parent=None):
super(ConnectionItem, self).__init__(parent)
self._start_point = startPoint
self._end_point = endPoint
self._hover = False
self.setAcceptHoverEvents(True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable )
self.setFlag(QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
self.setZValue(-100)
self.updatePath()
def updatePath(self):
p = [self._start_point]
for children in self.childItems():
if isinstance(children, KnotItem):
p.append(children.pos())
p.append(self._end_point)
v = sorted(p, key=partial(ConnectionItem.percentageByPoint, self.path()))
self.setPath(ConnectionItem.getBezierPath(v))
def paint(self, painter, option, widget):
painter.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform | QtGui.QPainter.HighQualityAntialiasing, True )
pen = QtGui.QPen(QtGui.QColor(170,170,170), 2, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
if self.isSelected():
pen.setColor(QtGui.QColor(255, 255, 255))
elif self._hover:
pen.setColor(QtGui.QColor(255, 30, 30))
painter.setPen(pen)
painter.drawPath(self.path())
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
item = KnotItem(parent=self)
item.setPos(event.pos())
def hoverEnterEvent(self, event):
self._hover = True
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self._hover = False
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def shape(self):
qp = QtGui.QPainterPathStroker()
qp.setWidth(15)
qp.setCapStyle(QtCore.Qt.SquareCap)
return qp.createStroke(self.path())
@staticmethod
def getBezierPath(points=[], curving=1.0):
# Calculate Bezier Line
path = QtGui.QPainterPath()
curving = 1.0 # range 0-1
if len(points) < 2:
return path
path.moveTo(points[0])
for i in range(len(points)-1):
startPoint = points[i]
endPoint = points[i+1]
# use distance as mult, closer the nodes less the bezier
dist = math.hypot(endPoint.x() - startPoint.x(), endPoint.y() - startPoint.y())
# multiply distance by 0.375
offset = dist * 0.375 * curving
ctrlPt1 = startPoint + QtCore.QPointF(offset,0);
ctrlPt2 = endPoint + QtCore.QPointF(-offset,0);
# print startPoint, ctrlPt1, ctrlPt2, endPoint
path.cubicTo(ctrlPt1, ctrlPt2, endPoint)
return path
@staticmethod
def percentageByPoint(path, point, precision=0.5):
t = 0.0
d = []
while t <=100.0:
d.append(QtGui.QVector2D(point - path.pointAtPercent(t/100.0)).length())
t += precision
percentage = d.index(min(d))*precision
return percentage
class MyMainWindow(QtGui.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
central_widget = QtGui.QWidget()
self.setCentralWidget(central_widget)
button = QtGui.QPushButton("Reset")
self._view = MyGraphicsView()
button.clicked.connect(self.reset)
lay = QtGui.QVBoxLayout(central_widget)
lay.addWidget(button)
lay.addWidget(self._view)
self.resize(640, 480)
self.reset()
@QtCore.Slot()
def reset(self):
self._view.scene().clear()
it = ConnectionItem(QtCore.QPointF(-150,150), QtCore.QPointF(250,-150))
self._view.scene().addItem(it)
def main():
import sys
app =QtGui.QApplication(sys.argv)
ex = MyMainWindow()
ex.show()
sys.exit(app.exec_())
main()