Launching of simulation application
This commit is contained in:
89
src/bedit_gui/controllers/simulation_controller.py
Normal file
89
src/bedit_gui/controllers/simulation_controller.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QObject, QProcess, Qt
|
||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
from bedit_core.models import Component
|
||||
from bedit_gui.documents import Document
|
||||
from bedit_gui.services import simulation_files
|
||||
from bedit_gui.services.simulation_loader import component_choices
|
||||
from bedit_gui.simulation_models import CompiledModel, SimulationRoot
|
||||
from bedit_gui.views.main_window import MainWindow
|
||||
from bedit_simulation import ModelBuildResult, compile_component_sync
|
||||
|
||||
Compiler = Callable[[Component, str | Path], ModelBuildResult]
|
||||
Launcher = Callable[[Path, CompiledModel], bool]
|
||||
|
||||
|
||||
def _launch_simulator(path: Path, compiled_model: CompiledModel) -> bool:
|
||||
arguments = ["-m", "bedit_gui.simulation_application", "--handoff", "--model-name", compiled_model.model_name, "--executable", compiled_model.executable, "--working-directory", compiled_model.working_directory, str(path)]
|
||||
launched = QProcess.startDetached(sys.executable, arguments)
|
||||
return launched[0] if isinstance(launched, tuple) else bool(launched)
|
||||
|
||||
|
||||
class SimulationController(QObject):
|
||||
"""Compile the active BEdit simulation and launch the simulator process."""
|
||||
|
||||
def __init__(self, document: Document, window: MainWindow, compiler: Compiler = compile_component_sync, launcher: Launcher = _launch_simulator) -> None:
|
||||
super().__init__(window)
|
||||
self.document = document
|
||||
self.window = window
|
||||
self.compiler = compiler
|
||||
self.launcher = launcher
|
||||
self.compiled_root: SimulationRoot | None = None
|
||||
self.compiled_model: CompiledModel | None = None
|
||||
window.ui.actionCompile_Model.triggered.connect(self.compile_model)
|
||||
window.ui.actionOpen_Simulation_Window.triggered.connect(self.open_simulation)
|
||||
|
||||
def compile_model(self) -> SimulationRoot | None:
|
||||
database = self.document.simulation_database()
|
||||
settings = database.simulations.get(database.active_simulation) if database.active_simulation is not None else None
|
||||
if settings is None:
|
||||
QMessageBox.warning(self.window, "No active simulation", "Select and accept a simulation settings block first.")
|
||||
return None
|
||||
match = next((choice for choice in component_choices(self.document.model) if choice[0] == settings.component), None)
|
||||
if match is None:
|
||||
QMessageBox.critical(self.window, "Could not compile", "The active simulation references a missing component.")
|
||||
return None
|
||||
component_id, component, component_path = match
|
||||
build_directory = Path(tempfile.mkdtemp(prefix="bedit-compiled-"))
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
self.window.statusBar().showMessage(f"Compiling {component_path}…")
|
||||
try:
|
||||
build = self.compiler(component, build_directory)
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
QMessageBox.critical(self.window, "Could not compile", str(exc))
|
||||
self.window.statusBar().showMessage("Compilation failed")
|
||||
return None
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
self.compiled_model = CompiledModel(build.model_name, str(build.executable.resolve()), str(build.executable.parent.resolve()))
|
||||
self.compiled_root = SimulationRoot(
|
||||
format_version=1,
|
||||
source_document=str(self.document.path.resolve()) if self.document.path is not None else None,
|
||||
source_document_id=str(self.document.model.id),
|
||||
component=component_id,
|
||||
component_path=component_path,
|
||||
settings_name=settings.name,
|
||||
settings=settings,
|
||||
)
|
||||
self.window.statusBar().showMessage(f"Compiled {component_path}")
|
||||
return self.compiled_root
|
||||
|
||||
def open_simulation(self) -> None:
|
||||
root = self.compile_model()
|
||||
if root is None or self.compiled_model is None:
|
||||
return
|
||||
transfer_directory = Path(tempfile.mkdtemp(prefix="bedit-simulator-launch-"))
|
||||
transfer_path = transfer_directory / "simulation.bes"
|
||||
try:
|
||||
simulation_files.save(root, transfer_path)
|
||||
if not self.launcher(transfer_path, self.compiled_model):
|
||||
raise RuntimeError("the simulator process could not be started")
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
QMessageBox.critical(self.window, "Could not open simulator", str(exc))
|
||||
107
src/bedit_gui/controllers/simulation_file_controller.py
Normal file
107
src/bedit_gui/controllers/simulation_file_controller.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QObject, Qt
|
||||
from PySide6.QtWidgets import QApplication, QFileDialog, QInputDialog, QMessageBox
|
||||
|
||||
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.simulation_models import CompiledModel, SimulationRoot
|
||||
from bedit_gui.views.simulation_window import SimulationWindow
|
||||
|
||||
|
||||
class SimulationFileController(QObject):
|
||||
def __init__(self, window: SimulationWindow) -> None:
|
||||
super().__init__(window)
|
||||
self.window = window
|
||||
self.root: SimulationRoot | None = None
|
||||
self.compiled_model: CompiledModel | None = None
|
||||
self.path: Path | None = None
|
||||
window.ui.actionNew_Simulation_Run.triggered.connect(self.new)
|
||||
window.ui.actionOpen_Simulation_Run.triggered.connect(self.open_dialog)
|
||||
window.ui.actionSave_Simulation_Run.triggered.connect(self.save)
|
||||
self._update_window()
|
||||
|
||||
def new(self) -> None:
|
||||
self.root = None
|
||||
self.compiled_model = None
|
||||
self.path = None
|
||||
self._update_window()
|
||||
|
||||
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)
|
||||
if file_path.suffix.lower() == ".bes" or simulation_files.is_simulation_json(file_path):
|
||||
self.root = simulation_files.load(file_path)
|
||||
self.compiled_model = compiled_model or self._recompile_with_wait_cursor(self.root)
|
||||
self.path = file_path if backed_by_file else None
|
||||
else:
|
||||
self.root, self.compiled_model = self._compile_with_wait_cursor(file_path, component=component, simulation=simulation)
|
||||
self.path = None
|
||||
self._update_window()
|
||||
|
||||
def open_dialog(self) -> None:
|
||||
filename, _ = QFileDialog.getOpenFileName(self.window, "Open Simulation", "", "Simulation and BEdit files (*.bes *.beb *.json)")
|
||||
if not filename:
|
||||
return
|
||||
try:
|
||||
path = Path(filename)
|
||||
if path.suffix.lower() == ".bes" or simulation_files.is_simulation_json(path):
|
||||
self.open(path)
|
||||
return
|
||||
document = document_files.load(path)
|
||||
database = document.metadata.get("simulation_database") if document.metadata is not None else None
|
||||
entries = [(f"Simulation: {simulation.name}", None, simulation.name) for simulation in getattr(database, "simulations", {}).values()]
|
||||
entries.extend((f"Component: {component_path}", component_path, None) for _component_id, _component, component_path in component_choices(document))
|
||||
if not entries:
|
||||
raise ValueError("the BEdit document contains no components or simulation settings")
|
||||
label, accepted = QInputDialog.getItem(self.window, "Simulation Target", "Compile:", [entry[0] for entry in entries], 0, False)
|
||||
if accepted:
|
||||
_display, component, simulation = next(entry for entry in entries if entry[0] == label)
|
||||
self.open(path, component=component, simulation=simulation)
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
QMessageBox.critical(self.window, "Could not open simulation", str(exc))
|
||||
|
||||
def save(self) -> None:
|
||||
if self.root is None:
|
||||
return
|
||||
path = self.path
|
||||
if path is None:
|
||||
filename, _ = QFileDialog.getSaveFileName(self.window, "Save Simulation", "simulation.bes", "BEdit simulation (*.bes);;Simulation JSON (*.json)")
|
||||
if not filename:
|
||||
return
|
||||
path = Path(filename)
|
||||
if path.suffix.lower() not in (".bes", ".json"):
|
||||
path = path.with_suffix(".bes")
|
||||
try:
|
||||
simulation_files.save(self.root, path)
|
||||
except (OSError, ValueError) as exc:
|
||||
QMessageBox.critical(self.window, "Could not save simulation", str(exc))
|
||||
return
|
||||
self.path = path
|
||||
self._update_window()
|
||||
|
||||
def _compile_with_wait_cursor(self, path: Path, *, component: str | None, simulation: str | None) -> tuple[SimulationRoot, CompiledModel]:
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
try:
|
||||
return load_and_compile_bedit(path, component_selector=component, simulation_selector=simulation)
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
def _recompile_with_wait_cursor(self, root: SimulationRoot) -> CompiledModel:
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
try:
|
||||
return compile_simulation_root(root)
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
def _update_window(self) -> None:
|
||||
if self.root is None:
|
||||
self.window.setWindowTitle("BEdit Simulator")
|
||||
self.window.statusBar().showMessage("No simulation loaded")
|
||||
self.window.ui.actionSave_Simulation_Run.setEnabled(False)
|
||||
return
|
||||
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"
|
||||
self.window.statusBar().showMessage(f"{self.root.component_path} · {compiled}")
|
||||
self.window.ui.actionSave_Simulation_Run.setEnabled(True)
|
||||
Reference in New Issue
Block a user