我希望只在特定方向(例如+/- 45度)上水平或垂直拖动QGraphicsItem
,并且一旦拖动光标就能够“跳”到新方向距离当前最近的方向足够远。这将复制例如绘制直线并按住Ctrl
(例如,this video)时会出现墨迹,但是我不确定如何实现。
我已经实现了一个拖动处理程序,该处理程序可以在移动项目时获取项目的新位置:
class Circle(QGraphicsEllipseItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Flags to allow dragging and tracking of dragging.
self.setFlag(self.ItemSendsGeometryChanges)
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
def itemChange(self, change, value):
if change == self.ItemPositionChange and self.isSelected():
# do something...
# Return the new position to parent to have this item move there.
return super().itemChange(change, value)
由于通过此方法返回给父级的位置用于更新场景中项目的位置,因此我希望可以修改此QPointF
以将其限制为一个轴,但是我不确定如何这样做的方式是,一旦将光标拖到足够远的位置,线就会“跳”到另一个方向。是否有针对此类行为的“标准算法”?还是一些内置的Qt代码可以为我做到这一点?
答案 0 :(得分:2)
该问题已减少,可以计算点在该线上的投影(项目的位置)。按照this post中的说明进行一些数学运算。
让p1和p2分别为直线上的两个不同点,而p为该点,则算法为:
e1 = p2 - p1
e2 = p - p1
dp = e1 • e2 # dot product
l = e1 • e1 # dot product
pp = p1 + dp * e1 / l
实施上述解决方案是:
import math
import random
from PyQt5 import QtCore, QtGui, QtWidgets
class Circle(QtWidgets.QGraphicsEllipseItem):
def __init__(self, *args, **kwargs):
self._line = QtCore.QLineF()
super().__init__(*args, **kwargs)
# Flags to allow dragging and tracking of dragging.
self.setFlags(
self.flags()
| QtWidgets.QGraphicsItem.ItemSendsGeometryChanges
| QtWidgets.QGraphicsItem.ItemIsMovable
| QtWidgets.QGraphicsItem.ItemIsSelectable
)
@property
def line(self):
return self._line
@line.setter
def line(self, line):
self._line = line
def itemChange(self, change, value):
if (
change == QtWidgets.QGraphicsItem.ItemPositionChange
and self.isSelected()
and not self.line.isNull()
):
# http://www.sunshine2k.de/coding/java/PointOnLine/PointOnLine.html
p1 = self.line.p1()
p2 = self.line.p2()
e1 = p2 - p1
e2 = value - p1
dp = QtCore.QPointF.dotProduct(e1, e2)
l = QtCore.QPointF.dotProduct(e1, e1)
p = p1 + dp * e1 / l
return p
return super().itemChange(change, value)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
scene = QtWidgets.QGraphicsScene(QtCore.QRectF(-200, -200, 400, 400))
view = QtWidgets.QGraphicsView(scene)
points = (
QtCore.QPointF(*random.sample(range(-150, 150), 2)) for _ in range(4)
)
angles = (math.pi / 4, math.pi / 3, math.pi / 5, math.pi / 2)
for point, angle in zip(points, angles):
item = Circle(QtCore.QRectF(-10, -10, 20, 20))
item.setBrush(QtGui.QColor("salmon"))
scene.addItem(item)
item.setPos(point)
end = 100 * QtCore.QPointF(math.cos(angle), math.sin(angle))
line = QtCore.QLineF(QtCore.QPointF(), end)
item.line = line.translated(item.pos())
line_item = scene.addLine(item.line)
line_item.setPen(QtGui.QPen(QtGui.QColor("green"), 4))
view.resize(640, 480)
view.show()
sys.exit(app.exec_())