BEsim logger

This commit is contained in:
2026-07-31 12:48:42 +02:00
parent 89ac2d8ff8
commit 22e9a0386c
10 changed files with 125 additions and 18 deletions

18
.vscode/launch.json vendored
View File

@@ -5,7 +5,7 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Python Debugger: Module", "name": "BEdit debug",
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"module": "bedit_gui", "module": "bedit_gui",
@@ -18,6 +18,22 @@
"args": [ "args": [
"-f", "${workspaceFolder}/untitled.bedit.json" "-f", "${workspaceFolder}/untitled.bedit.json"
] ]
},
{
"name": "BEsim debug",
"type": "debugpy",
"request": "launch",
"module": "bedit_gui.simulation_application",
"preLaunchTask": "Qt: Generate files",
"cwd": "${workspaceFolder}",
"env": {
"QT_QPA_PLATFORMTHEME": "qt6ct",
"QT_QPA_PLATFORM": "xcb"
},
"args": [
"-s", "run_bondgraph",
"${workspaceFolder}/untitled.besim.json"
]
} }
] ]
} }

View File

@@ -6,6 +6,7 @@ from PySide6.QtCore import QObject, Signal
from bedit_gui.services.application_logging import configure_logging from bedit_gui.services.application_logging import configure_logging
from bedit_gui.views.main_window import MainWindow from bedit_gui.views.main_window import MainWindow
from bedit_gui.views.simulation_window import SimulationWindow
from bedit_gui.views.models import LogListModel from bedit_gui.views.models import LogListModel
@@ -30,7 +31,7 @@ class LogController(QObject):
def __init__( def __init__(
self, self,
window: MainWindow, window: MainWindow | SimulationWindow,
level: int | str = logging.INFO, level: int | str = logging.INFO,
) -> None: ) -> None:
super().__init__(window) super().__init__(window)

View File

@@ -3,13 +3,18 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
from PySide6.QtCore import QObject, Qt from PySide6.QtCore import QObject, Qt
from PySide6.QtWidgets import QApplication, QFileDialog, QInputDialog, QMessageBox from PySide6.QtWidgets import QApplication, QDialog, QFileDialog, QInputDialog, QMessageBox
from bedit_gui.services import document_files, simulation_files from bedit_gui.services import document_files, simulation_files
from bedit_gui.services.simulation_loader import compile_simulation_root, component_choices, load_and_compile_bedit from bedit_gui.services.simulation_loader import compile_simulation_root, component_choices, load_and_compile_bedit
from bedit_gui.models import SimulationDatabase, SimulationID
from bedit_gui.services.application_logging import get_logger
from bedit_gui.simulation_models import CompiledModel, SimulationRoot from bedit_gui.simulation_models import CompiledModel, SimulationRoot
from bedit_gui.views.dialogs.simulation_settings_dialog import SimulationSettingsDialog
from bedit_gui.views.simulation_window import SimulationWindow from bedit_gui.views.simulation_window import SimulationWindow
logger = get_logger(__name__)
class SimulationFileController(QObject): class SimulationFileController(QObject):
def __init__(self, window: SimulationWindow) -> None: def __init__(self, window: SimulationWindow) -> None:
@@ -21,6 +26,7 @@ class SimulationFileController(QObject):
window.ui.actionNew_Simulation_Run.triggered.connect(self.new) window.ui.actionNew_Simulation_Run.triggered.connect(self.new)
window.ui.actionOpen_Simulation_Run.triggered.connect(self.open_dialog) window.ui.actionOpen_Simulation_Run.triggered.connect(self.open_dialog)
window.ui.actionSave_Simulation_Run.triggered.connect(self.save) window.ui.actionSave_Simulation_Run.triggered.connect(self.save)
window.ui.actionSimulation_Options.triggered.connect(self.open_simulation_settings)
self._update_window() self._update_window()
def new(self) -> None: def new(self) -> None:
@@ -28,9 +34,11 @@ class SimulationFileController(QObject):
self.compiled_model = None self.compiled_model = None
self.path = None self.path = None
self._update_window() self._update_window()
logger.info("Created new simulation")
def open(self, path: str | Path, *, component: str | None = None, simulation: str | None = None, backed_by_file: bool = True, compiled_model: CompiledModel | None = None) -> None: def open(self, path: str | Path, *, component: str | None = None, simulation: str | None = None, backed_by_file: bool = True, compiled_model: CompiledModel | None = None) -> None:
file_path = Path(path) file_path = Path(path)
try:
if file_path.suffix.lower() == ".bes" or simulation_files.is_simulation_json(file_path): if file_path.suffix.lower() == ".bes" or simulation_files.is_simulation_json(file_path):
self.root = simulation_files.load(file_path) self.root = simulation_files.load(file_path)
self.compiled_model = compiled_model or self._recompile_with_wait_cursor(self.root) self.compiled_model = compiled_model or self._recompile_with_wait_cursor(self.root)
@@ -38,7 +46,11 @@ class SimulationFileController(QObject):
else: else:
self.root, self.compiled_model = self._compile_with_wait_cursor(file_path, component=component, simulation=simulation) self.root, self.compiled_model = self._compile_with_wait_cursor(file_path, component=component, simulation=simulation)
self.path = None self.path = None
except (OSError, RuntimeError, TypeError, ValueError):
logger.exception("Could not open simulation %s", file_path)
raise
self._update_window() self._update_window()
logger.info("Opened simulation: %s", file_path)
def open_dialog(self) -> None: def open_dialog(self) -> None:
filename, _ = QFileDialog.getOpenFileName(self.window, "Open Simulation", "", "Simulation and BEdit files (*.bes *.beb *.json)") filename, _ = QFileDialog.getOpenFileName(self.window, "Open Simulation", "", "Simulation and BEdit files (*.bes *.beb *.json)")
@@ -75,11 +87,43 @@ class SimulationFileController(QObject):
path = path.with_suffix(".bes") path = path.with_suffix(".bes")
try: try:
simulation_files.save(self.root, path) simulation_files.save(self.root, path)
except (OSError, ValueError) as exc: except (OSError, TypeError, ValueError) as exc:
logger.exception("Could not save simulation %s", path)
QMessageBox.critical(self.window, "Could not save simulation", str(exc)) QMessageBox.critical(self.window, "Could not save simulation", str(exc))
return return
self.path = path self.path = path
self._update_window() self._update_window()
logger.info("Saved simulation: %s", path)
def open_simulation_settings(self) -> None:
if self.root is None:
return
simulation_id = SimulationID()
database = SimulationDatabase(simulations={simulation_id: self.root.settings}, active_simulation=simulation_id)
components = self._source_components()
dialog = SimulationSettingsDialog(database, [(component_id, path) for component_id, _component, path in components], self.window, show_simulation_list=False)
if dialog.exec() != QDialog.DialogCode.Accepted:
return
updated = dialog.database().simulations[simulation_id]
component_changed = updated.component != self.root.component
self.root.settings = updated
self.root.component = updated.component
self.root.component_path = next((path for component_id, _component, path in components if component_id == updated.component), self.root.component_path)
self.root.settings_name = updated.name
if component_changed:
self.compiled_model = None
self._update_window()
logger.info("Updated simulation settings: %s", updated.name)
def _source_components(self) -> list[tuple]:
if self.root is not None and self.root.source_document is not None:
try:
return component_choices(document_files.load(self.root.source_document))
except (OSError, TypeError, ValueError):
logger.warning("Could not load source components from %s", self.root.source_document, exc_info=True)
if self.root is None:
return []
return [(self.root.component, None, self.root.component_path)]
def _compile_with_wait_cursor(self, path: Path, *, component: str | None, simulation: str | None) -> tuple[SimulationRoot, CompiledModel]: def _compile_with_wait_cursor(self, path: Path, *, component: str | None, simulation: str | None) -> tuple[SimulationRoot, CompiledModel]:
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
@@ -100,8 +144,10 @@ class SimulationFileController(QObject):
self.window.setWindowTitle("BEdit Simulator") self.window.setWindowTitle("BEdit Simulator")
self.window.statusBar().showMessage("No simulation loaded") self.window.statusBar().showMessage("No simulation loaded")
self.window.ui.actionSave_Simulation_Run.setEnabled(False) self.window.ui.actionSave_Simulation_Run.setEnabled(False)
self.window.ui.actionSimulation_Options.setEnabled(False)
return return
self.window.setWindowTitle(f"{self.root.settings.name} — BEdit Simulator") self.window.setWindowTitle(f"{self.root.settings.name} — BEdit Simulator")
compiled = self.compiled_model.executable if self.compiled_model is not None else "not compiled" compiled = self.compiled_model.executable if self.compiled_model is not None else "not compiled"
self.window.statusBar().showMessage(f"{self.root.component_path} · {compiled}") self.window.statusBar().showMessage(f"{self.root.component_path} · {compiled}")
self.window.ui.actionSave_Simulation_Run.setEnabled(True) self.window.ui.actionSave_Simulation_Run.setEnabled(True)
self.window.ui.actionSimulation_Options.setEnabled(True)

View File

@@ -25,3 +25,24 @@ class ApplicationSettings:
@log_level.setter @log_level.setter
def log_level(self, level: int) -> None: def log_level(self, level: int) -> None:
self._settings.setValue(self.LOG_LEVEL_KEY, level) self._settings.setValue(self.LOG_LEVEL_KEY, level)
class SimulationApplicationSettings:
"""Typed access to persistent BEsim application settings."""
LOG_LEVEL_KEY = "logging/level"
DEFAULT_LOG_LEVEL = logging.INFO
def __init__(self, settings: QSettings | None = None) -> None:
self._settings = settings if settings is not None else QSettings()
@property
def log_level(self) -> int:
return self._settings.value(
self.LOG_LEVEL_KEY,
self.DEFAULT_LOG_LEVEL,
type=int,
)
@log_level.setter
def log_level(self, level: int) -> None:
self._settings.setValue(self.LOG_LEVEL_KEY, level)

View File

@@ -67,7 +67,7 @@ def _load_bes(path: Path) -> Mapping[str, Any]:
if version != FILE_FORMAT_VERSION: if version != FILE_FORMAT_VERSION:
raise ValueError(f"unsupported BES file version {version}") raise ValueError(f"unsupported BES file version {version}")
data = msgpack.unpackb(zlib.decompress(payload[header_end:]), raw=False, strict_map_key=False) data = msgpack.unpackb(zlib.decompress(payload[header_end:]), raw=False, strict_map_key=False)
except (OSError, ValueError, zlib.error, msgpack.exceptions.MsgpackException) as exc: except (OSError, ValueError, zlib.error, msgpack.exceptions.UnpackException) as exc:
raise ValueError(f"could not read BES simulation {path}: {exc}") from exc raise ValueError(f"could not read BES simulation {path}: {exc}") from exc
if not isinstance(data, Mapping): if not isinstance(data, Mapping):
raise TypeError(f"BES simulation {path} must contain a map") raise TypeError(f"BES simulation {path} must contain a map")

View File

@@ -6,7 +6,9 @@ import sys
from PySide6.QtWidgets import QApplication, QMessageBox from PySide6.QtWidgets import QApplication, QMessageBox
from bedit_gui.controllers.simulation_file_controller import SimulationFileController from bedit_gui.controllers.simulation_file_controller import SimulationFileController
from bedit_gui.controllers.log_controller import LogController
from bedit_gui.simulation_models import CompiledModel from bedit_gui.simulation_models import CompiledModel
from bedit_gui.services.application_settings import SimulationApplicationSettings
from bedit_gui.views.simulation_window import SimulationWindow from bedit_gui.views.simulation_window import SimulationWindow
@@ -31,11 +33,18 @@ def parse_arguments(arguments: list[str] | None = None) -> argparse.Namespace:
def main(arguments: list[str] | None = None) -> int: def main(arguments: list[str] | None = None) -> int:
args = parse_arguments(arguments) args = parse_arguments(arguments)
app = QApplication(sys.argv if arguments is None else [sys.argv[0], *arguments]) app = QApplication(sys.argv if arguments is None else [sys.argv[0], *arguments])
app.setOrganizationName("BEdit")
app.setApplicationName("BEdit Simulator") app.setOrganizationName("BEsim")
app.setApplicationName("BEsim")
window = SimulationWindow() window = SimulationWindow()
controller = SimulationFileController(window) controller = SimulationFileController(window)
settings = SimulationApplicationSettings()
LogController(window, settings.log_level)
window.showMaximized() window.showMaximized()
if args.file: if args.file:
try: try:
compiled_model = CompiledModel(args.model_name, args.executable, args.working_directory) if args.handoff else None compiled_model = CompiledModel(args.model_name, args.executable, args.working_directory) if args.handoff else None

View File

@@ -136,7 +136,7 @@
<widget class="QWidget" name="dockWidgetContents"> <widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QListWidget" name="logList"/> <widget class="QListView" name="listView"/>
</item> </item>
</layout> </layout>
</widget> </widget>

View File

@@ -4,7 +4,7 @@ from copy import deepcopy
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from PySide6.QtGui import QDoubleValidator from PySide6.QtGui import QDoubleValidator
from PySide6.QtWidgets import QDialog, QListWidgetItem, QMessageBox, QWidget from PySide6.QtWidgets import QDialog, QLayout, QListWidgetItem, QMessageBox, QWidget
from bedit_core.models import ComponentID from bedit_core.models import ComponentID
from bedit_gui.models import Simulation, SimulationDatabase, SimulationID, SimulationMethod from bedit_gui.models import Simulation, SimulationDatabase, SimulationID, SimulationMethod
@@ -14,7 +14,7 @@ from bedit_gui.ui.generated.ui_simulation_settings import Ui_Dialog
class SimulationSettingsDialog(QDialog): class SimulationSettingsDialog(QDialog):
"""Editor for the detached simulation database of a document.""" """Editor for the detached simulation database of a document."""
def __init__(self, database: SimulationDatabase, components: list[tuple[ComponentID, str]], parent: QWidget | None = None) -> None: def __init__(self, database: SimulationDatabase, components: list[tuple[ComponentID, str]], parent: QWidget | None = None, *, show_simulation_list: bool = True) -> None:
super().__init__(parent) super().__init__(parent)
self.ui = Ui_Dialog() self.ui = Ui_Dialog()
@@ -25,6 +25,9 @@ class SimulationSettingsDialog(QDialog):
self._components = components self._components = components
self._loading = False self._loading = False
if not show_simulation_list:
self._set_layout_visible(self.ui.simulationListLayout, False)
self.ui.startTimeSpinBox.setRange(-1e12, 1e12) self.ui.startTimeSpinBox.setRange(-1e12, 1e12)
self.ui.simLengthSpinBox.setRange(0.0, 1e12) self.ui.simLengthSpinBox.setRange(0.0, 1e12)
self.ui.stepSizeSpinBox.setRange(1e-9, 1e12) self.ui.stepSizeSpinBox.setRange(1e-9, 1e12)
@@ -196,3 +199,14 @@ class SimulationSettingsDialog(QDialog):
self.ui.frame.setEnabled(enabled) self.ui.frame.setEnabled(enabled)
self.ui.removeSimulationButton.setEnabled(enabled) self.ui.removeSimulationButton.setEnabled(enabled)
self.ui.addSimulationButton.setEnabled(bool(self._components)) self.ui.addSimulationButton.setEnabled(bool(self._components))
@classmethod
def _set_layout_visible(cls, layout: QLayout, visible: bool) -> None:
for index in range(layout.count()):
item = layout.itemAt(index)
widget = item.widget()
child_layout = item.layout()
if widget is not None:
widget.setVisible(visible)
elif child_layout is not None:
cls._set_layout_visible(child_layout, visible)

View File

@@ -10,4 +10,4 @@ class SimulationWindow(QMainWindow):
super().__init__() super().__init__()
self.ui = Ui_SimulationWindow() self.ui = Ui_SimulationWindow()
self.ui.setupUi(self) self.ui.setupUi(self)
self.setWindowTitle("BEdit Simulator") self.setWindowTitle("BEsim")