Start of a sim window
This commit is contained in:
@@ -427,11 +427,23 @@ class DocumentController(QObject):
|
||||
raise ValueError("Open a graph component before composing")
|
||||
self.simulation.compose(component.to_dict())
|
||||
|
||||
def run_simulation(self) -> None:
|
||||
def run_simulation(
|
||||
self,
|
||||
progress_callback=None,
|
||||
message_callback=None,
|
||||
callback=None,
|
||||
error_callback=None,
|
||||
) -> None:
|
||||
component = self.active_component
|
||||
if component is None or component.implementation_kind != "graph":
|
||||
raise ValueError("Open a graph component before running a simulation")
|
||||
self.simulation.run_simulation(component.to_dict())
|
||||
self.simulation.run_simulation(
|
||||
component.to_dict(),
|
||||
progress_callback,
|
||||
message_callback,
|
||||
callback,
|
||||
error_callback,
|
||||
)
|
||||
|
||||
def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
|
||||
item = (
|
||||
|
||||
94
BEdit/src/bedit/gui/generated/ui_simulation_window.py
Normal file
94
BEdit/src/bedit/gui/generated/ui_simulation_window.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'simulation_window.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.11.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
|
||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||
QMetaObject, QObject, QPoint, QRect,
|
||||
QSize, QTime, QUrl, Qt)
|
||||
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QApplication, QLabel, QListWidget, QListWidgetItem,
|
||||
QMainWindow, QProgressBar, QSizePolicy, QTabWidget,
|
||||
QVBoxLayout, QWidget)
|
||||
|
||||
class Ui_SimulationWindow(object):
|
||||
def setupUi(self, SimulationWindow):
|
||||
if not SimulationWindow.objectName():
|
||||
SimulationWindow.setObjectName(u"SimulationWindow")
|
||||
SimulationWindow.resize(720, 480)
|
||||
self.centralWidget = QWidget(SimulationWindow)
|
||||
self.centralWidget.setObjectName(u"centralWidget")
|
||||
self.windowLayout = QVBoxLayout(self.centralWidget)
|
||||
self.windowLayout.setObjectName(u"windowLayout")
|
||||
self.statusLabel = QLabel(self.centralWidget)
|
||||
self.statusLabel.setObjectName(u"statusLabel")
|
||||
|
||||
self.windowLayout.addWidget(self.statusLabel)
|
||||
|
||||
self.progressBar = QProgressBar(self.centralWidget)
|
||||
self.progressBar.setObjectName(u"progressBar")
|
||||
self.progressBar.setMaximum(10000)
|
||||
self.progressBar.setValue(0)
|
||||
|
||||
self.windowLayout.addWidget(self.progressBar)
|
||||
|
||||
self.timeLabel = QLabel(self.centralWidget)
|
||||
self.timeLabel.setObjectName(u"timeLabel")
|
||||
|
||||
self.windowLayout.addWidget(self.timeLabel)
|
||||
|
||||
self.resultsTabs = QTabWidget(self.centralWidget)
|
||||
self.resultsTabs.setObjectName(u"resultsTabs")
|
||||
self.logTab = QWidget()
|
||||
self.logTab.setObjectName(u"logTab")
|
||||
self.logLayout = QVBoxLayout(self.logTab)
|
||||
self.logLayout.setObjectName(u"logLayout")
|
||||
self.messageList = QListWidget(self.logTab)
|
||||
self.messageList.setObjectName(u"messageList")
|
||||
self.messageList.setAlternatingRowColors(True)
|
||||
|
||||
self.logLayout.addWidget(self.messageList)
|
||||
|
||||
self.resultsTabs.addTab(self.logTab, "")
|
||||
self.resultsTab = QWidget()
|
||||
self.resultsTab.setObjectName(u"resultsTab")
|
||||
self.resultsLayout = QVBoxLayout(self.resultsTab)
|
||||
self.resultsLayout.setObjectName(u"resultsLayout")
|
||||
self.resultsPlaceholder = QLabel(self.resultsTab)
|
||||
self.resultsPlaceholder.setObjectName(u"resultsPlaceholder")
|
||||
self.resultsPlaceholder.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.resultsLayout.addWidget(self.resultsPlaceholder)
|
||||
|
||||
self.resultsTabs.addTab(self.resultsTab, "")
|
||||
|
||||
self.windowLayout.addWidget(self.resultsTabs)
|
||||
|
||||
SimulationWindow.setCentralWidget(self.centralWidget)
|
||||
|
||||
self.retranslateUi(SimulationWindow)
|
||||
|
||||
self.resultsTabs.setCurrentIndex(0)
|
||||
|
||||
|
||||
QMetaObject.connectSlotsByName(SimulationWindow)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, SimulationWindow):
|
||||
SimulationWindow.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Simulation", None))
|
||||
self.statusLabel.setText(QCoreApplication.translate("SimulationWindow", u"No simulation has been run yet.", None))
|
||||
self.progressBar.setFormat(QCoreApplication.translate("SimulationWindow", u"%p%", None))
|
||||
self.timeLabel.setText(QCoreApplication.translate("SimulationWindow", u"Time: 0 s", None))
|
||||
self.resultsTabs.setTabText(self.resultsTabs.indexOf(self.logTab), QCoreApplication.translate("SimulationWindow", u"Simulation Log", None))
|
||||
self.resultsPlaceholder.setText(QCoreApplication.translate("SimulationWindow", u"Simulation graphs and result controls can be added here.", None))
|
||||
self.resultsTabs.setTabText(self.resultsTabs.indexOf(self.resultsTab), QCoreApplication.translate("SimulationWindow", u"Results", None))
|
||||
# retranslateUi
|
||||
|
||||
@@ -32,6 +32,7 @@ from bedit.gui.dialogs.settings import SettingsDialog
|
||||
from bedit.gui.dialogs.port_options import PortOptionsDialog
|
||||
from bedit.gui.dialogs.parameter_options import ParameterOptionsDialog
|
||||
from bedit.gui.dialogs.simulation_settings import SimulationSettingsDialog
|
||||
from bedit.gui.simulation_window import SimulationWindow
|
||||
from bedit.gui.preferences import application_settings
|
||||
from bedit.gui.simulation_reload import reload_simulation
|
||||
from bedit.gui.generated.ui_main_window import Ui_MainWindow
|
||||
@@ -54,6 +55,7 @@ class MainWindow(QMainWindow):
|
||||
self.log.info("BEdit started")
|
||||
self.settings = application_settings()
|
||||
self._applying_text_definition = False
|
||||
self._simulation_window = SimulationWindow(self)
|
||||
|
||||
self.libraries = LibraryRepository(self)
|
||||
self.simulation = Simulation(
|
||||
@@ -248,11 +250,16 @@ class MainWindow(QMainWindow):
|
||||
|
||||
@Slot()
|
||||
def run_simulation(self) -> None:
|
||||
window = self._simulation_window
|
||||
callbacks = window.begin_run()
|
||||
window.show()
|
||||
window.raise_()
|
||||
window.activateWindow()
|
||||
try:
|
||||
self.document_controller.run_simulation()
|
||||
self.document_controller.run_simulation(*callbacks)
|
||||
except Exception as error:
|
||||
self.log.exception("Simulation run failed")
|
||||
QMessageBox.warning(self, "Cannot run simulation", str(error))
|
||||
window.report_start_error(error)
|
||||
|
||||
def _restore_window_geometry(self) -> None:
|
||||
geometry = self.settings.value("window/geometry")
|
||||
|
||||
102
BEdit/src/bedit/gui/simulation_window.py
Normal file
102
BEdit/src/bedit/gui/simulation_window.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import QMainWindow
|
||||
|
||||
from bedit.core.simulation.openmodelica import SimulationMessage, SimulationProgress
|
||||
from bedit.gui.generated.ui_simulation_window import Ui_SimulationWindow
|
||||
|
||||
|
||||
class SimulationWindow(QMainWindow):
|
||||
"""Persistent, reusable view of simulation progress and results."""
|
||||
|
||||
progressReceived = Signal(object)
|
||||
messageReceived = Signal(object)
|
||||
simulationFinished = Signal(object)
|
||||
simulationFailed = Signal(str)
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_SimulationWindow()
|
||||
self.ui.setupUi(self)
|
||||
self._running = False
|
||||
self._run_generation = 0
|
||||
self.progressReceived.connect(self._show_progress)
|
||||
self.messageReceived.connect(self._show_message)
|
||||
self.simulationFinished.connect(self._show_finished)
|
||||
self.simulationFailed.connect(self._show_error)
|
||||
|
||||
def begin_run(self) -> tuple:
|
||||
"""Reset the view and return callbacks bound to this particular run."""
|
||||
|
||||
self._run_generation += 1
|
||||
generation = self._run_generation
|
||||
self._running = True
|
||||
self.ui.statusLabel.setText("Preparing simulation…")
|
||||
self.ui.progressBar.setValue(0)
|
||||
self.ui.timeLabel.setText("Time: 0 s")
|
||||
self.ui.messageList.clear()
|
||||
self.clear_results()
|
||||
return (
|
||||
lambda progress: self._report_progress(generation, progress),
|
||||
lambda message: self._report_message(generation, message),
|
||||
lambda result: self._report_finished(generation, result),
|
||||
lambda error: self._report_error(generation, error),
|
||||
)
|
||||
|
||||
def clear_results(self) -> None:
|
||||
"""Clear future plots and result models before a new run.
|
||||
|
||||
Add graph widgets and their clearing logic here when simulation result
|
||||
loading and plotting are implemented. The Designer-owned `resultsLayout`
|
||||
is the intended container for those widgets.
|
||||
"""
|
||||
|
||||
def _report_progress(
|
||||
self, generation: int, progress: SimulationProgress
|
||||
) -> None:
|
||||
if generation == self._run_generation:
|
||||
self.progressReceived.emit(progress)
|
||||
|
||||
def _report_message(self, generation: int, message: SimulationMessage) -> None:
|
||||
if generation == self._run_generation:
|
||||
self.messageReceived.emit(message)
|
||||
|
||||
def _report_finished(self, generation: int, result) -> None:
|
||||
if generation == self._run_generation:
|
||||
self.simulationFinished.emit(result)
|
||||
|
||||
def _report_error(self, generation: int, error: Exception) -> None:
|
||||
if generation == self._run_generation:
|
||||
self.simulationFailed.emit(str(error))
|
||||
|
||||
def report_start_error(self, error: Exception) -> None:
|
||||
"""Report an error raised before asynchronous callbacks were installed."""
|
||||
|
||||
self.simulationFailed.emit(str(error))
|
||||
|
||||
def _show_progress(self, progress: SimulationProgress) -> None:
|
||||
self.ui.statusLabel.setText(progress.phase or "Running")
|
||||
self.ui.timeLabel.setText(f"Time: {progress.time:g} s")
|
||||
self.ui.progressBar.setValue(max(0, min(10000, progress.progress)))
|
||||
|
||||
def _show_message(self, message: SimulationMessage) -> None:
|
||||
prefix = message.stream or message.type or "OpenModelica"
|
||||
self.ui.messageList.addItem(f"{prefix}: {message.text}")
|
||||
self.ui.messageList.scrollToBottom()
|
||||
|
||||
def _show_finished(self, _result) -> None:
|
||||
self._running = False
|
||||
self.ui.progressBar.setValue(10000)
|
||||
self.ui.statusLabel.setText("Simulation finished")
|
||||
self.load_results()
|
||||
|
||||
def _show_error(self, message: str) -> None:
|
||||
self._running = False
|
||||
self.ui.statusLabel.setText("Simulation failed")
|
||||
self.ui.messageList.addItem(f"Error: {message}")
|
||||
|
||||
def load_results(self) -> None:
|
||||
"""Populate future plots and result controls after a successful run."""
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
return self._running
|
||||
Reference in New Issue
Block a user