From eb8f77ce64d214149e55ceae64d5000ba47f1e9e Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Tue, 21 Jul 2026 15:24:24 +0200 Subject: [PATCH] better signal graphing --- BEdit/AGENTS.md | 5 ++ BEdit/src/bedit/gui/main_window.py | 2 +- BEdit/src/bedit/gui/simulation_window.py | 81 ++++++++++++++++++++++-- BEdit/untitled.bedit.json | 2 +- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/BEdit/AGENTS.md b/BEdit/AGENTS.md index 15762e2..600204b 100644 --- a/BEdit/AGENTS.md +++ b/BEdit/AGENTS.md @@ -131,6 +131,8 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant progress, log, and future result views. Extend graph presentation through its Designer-owned `resultsLayout` and the `clear_result_views()`/`load_result_views()` hooks. +- Simulation-window geometry, dock/toolbar state, and central-results visibility + persist under `simulationWindow/` through `application_settings()`. - Standalone simulation results are modeled in `core/simulation/results.py`. Its versioned schema retains model status, messages, metadata, and plottable traces so the simulation window can open results without an active document. @@ -149,6 +151,9 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant Signals tree checkboxes edit the active graph's traces, and each page embeds a Matplotlib QtAgg canvas. Each graph persists its own `x_axis` signal (default `time`), selectable from the Signals tree context menu. +- Rerunning the same composed model retains graph tabs, ordering, titles, X axes, + and surviving trace settings while replacing numeric data. Missing signals are + pruned from traces/X-axis selection and newly returned columns appear in the tree. - The optional OpenModelica executable is persisted as `simulation/openModelicaPath`. The GUI passes it into `Simulation`; core must not read `QSettings`. An empty path uses OMPython/PATH discovery, while an diff --git a/BEdit/src/bedit/gui/main_window.py b/BEdit/src/bedit/gui/main_window.py index ad1c5bb..e427870 100644 --- a/BEdit/src/bedit/gui/main_window.py +++ b/BEdit/src/bedit/gui/main_window.py @@ -274,7 +274,7 @@ class MainWindow(QMainWindow): self.show_simulation_window() try: self.document_controller.run_simulation(*callbacks) - window.set_model_name(self.simulation.model_name) + window.prepare_run_model(self.simulation.model_name) except Exception as error: self.log.exception("Simulation run failed") window.report_start_error(error) diff --git a/BEdit/src/bedit/gui/simulation_window.py b/BEdit/src/bedit/gui/simulation_window.py index 6dbfedd..7746ca8 100644 --- a/BEdit/src/bedit/gui/simulation_window.py +++ b/BEdit/src/bedit/gui/simulation_window.py @@ -4,6 +4,7 @@ from pathlib import Path from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg, NavigationToolbar2QT from matplotlib.figure import Figure from PySide6.QtCore import Qt, Signal +from PySide6.QtGui import QCloseEvent from PySide6.QtWidgets import ( QFileDialog, QInputDialog, @@ -25,6 +26,7 @@ from bedit.core.simulation.results import ( save_simulation_results, ) from bedit.gui.generated.ui_simulation_window import Ui_SimulationWindow +from bedit.gui.preferences import application_settings class SimulationWindow(QMainWindow): @@ -39,6 +41,7 @@ class SimulationWindow(QMainWindow): super().__init__(parent) self.ui = Ui_SimulationWindow() self.ui.setupUi(self) + self.settings = application_settings() self._running = False self._run_generation = 0 self._file_path: Path | None = None @@ -51,6 +54,7 @@ class SimulationWindow(QMainWindow): self.ui.statusDock, self.ui.logDock, Qt.Orientation.Vertical ) self.ui.statusDock.setFixedHeight(self.ui.statusDock.sizeHint().height()) + self._restore_window_layout() self.progressReceived.connect(self._show_progress) self.messageReceived.connect(self._show_message) self.simulationFinished.connect(self._show_finished) @@ -84,12 +88,40 @@ class SimulationWindow(QMainWindow): self.ui.menuToolbars.addAction(self.ui.fileToolbar.toggleViewAction()) self.ui.menuToolbars.addAction(self.ui.workspaceToolbar.toggleViewAction()) - def begin_run(self, model_name: str = "") -> tuple: - """Reset the viewer and return callbacks bound to this run.""" + def _restore_window_layout(self) -> None: + geometry = self.settings.value("simulationWindow/geometry") + if geometry is not None: + self.restoreGeometry(geometry) + state = self.settings.value("simulationWindow/state") + if state is not None: + self.restoreState(state) + results_visible = self.settings.value( + "simulationWindow/resultsVisible", True, type=bool + ) + self.ui.actionToggleResults.setChecked(results_visible) - self.clear(model_name=model_name) + def _save_window_layout(self) -> None: + self.settings.setValue("simulationWindow/geometry", self.saveGeometry()) + self.settings.setValue("simulationWindow/state", self.saveState()) + self.settings.setValue( + "simulationWindow/resultsVisible", + self.ui.actionToggleResults.isChecked(), + ) + self.settings.sync() + + def begin_run(self) -> tuple: + """Reset transient run state while retaining the current workspace.""" + + self._run_generation += 1 generation = self._run_generation self._running = True + self._file_path = None + self.results.status = {} + self.results.messages.clear() + self.results.metadata.clear() + self.ui.progressBar.setValue(0) + self.ui.timeLabel.setText("Time: 0 s") + self.ui.messageList.clear() self.ui.statusLabel.setText("Preparing simulation…") return ( lambda progress: self._report_progress(generation, progress), @@ -98,10 +130,19 @@ class SimulationWindow(QMainWindow): lambda error: self._report_error(generation, error), ) - def set_model_name(self, model_name: str | None) -> None: - if model_name: + def prepare_run_model(self, model_name: str | None) -> None: + """Retain graph configuration only when rerunning the same model.""" + + if not model_name: + return + if self.results.model_name != model_name: + self.results = SimulationResults(model_name=model_name) + self.clear_result_views() + self.load_result_views() + else: self.results.model_name = model_name - self._update_title() + self.ui.statusLabel.setText("Preparing simulation…") + self._update_title() def clear(self, checked: bool = False, *, model_name: str = "") -> None: """Discard the displayed run and prepare an empty results document.""" @@ -141,12 +182,23 @@ class SimulationWindow(QMainWindow): self._rebuild_signal_tree() def _rebuild_graph_tabs(self) -> None: + current_page = self.ui.graphTabs.currentWidget() + current_graph_id = ( + current_page.graph_id + if isinstance(current_page, GraphWorkspacePage) + else None + ) self._rebuilding_graph_tabs = True self._clear_graph_tab_widgets() for graph in self.results.graphs: self.ui.graphTabs.addTab( GraphWorkspacePage(graph, self.results), graph.title ) + if current_graph_id is not None: + for index, graph in enumerate(self.results.graphs): + if graph.id == current_graph_id: + self.ui.graphTabs.setCurrentIndex(index) + break self._rebuilding_graph_tabs = False self._update_graph_actions() self._sync_signal_checks() @@ -439,6 +491,19 @@ class SimulationWindow(QMainWindow): self.results.status.update(phase="Simulation finished", progress=10000) if isinstance(result, SimulationExecutionResult): self.results.data = result.data + available_signals = set(result.data) + for graph in self.results.graphs: + graph.traces = [ + trace + for trace in graph.traces + if trace.name in available_signals + ] + if graph.x_axis not in available_signals: + graph.x_axis = ( + "time" + if "time" in available_signals + else next(iter(result.data), "") + ) self.results.metadata["processReturnCode"] = result.return_code self.results.metadata["sourceResultFile"] = Path(result.result_file).name else: @@ -464,6 +529,10 @@ class SimulationWindow(QMainWindow): "

View live progress and open or save simulation results.

", ) + def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802 (Qt API name) + self._save_window_layout() + super().closeEvent(event) + @property def is_running(self) -> bool: return self._running diff --git a/BEdit/untitled.bedit.json b/BEdit/untitled.bedit.json index 086b172..69d365e 100644 --- a/BEdit/untitled.bedit.json +++ b/BEdit/untitled.bedit.json @@ -377,7 +377,7 @@ "implementation": { "kind": "text", "source": { - "equations": "y = v+sin(time);" + "equations": "y = v+sin(10*time);" } } },