104 lines
5.2 KiB
Python
104 lines
5.2 KiB
Python
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.application_logging import get_logger
|
|
from bedit_gui.services.simulation_loader import component_choices
|
|
from bedit_gui.services.simulation_handoff import send_simulation_handoff
|
|
from bedit_gui.simulation_models import CompiledModel, SimulationRoot
|
|
from bedit_gui.views.main_window import MainWindow
|
|
from bedit_simulation import ModelBuildResult, compile_component_sync
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
Compiler = Callable[[Component, str | Path], ModelBuildResult]
|
|
Launcher = Callable[[Path, CompiledModel], bool]
|
|
|
|
|
|
def _launch_simulator(path: Path, compiled_model: CompiledModel) -> bool:
|
|
if send_simulation_handoff(path, compiled_model):
|
|
return True
|
|
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}…")
|
|
logger.info("Compiling model: %s", component_path)
|
|
try:
|
|
self.document.infer_causality(component)
|
|
build = self.compiler(component, build_directory)
|
|
except (OSError, RuntimeError, ValueError) as exc:
|
|
logger.error("Could not compile model %s:\n%s", component_path, 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()), build.output, build.errors)
|
|
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}")
|
|
logger.info("Compiled model %s: %s", build.model_name, self.compiled_model.executable)
|
|
if build.output.strip():
|
|
logger.info("Compiler output:\n%s", build.output.strip())
|
|
if build.errors.strip():
|
|
logger.warning("Compiler errors:\n%s", build.errors.strip())
|
|
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))
|