Added comments to the button editor

This commit is contained in:
Daniel Carta
2024-07-25 10:34:40 -04:00
parent 19cd66551a
commit 6f9829f4f8
+58 -20
View File
@@ -5,12 +5,19 @@ from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QV
from PySide6.QtGui import QPixmap, QPainter, QPen, QColor from PySide6.QtGui import QPixmap, QPainter, QPen, QColor
from PySide6.QtCore import Qt, QPoint, QRect from PySide6.QtCore import Qt, QPoint, QRect
'''
This window is the button editor
The button editor takes the path that is made in the path planner and lets the user fine tune the auto by adding button inputs, changing timing, etc...
'''
class ButtonEditor(QMainWindow): class ButtonEditor(QMainWindow):
def __init__(self, pathPlanner): def __init__(self, pathPlanner):
super().__init__() super().__init__()
# Initialization
self.setWindowTitle("Button Editor") self.setWindowTitle("Button Editor")
self.pathPlanner = pathPlanner self.pathPlanner = pathPlanner
# Background / Field setup
self.imageLabel = QLabel(self) self.imageLabel = QLabel(self)
scriptDir = os.path.dirname(os.path.abspath(__file__)) scriptDir = os.path.dirname(os.path.abspath(__file__))
imagePath = os.path.join(scriptDir, "images", "Field.png") imagePath = os.path.join(scriptDir, "images", "Field.png")
@@ -21,58 +28,66 @@ class ButtonEditor(QMainWindow):
else: else:
self.imageLabel.setPixmap(self.pixmap) self.imageLabel.setPixmap(self.pixmap)
# Buttons at the top of the screen
self.pathPlannerButton = QPushButton("Main Window") self.pathPlannerButton = QPushButton("Main Window")
self.pathPlannerButton.clicked.connect(self.showPathPlanner) self.pathPlannerButton.clicked.connect(self.showPathPlanner)
self.buttonEditorButton = QPushButton("Button Editor") self.buttonEditorButton = QPushButton("Button Editor")
self.buttonEditorButton.clicked.connect(self.showButtonEditor) self.buttonEditorButton.clicked.connect(self.showButtonEditor)
# Button layouts
buttonLayout = QHBoxLayout() buttonLayout = QHBoxLayout()
buttonLayout.addWidget(self.pathPlannerButton) buttonLayout.addWidget(self.pathPlannerButton)
buttonLayout.addWidget(self.buttonEditorButton) buttonLayout.addWidget(self.buttonEditorButton)
# Adding the button layout to the main layout
layout = QVBoxLayout() layout = QVBoxLayout()
layout.addLayout(buttonLayout) layout.addLayout(buttonLayout)
layout.addWidget(self.imageLabel) layout.addWidget(self.imageLabel)
# Adding the time text to the layout
self.timeLabel = QLabel("(0:00 / 0:15 sec)", self) self.timeLabel = QLabel("(0:00 / 0:15 sec)", self)
layout.addWidget(self.timeLabel, alignment=Qt.AlignCenter) layout.addWidget(self.timeLabel, alignment=Qt.AlignCenter)
# Defining the overall layout
self.rectanglesWidget = QWidget() self.rectanglesWidget = QWidget()
self.rectanglesLayout = QHBoxLayout() self.rectanglesLayout = QHBoxLayout()
self.rectanglesLayout.setSpacing(0) self.rectanglesLayout.setSpacing(0)
self.rectanglesLayout.setContentsMargins(40, 0, 0, 0) self.rectanglesLayout.setContentsMargins(40, 0, 0, 0)
self.rectanglesWidget.setLayout(self.rectanglesLayout) self.rectanglesWidget.setLayout(self.rectanglesLayout)
layout.addWidget(self.rectanglesWidget) layout.addWidget(self.rectanglesWidget)
container = QWidget() container = QWidget()
container.setLayout(layout) container.setLayout(layout)
self.setCentralWidget(container) self.setCentralWidget(container)
# Resize the window to all of the layouts and widgets added
self.resize(self.pixmap.width(), self.pixmap.height() + 250) self.resize(self.pixmap.width(), self.pixmap.height() + 250)
# Variables # Variables
self.matchLength = 15 self.matchLength = 15 # How many seconds the auto lasts (it's always 15)
self.TPS = 50 self.TPS = 50 # Ticks per second (the robot runs at 50 tps)
self.matchTicks = self.matchLength * self.TPS self.matchTicks = self.matchLength * self.TPS # The amount of ticks in a match
self.displayTickResolution = 6.25 self.displayTickResolution = 6.25 # The number of ticks to divide the match ticks by
self.displayTicks = round(self.matchTicks / self.displayTickResolution) self.displayTicks = round(self.matchTicks / self.displayTickResolution) # How many ticks to show the user
self.currentFrame = 1 self.currentFrame = 1 # The frame that is currently selected
self.keyFrameData = [{"isNode": False, "isButton": False} for _ in range(self.displayTicks)] self.keyFrameData = [{"isNode": False, "isButton": False} for _ in range(self.displayTicks)] # Dictionary for every tick including if that tick is a node frame or a button frame
self.updateKeyFrameData() self.updateKeyFrameData()
self.displayFrames = list(range(1, self.displayTicks + 1)) self.displayFrames = list(range(1, self.displayTicks + 1)) # The list of display frames
self.currentTime = [i * self.matchLength / (self.displayTicks - 1) for i in range(self.displayTicks)] self.currentTime = [i * self.matchLength / (self.displayTicks - 1) for i in range(self.displayTicks)] # Divides the amount of frames by the match length to put a time for every frame
num_nodes = 2 # Setting up the nodes from the path planner
num_nodes = 2 # The number of nodes in the path planner
node_indices = np.linspace(0, self.displayTicks - 1, num_nodes, dtype=int) node_indices = np.linspace(0, self.displayTicks - 1, num_nodes, dtype=int)
for idx in node_indices: for idx in node_indices:
self.keyFrameData[idx]["isNode"] = True self.keyFrameData[idx]["isNode"] = True
# Initialization
self.setupFrames() self.setupFrames()
self.updateRectangles() self.updateRectangles()
self.updateTimeLabel() self.updateTimeLabel()
# Updating the key frames with data from the path planner
def updateKeyFrameData(self): def updateKeyFrameData(self):
num_nodes = len(self.pathPlanner.coordinates) if self.pathPlanner else 0 num_nodes = len(self.pathPlanner.coordinates) if self.pathPlanner else 0
self.keyFrameData = [{"isNode": False, "isButton": False} for _ in range(self.displayTicks)] self.keyFrameData = [{"isNode": False, "isButton": False} for _ in range(self.displayTicks)]
@@ -82,34 +97,40 @@ class ButtonEditor(QMainWindow):
for idx in node_indices: for idx in node_indices:
self.keyFrameData[idx]["isNode"] = True self.keyFrameData[idx]["isNode"] = True
# Resizing the rectangles and updating the time on call
def resizeEvent(self, event): def resizeEvent(self, event):
super().resizeEvent(event) super().resizeEvent(event)
self.updateRectangles() self.updateRectangles()
self.updateTimeLabel() self.updateTimeLabel()
# Setup for the display frames
def setupFrames(self): def setupFrames(self):
self.displayFrames = list(range(1, self.displayTicks + 1)) self.displayFrames = list(range(1, self.displayTicks + 1))
print(len(self.keyFrameData))
print(len(self.displayFrames))
# Move to the button editor window
def showButtonEditor(self): def showButtonEditor(self):
self.show() self.show()
if self.pathPlanner: if self.pathPlanner:
self.pathPlanner.hide() self.pathPlanner.hide()
# Move to the path planner window
def showPathPlanner(self): def showPathPlanner(self):
self.hide() self.hide()
if self.pathPlanner: if self.pathPlanner:
self.pathPlanner.show() self.pathPlanner.show()
# Updates the scene with all of the data from the path planner and draw the scene
def updateScene(self): def updateScene(self):
self.updateKeyFrameData() self.updateKeyFrameData() # Make sure key frame data is up to date
# Draw the field
self.pixmap = QPixmap(os.path.join(os.path.dirname(os.path.abspath(__file__)), "images", "Field.png")) self.pixmap = QPixmap(os.path.join(os.path.dirname(os.path.abspath(__file__)), "images", "Field.png"))
painter = QPainter(self.pixmap) painter = QPainter(self.pixmap)
painter.setPen(Qt.NoPen) painter.setPen(Qt.NoPen)
painter.setBrush(Qt.white) painter.setBrush(Qt.white)
# Draw the nodes from the path planner onto the button editor
if self.pathPlanner and hasattr(self.pathPlanner, 'coordinates'): if self.pathPlanner and hasattr(self.pathPlanner, 'coordinates'):
for i, (x, y) in enumerate(self.pathPlanner.coordinates): for i, (x, y) in enumerate(self.pathPlanner.coordinates):
nodeRect = QRect(x - self.pathPlanner.nodeSize // 6, y - self.pathPlanner.nodeSize // 6, nodeRect = QRect(x - self.pathPlanner.nodeSize // 6, y - self.pathPlanner.nodeSize // 6,
@@ -117,17 +138,19 @@ class ButtonEditor(QMainWindow):
painter.drawEllipse(nodeRect) painter.drawEllipse(nodeRect)
painter.drawText(nodeRect, Qt.AlignCenter, str(i + 1)) painter.drawText(nodeRect, Qt.AlignCenter, str(i + 1))
# Draw points on the bezier curve in between the nodes (Where the robot is going to go)
if len(self.pathPlanner.coordinates) > 1: if len(self.pathPlanner.coordinates) > 1:
total_points = 60 total_points = 60 # The amount of points to draw
points_per_curve = total_points // len(self.pathPlanner.controlPoints) points_per_curve = total_points // len(self.pathPlanner.controlPoints)
remaining_points = total_points % len(self.pathPlanner.controlPoints) remaining_points = total_points % len(self.pathPlanner.controlPoints)
# Placing the points and nodes
for i, controlPair in enumerate(self.pathPlanner.controlPoints): for i, controlPair in enumerate(self.pathPlanner.controlPoints):
if i < len(self.pathPlanner.coordinates) - 1: if i < len(self.pathPlanner.coordinates) - 1:
start = QPoint(self.pathPlanner.coordinates[i][0], self.pathPlanner.coordinates[i][1]) start = QPoint(self.pathPlanner.coordinates[i][0], self.pathPlanner.coordinates[i][1])
end = QPoint(self.pathPlanner.coordinates[i + 1][0], self.pathPlanner.coordinates[i + 1][1]) end = QPoint(self.pathPlanner.coordinates[i + 1][0], self.pathPlanner.coordinates[i + 1][1])
pen = QPen(Qt.yellow if self.keyFrameData[i]["isNode"] else Qt.white) # Set color based on isNode pen = QPen(Qt.yellow if self.keyFrameData[i]["isNode"] else Qt.white)
pen.setWidth(2) pen.setWidth(2)
painter.setPen(pen) painter.setPen(pen)
@@ -135,10 +158,12 @@ class ButtonEditor(QMainWindow):
if i < remaining_points: if i < remaining_points:
num_points += 1 num_points += 1
# Set up the points on the bezier curve
for t in np.linspace(0, 1, num_points): for t in np.linspace(0, 1, num_points):
point = self.pointOnBezierCurve(start, controlPair[0], controlPair[1], end, t) point = self.pointOnBezierCurve(start, controlPair[0], controlPair[1], end, t)
painter.drawEllipse(point, 2, 2) painter.drawEllipse(point, 2, 2)
# Draw every point on the bezier curve with the rotation of the robot at that point
if i == self.currentFrame - 1: if i == self.currentFrame - 1:
t = (self.currentFrame - 1) / (num_points - 1) t = (self.currentFrame - 1) / (num_points - 1)
point = self.pointOnBezierCurve(start, controlPair[0], controlPair[1], end, t) point = self.pointOnBezierCurve(start, controlPair[0], controlPair[1], end, t)
@@ -147,14 +172,18 @@ class ButtonEditor(QMainWindow):
angle = self.interpolateAngle(start_angle, end_angle, t) angle = self.interpolateAngle(start_angle, end_angle, t)
self.drawRobot(painter, point, angle) self.drawRobot(painter, point, angle)
painter.end() painter.end() # If you don't end the painter the app crashes
self.imageLabel.setPixmap(self.pixmap) self.imageLabel.setPixmap(self.pixmap)
self.updateRectangles() self.updateRectangles()
# Finds the angle between two angles
# This is used to find the angles of all of the points in between the nodes
def interpolateAngle(self, start_angle, end_angle, t): def interpolateAngle(self, start_angle, end_angle, t):
diff = (end_angle - start_angle + 180) % 360 - 180 diff = (end_angle - start_angle + 180) % 360 - 180
return start_angle + diff * t return start_angle + diff * t
# Draw where the robot is at the current frame
# DOES NOT WORK AT THE MOMENT
def drawRobot(self, painter, position, angle): def drawRobot(self, painter, position, angle):
side_length = self.pathPlanner.nodeSize side_length = self.pathPlanner.nodeSize
half_side = side_length / 2 half_side = side_length / 2
@@ -173,26 +202,31 @@ class ButtonEditor(QMainWindow):
painter.restore() painter.restore()
# Function for drawing the points on the bezier curve (I dont get the math tbh)
def pointOnBezierCurve(self, start, control1, control2, end, t): def pointOnBezierCurve(self, start, control1, control2, end, t):
x = (1-t)**3 * start.x() + 3*(1-t)**2*t * control1.x() + 3*(1-t)*t**2 * control2.x() + t**3 * end.x() x = (1-t)**3 * start.x() + 3*(1-t)**2*t * control1.x() + 3*(1-t)*t**2 * control2.x() + t**3 * end.x()
y = (1-t)**3 * start.y() + 3*(1-t)**2*t * control1.y() + 3*(1-t)*t**2 * control2.y() + t**3 * end.y() y = (1-t)**3 * start.y() + 3*(1-t)**2*t * control1.y() + 3*(1-t)*t**2 * control2.y() + t**3 * end.y()
return QPoint(int(x), int(y)) return QPoint(int(x), int(y))
# Update the frames on the bottom of the screen that the user interacts with
def updateRectangles(self): def updateRectangles(self):
# Add the key frame widget
for i in reversed(range(self.rectanglesLayout.count())): for i in reversed(range(self.rectanglesLayout.count())):
widgetToRemove = self.rectanglesLayout.itemAt(i).widget() widgetToRemove = self.rectanglesLayout.itemAt(i).widget()
self.rectanglesLayout.removeWidget(widgetToRemove) self.rectanglesLayout.removeWidget(widgetToRemove)
widgetToRemove.setParent(None) widgetToRemove.setParent(None)
#Set the key frames width
window_width = self.rectanglesWidget.width() window_width = self.rectanglesWidget.width()
if self.keyFrameData: if self.keyFrameData:
rect_width = window_width / len(self.keyFrameData) rect_width = window_width / len(self.keyFrameData)
rect_height = 100 rect_height = 100 # Adjustable key frame height
for index, frame in enumerate(self.keyFrameData): for index, frame in enumerate(self.keyFrameData):
rectWidget = QWidget() rectWidget = QWidget()
rectWidget.setFixedSize(rect_width, rect_height) rectWidget.setFixedSize(rect_width, rect_height)
# Set the colors of the frames
if frame["isNode"]: if frame["isNode"]:
rectWidget.setStyleSheet("background-color: yellow;") rectWidget.setStyleSheet("background-color: yellow;")
else: else:
@@ -204,22 +238,26 @@ class ButtonEditor(QMainWindow):
rectWidget.mousePressEvent = lambda event, idx=index: self.rectangleClicked(idx) rectWidget.mousePressEvent = lambda event, idx=index: self.rectangleClicked(idx)
self.rectanglesLayout.addWidget(rectWidget) self.rectanglesLayout.addWidget(rectWidget)
# Set the current frame to the frame that the user clicked
def rectangleClicked(self, index): def rectangleClicked(self, index):
self.currentFrame = index + 1 self.currentFrame = index + 1 # Setting the frame
self.updateRectangles() self.updateRectangles()
self.updateScene() self.updateScene()
self.updateTimeLabel() self.updateTimeLabel()
# Set the current frame to be red
clicked_widget = self.rectanglesLayout.itemAt(index).widget() clicked_widget = self.rectanglesLayout.itemAt(index).widget()
clicked_widget.setStyleSheet("background-color: red;") clicked_widget.setStyleSheet("background-color: red;")
# Update the time depending on the current frame
def updateTimeLabel(self): def updateTimeLabel(self):
current_time = self.currentTime[self.currentFrame - 1] current_time = self.currentTime[self.currentFrame - 1] # Sets the time to the current time
minutes = int(current_time // 60) minutes = int(current_time // 60)
seconds = int(current_time % 60) seconds = int(current_time % 60)
milliseconds = int((current_time % 1) * 1000) milliseconds = int((current_time % 1) * 1000)
self.timeLabel.setText(f"{minutes}:{seconds:02d}.{milliseconds:03d} / {self.matchLength:.3f} sec") self.timeLabel.setText(f"{minutes}:{seconds:02d}.{milliseconds:03d} / {self.matchLength:.3f} sec")
# App initialization
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
window = ButtonEditor(None) window = ButtonEditor(None)