the FloorPlotWidget you are trying to access has already been deleted, but
you are still trying to interact with it, specifically when you call
proxy.setWidget(floor_plot_widget).
Problem
In your remove_floor_plot() method, you are explicitly deleting the
floor_plot_widget and its associated QGraphicsProxyWidget:
floor_plot_widget.setParent(None)
del floor_plot_widget
del proxy
After this, the widget is completely destroyed, but when you call
rearrange_plots(), you attempt to access this deleted widget:
proxy.setWidget(floor_plot_widget)
This leads to the RuntimeError because the object no longer exists.
Solution
Instead of manually deleting the widget with del floor_plot_widget and del
proxy, use deleteLater() to safely schedule the deletion of the widget and
proxy. This ensures that they are deleted at an appropriate time after the
event loop has completed any pending operations.
Fix:
Replace this part of your remove_floor_plot() method:
floor_plot_widget.setParent(None)
del floor_plot_widget
del proxy
# floor_plot_widget.deleteLater()
# proxy.deleteLater()
With:
floor_plot_widget.setParent(None)
floor_plot_widget.deleteLater()
proxy.deleteLater()
Explanation
deleteLater() tells Qt to delete the object when it is safe to do so, after
all pending events have been processed.
This avoids accessing a deleted object during the rearrangement of your
plots.
Additional Consideration
In your rearrange_plots() method, ensure that you are not trying to use
already-deleted objects:
if len(self.ci.items) > 1:
self.ci.clear()
Instead of manually clearing and resetting, it may be safer to use a check
like this:
if not self.floor_plots:
return
This will skip the rearrangement if there are no plots left.
Summary
Replace manual deletions with deleteLater() to avoid accessing deleted
objects, and add additional checks to ensure you are not working with empty
lists or invalid references.
---------- Forwarded message ---------
From: Arnold Kyeza <[email protected]>
Date: Fri, 8 Nov 2024, 18.06
Subject: [pyqtgraph] RuntimeError: Internal C++ object (FloorPlotWidget)
already deleted.
To: pyqtgraph <[email protected]>
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
--
You received this message because you are subscribed to the Google Groups
"pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an
email to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/pyqtgraph/8911a4c2-2030-44c1-94cf-88f516d202f6n%40googlegroups.com
<https://groups.google.com/d/msgid/pyqtgraph/8911a4c2-2030-44c1-94cf-88f516d202f6n%40googlegroups.com?utm_medium=email&utm_source=footer>
.
--
You received this message because you are subscribed to the Google Groups
"pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/pyqtgraph/CAC5rm4N1QzzghwVrW_1gx-v-dGN8tt7m4ntaUbVnd9qzsUTuKA%40mail.gmail.com.