better signal graphing
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,9 +130,18 @@ 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.ui.statusLabel.setText("Preparing simulation…")
|
||||
self._update_title()
|
||||
|
||||
def clear(self, checked: bool = False, *, model_name: str = "") -> None:
|
||||
@@ -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):
|
||||
"<p>View live progress and open or save simulation results.</p>",
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
"implementation": {
|
||||
"kind": "text",
|
||||
"source": {
|
||||
"equations": "y = v+sin(time);"
|
||||
"equations": "y = v+sin(10*time);"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user