Issue
I am trying to modify my code below to take-in mouse events (click, drag, release) such that the control points can be selected and moved, resulting in change in the curve. I am not sure where to begin, any suggestions? The control points are marked as red dots, the curve is in blue.
This would basically let me modify the curve within the gui. Any reference would be appreciated as well.
import sys
import random
import functools
from PyQt5 import QtWidgets, QtGui, QtCore
@functools.lru_cache(maxsize=100)
def factorial(n):
prod = 1
for i in range(1,n+1):
prod *= i
return prod
def randPt(minv, maxv):
return (random.randint(minv, maxv), random.randint(minv, maxv))
def B(i,n,u):
val = factorial(n)/(factorial(i)*factorial(n-i))
return val * (u**i) * ((1-u)**(n-i))
def C(u, pts):
x = 0
y = 0
n = len(pts)-1
for i in range(n+1):
binu = B(i,n,u)
x += binu * pts[i][0]
y += binu * pts[i][1]
return (x, y)
class BezierDrawer(QtWidgets.QWidget):
def __init__(self):
super(BezierDrawer, self).__init__()
self.setGeometry(300, 300, 1500,1000)
self.setWindowTitle('Bezier Curves')
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
qp.setRenderHints(QtGui.QPainter.Antialiasing, True)
self.doDrawing(qp)
qp.end()
def doDrawing(self, qp):
blackPen = QtGui.QPen(QtCore.Qt.black, 3, QtCore.Qt.DashLine)
redPen = QtGui.QPen(QtCore.Qt.red, 30, QtCore.Qt.DashLine)
bluePen = QtGui.QPen(QtCore.Qt.blue, 3, QtCore.Qt.DashLine)
greenPen = QtGui.QPen(QtCore.Qt.green, 3, QtCore.Qt.DashLine)
redBrush = QtGui.QBrush(QtCore.Qt.red)
steps = 400
min_t = 0.0
max_t = 1.0
dt = (max_t - min_t)/steps
# controlPts = [randPt(0,1000) for i in range(6)]
# controlPts.append(controlPts[1])
# controlPts.append(controlPts[0])
controlPts = [(500,500), (600,700), (600,550), (700,500),(700,500), (800,400), (1000,200), (1000,500)]
oldPt = controlPts[0]
pn = 1
qp.setPen(redPen)
qp.setBrush(redBrush)
qp.drawEllipse(oldPt[0]-3, oldPt[1]-3, 6,6)
#qp.drawText(oldPt[0]+5, oldPt[1]-3, '{}'.format(pn))
for pt in controlPts[1:]:
pn+=1
qp.setPen(blackPen)
qp.drawLine(oldPt[0],oldPt[1],pt[0],pt[1])
qp.setPen(redPen)
qp.drawEllipse(pt[0]-3, pt[1]-3, 6,6)
#xv=qp.drawText(pt[0]+5, pt[1]-3, '{}'.format(pn))
#xv.setTextWidth(3)
oldPt = pt
qp.setPen(bluePen)
oldPt = controlPts[0]
for i in range(steps+1):
t = dt*i
pt = C(t, controlPts)
qp.drawLine(oldPt[0],oldPt[1], pt[0],pt[1])
oldPt = pt
def main(args):
app = QtWidgets.QApplication(sys.argv)
ex = BezierDrawer()
ex.show()
app.exec_()
if __name__=='__main__':
main(sys.argv[1:])
Solution
As it was suggested, for more "serious" application, You should go for GraphicsView
.
Your app was however almost done with simple drawing. It's not a big deal to to modify it to work as You want.
I made few changes to Your code. You have to make list of control points as a attribute of Your BezierDrawer
. Then You can use events: mousePressEvent
, mouseMoveEvent
and mouseReleaseEvent
to interact with control points.
First we need to find out, if any point was clicked in mousePressEvent
and then just update it's position while dragging. Every change of point position must end with update
method, to repaint widget.
Here is modified code:
import functools
import random
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
@functools.lru_cache(maxsize=100)
def factorial(n):
prod = 1
for i in range(1, n + 1):
prod *= i
return prod
def randPt(minv, maxv):
return (random.randint(minv, maxv), random.randint(minv, maxv))
def B(i, n, u):
val = factorial(n) / (factorial(i) * factorial(n - i))
return val * (u ** i) * ((1 - u) ** (n - i))
def C(u, pts):
x = 0
y = 0
n = len(pts) - 1
for i in range(n + 1):
binu = B(i, n, u)
x += binu * pts[i][0]
y += binu * pts[i][1]
return (x, y)
class BezierDrawer(QtWidgets.QWidget):
def __init__(self):
super(BezierDrawer, self).__init__()
# Dragged point index
self.dragged_point = None
# List of control points
self.controlPts = [(500, 500), (600, 700), (600, 550), (700, 500), (800, 400), (1000, 200),
(1000, 500)]
self.setGeometry(300, 300, 1500, 1000)
self.setWindowTitle('Bezier Curves')
def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None:
"""Get through all control points and find out if mouse clicked on it"""
for control_point, (x, y) in enumerate(self.controlPts):
if a0.x() - 15 <= x <= a0.x() + 15 and a0.y() - 15 <= y <= a0.y() + 15:
self.dragged_point = control_point
return
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
"""If any point is dragged, change its position and repaint scene"""
if self.dragged_point is not None:
self.controlPts[self.dragged_point] = (a0.x(), a0.y())
self.update()
def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None:
"""Release dragging point and repaint scene again"""
self.dragged_point = None
self.update()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
qp.setRenderHints(QtGui.QPainter.Antialiasing, True)
self.doDrawing(qp)
qp.end()
def doDrawing(self, qp):
blackPen = QtGui.QPen(QtCore.Qt.black, 3, QtCore.Qt.DashLine)
redPen = QtGui.QPen(QtCore.Qt.red, 30, QtCore.Qt.DashLine)
bluePen = QtGui.QPen(QtCore.Qt.blue, 3, QtCore.Qt.DashLine)
greenPen = QtGui.QPen(QtCore.Qt.green, 3, QtCore.Qt.DashLine)
redBrush = QtGui.QBrush(QtCore.Qt.red)
steps = 400
min_t = 0.0
max_t = 1.0
dt = (max_t - min_t) / steps
oldPt = self.controlPts[0]
pn = 1
qp.setPen(redPen)
qp.setBrush(redBrush)
qp.drawEllipse(oldPt[0] - 3, oldPt[1] - 3, 6, 6)
for pt in self.controlPts[1:]:
pn += 1
qp.setPen(blackPen)
qp.drawLine(oldPt[0], oldPt[1], pt[0], pt[1])
qp.setPen(redPen)
qp.drawEllipse(pt[0] - 3, pt[1] - 3, 6, 6)
oldPt = pt
qp.setPen(bluePen)
oldPt = self.controlPts[0]
for i in range(steps + 1):
t = dt * i
pt = C(t, self.controlPts)
qp.drawLine(oldPt[0], oldPt[1], pt[0], pt[1])
oldPt = pt
def main(args):
app = QtWidgets.QApplication(sys.argv)
ex = BezierDrawer()
ex.show()
app.exec_()
if __name__ == '__main__':
main(sys.argv[1:])
Answered By - Domarm
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.