I have this simple code, but every time I try to close one of my proxy widgets, I get this error below:
import sys
import numpy as np
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QGraphicsProxyWidget, QGridLayout
)
from PySide6.QtCore import Signal, Qt
import pyqtgraph as pg
class YawSweepPlot(pg.PlotWidget):
"""
Represents Plot A: Yaw Sweep Plot. Displays Clf vs Yaw with a movable vertical cursor. Emits a yawChanged signal when the cursor is moved. """
yawChanged = Signal(float)
def __init__(self, parent=None):
super().__init__(parent)
self.setBackground('w')
self.setTitle("Clf vs Yaw")
self.setLabel('left', "Clf")
self.setLabel('bottom', "Yaw")
self.setXRange(-12, 10)
self.setYRange(-2.5, -1.5)
# Simulated Clf data
yaw = np.linspace(-12, 10, 500)
clf = -2 + 0.5 * np.sin(0.5 * yaw)
self.plot(yaw, clf, pen=pg.mkPen('b', width=2))
# Add Vertical Cursor
self.cursor = pg.InfiniteLine(angle=90, movable=True, pen=pg.mkPen('r', width=2))
self.addItem(self.cursor)
self.cursor.setPos(0) # Initial Yaw position
# Connect cursor movement to signal self.cursor.sigPositionChanged.connect(self.emit_yaw_changed)
def emit_yaw_changed(self):
current_yaw = self.cursor.value()
self.yawChanged.emit(current_yaw)
class FloorPlot(pg.PlotWidget):
"""
Represents each Floor Plot (Plot B). Displays pressure data based on yaw. """
def __init__(self, yaw=0.0, parent=None):
super().__init__(parent)
self.setBackground('w')
self.setContentsMargins(0, 0, 0, 0)
self.setTitle(f"Floor Plot at Yaw = {yaw:.2f}")
self.setXRange(-1, 1)
self.setYRange(-1, 1)
self.yaw = yaw
self.pressure_data = self.generate_pressure_data(yaw)
self.plot_pressure()
def generate_pressure_data(self, yaw):
"""
Generates simulated pressure data based on yaw. Ensures the seed is within [0, 2**32 - 1]. """
seed = int(yaw * 10)
seed = abs(seed) % (2 ** 32)
rng = np.random.default_rng(seed)
num_points = 50
x = rng.uniform(-1, 1, num_points)
y = rng.uniform(-1, 1, num_points)
pressure = rng.uniform(0, 100, num_points)
return np.column_stack((x, y, pressure))
def plot_pressure(self):
"""
Plots the pressure data on the PlotWidget. """
self.clear()
x = self.pressure_data[:, 0]
y = self.pressure_data[:, 1]
pressure = self.pressure_data[:, 2]
# Normalize pressure for color mapping
norm_pressure = (pressure - pressure.min()) / (pressure.max() - pressure.min())
colors = [pg.intColor(int(p * 255)) for p in norm_pressure]
scatter = pg.ScatterPlotItem(x, y, size=10, brush=colors)
self.addItem(scatter)
def update_data(self, yaw):
"""
Updates the pressure plot based on the new yaw value. """
self.yaw = yaw
self.setTitle(f"Floor Plot at Yaw = {yaw:.2f}")
self.pressure_data = self.generate_pressure_data(yaw)
self.plot_pressure()
class FloorPlotWidget(QWidget):
"""
Encapsulates a FloorPlot along with control buttons. """
duplicateRequested = Signal()
closeRequested = Signal(QWidget)
def __init__(self, yawChanged_signal, parent=None):
super().__init__(parent)
self.is_frozen = False
self.yawChanged_signal = yawChanged_signal
# Layout Setup
main_layout = QVBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(main_layout)
# Initialize FloorPlot
self.floor_plot = FloorPlot(yaw=0.0)
main_layout.addWidget(self.floor_plot)
# Buttons Layout
buttons_layout = QHBoxLayout()
main_layout.addLayout(buttons_layout)
# Freeze Button
self.freeze_button = QPushButton("Freeze")
self.freeze_button.clicked.connect(self.toggle_freeze)
buttons_layout.addWidget(self.freeze_button)
# Duplicate Button
self.duplicate_button = QPushButton("Duplicate")
self.duplicate_button.clicked.connect(self.duplicateRequested.emit)
buttons_layout.addWidget(self.duplicate_button)
# Close Button
self.close_button = QPushButton("Close")
self.close_button.clicked.connect(self.close_plot)
buttons_layout.addWidget(self.close_button)
# Connect to yawChanged_signal
self.yawChanged_signal.connect(self.handle_yaw_changed)
def handle_yaw_changed(self, yaw):
if not self.is_frozen:
self.floor_plot.update_data(yaw)
def toggle_freeze(self):
if self.is_frozen:
# Unfreeze: Start listening
self.is_frozen = False
self.freeze_button.setText("Freeze")
self.yawChanged_signal.connect(self.handle_yaw_changed)
else:
# Freeze: Stop listening
self.is_frozen = True
self.freeze_button.setText("Unfreeze")
self.yawChanged_signal.disconnect(self.handle_yaw_changed)
def close_plot(self):
"""
Emits a signal to request closing this plot. """
self.closeRequested.emit(self)
class FloorPlotGrid(pg.GraphicsLayoutWidget):
"""
Manages a grid layout of FloorPlotWidget instances. """
def __init__(self, yawChanged_signal, parent=None):
super().__init__(parent)
self.yawChanged_signal = yawChanged_signal
self.setLayout(QGridLayout())
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().setSpacing(0)
self.color_bar = pg.ColorBarItem(interactive=False)
color_map = pg.colormap.get('CET-D9')
self.color_bar.setColorMap(color_map)
self.color_bar.setLevels((-0.2, 0.2))
self.max_columns = 3
self.floor_plots = []
self.proxies = []
self.setStyleSheet("background: red")
# Initialize with one FloorPlotWidget
self.add_floor_plot()
def update_color_bar(self):
if len(self.floor_plots) == 0:
return
if self.ci.getItem(0, self.max_columns) is not None:
self.removeItem(self.color_bar)
rows_span, _ = self.get_row_col()
col = self.max_columns if len(self.floor_plots) < self.max_columns else len(self.floor_plots)
self.addItem(self.color_bar, 0, col, rowspan=rows_span + 1)
def add_floor_plot(self, floor_plot_widget=None):
"""
Adds a new FloorPlotWidget to the grid. If floor_plot_widget is None, creates a new instance. """
if floor_plot_widget is None:
floor_plot_widget = FloorPlotWidget(self.yawChanged_signal)
# Connect signals
floor_plot_widget.duplicateRequested.connect(self.add_floor_plot)
floor_plot_widget.closeRequested.connect(self.remove_floor_plot)
row, col = self.get_row_col()
# Create QGraphicsProxyWidget to embed FloorPlotWidget
proxy = QGraphicsProxyWidget()
proxy.setContentsMargins(0, 0, 0, 0)
proxy.setWidget(floor_plot_widget)
self.addItem(proxy, row, col)
# Track the FloorPlotWidget and its proxy
self.floor_plots.append(floor_plot_widget)
self.proxies.append(proxy)
self.update_color_bar()
def get_row_col(self):
# Calculate row and column
index = len(self.floor_plots)
return divmod(index, self.max_columns)
def remove_floor_plot(self, floor_plot_widget):
"""
Removes a FloorPlotWidget from the grid. """
if floor_plot_widget in self.floor_plots:
index = self.floor_plots.index(floor_plot_widget)
proxy = self.proxies[index]
# Remove from GraphicsLayoutWidget
self.removeItem(proxy)
# Remove references
self.floor_plots.pop(index)
self.proxies.pop(index)
# Delete the proxy and widget
floor_plot_widget.setParent(None)
del floor_plot_widget
del proxy
# floor_plot_widget.deleteLater()
# proxy.deleteLater()
# Rearrange the remaining plots
self.rearrange_plots()
self.update_color_bar()
def rearrange_plots(self):
"""
Rearranges the plots in the grid after removal. """
if len(self.ci.items) > 1:
self.ci.clear()
# Reset proxies list
self.proxies = []
# Re-add FloorPlotWidgets
for idx, floor_plot_widget in enumerate(self.floor_plots):
row, col = divmod(idx, self.max_columns)
proxy = QGraphicsProxyWidget()
proxy.setWidget(floor_plot_widget)
self.addItem(proxy, row, col)
self.proxies.append(proxy)
class MainLayoutWidget(QWidget):
"""
Main layout widget containing YawSweepPlot on the left and FloorPlotGrid on the right. """
def __init__(self, parent=None):
super().__init__(parent)
# Main Layout
main_layout = QHBoxLayout()
self.setLayout(main_layout)
# Initialize YawSweepPlot
self.yaw_sweep_plot = YawSweepPlot()
main_layout.addWidget(self.yaw_sweep_plot, stretch=1)
# Initialize FloorPlotGrid
self.floor_plot_grid = FloorPlotGrid(self.yaw_sweep_plot.yawChanged)
main_layout.addWidget(self.floor_plot_grid, stretch=2)
class MainWindow(QMainWindow):
"""
The main application window. """
def __init__(self):
super().__init__()
self.setWindowTitle("Yaw Sweep and Floor Plots")
self.resize(1600, 800)
# Set Central Widget
self.main_layout_widget = MainLayoutWidget()
self.setCentralWidget(self.main_layout_widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
I'm getting this error:
Traceback (most recent call last):
File "C:\dev\personal\LearningPyQT\final_floor_prototype.py", line 243, in remove_floor_plot
self.rearrange_plots()
File "C:\dev\personal\LearningPyQT\final_floor_prototype.py", line 260, in rearrange_plots
proxy.setWidget(floor_plot_widget)
RuntimeError: Internal C++ object (FloorPlotWidget) already deleted.
I'm unable to figure out the exact issue, anyone faced this before