new runner layout, still todo
This commit is contained in:
@@ -20,6 +20,7 @@ class CompilationResult:
|
||||
graph: dict[str, Any]
|
||||
objects_by_id: dict[str, Any]
|
||||
modelica: str
|
||||
model_name: str
|
||||
|
||||
|
||||
def compile_graph(graph: dict[str, Any]) -> CompilationResult:
|
||||
@@ -31,6 +32,7 @@ def compile_graph(graph: dict[str, Any]) -> CompilationResult:
|
||||
graph=cleaned_graph,
|
||||
objects_by_id=objects_by_id,
|
||||
modelica=emit_model(cleaned_graph, objects_by_id),
|
||||
model_name=model_name_for(cleaned_graph)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
@@ -11,18 +12,48 @@ _worker_lock = Lock()
|
||||
_worker: Thread | None = None
|
||||
|
||||
|
||||
def run_simulation_async(
|
||||
openmodelica_path: str = "", task: Callable[[], None] | None = None
|
||||
def run_validation_async(
|
||||
openmodelica_path: str = "",
|
||||
model: str = "",
|
||||
model_name: str = "",
|
||||
task: Callable[[], None] | None = None,
|
||||
) -> None:
|
||||
"""Start one simulation task in a background thread and return immediately."""
|
||||
"""Start one validation task in a background thread and return immediately."""
|
||||
|
||||
global _worker
|
||||
with _worker_lock:
|
||||
if _worker is not None and _worker.is_alive():
|
||||
raise RuntimeError("A simulation is already running")
|
||||
raise RuntimeError("An OpenModelica task is already running")
|
||||
_worker = Thread(
|
||||
target=_run_safely,
|
||||
args=(task or (lambda: _run_openmodelica(openmodelica_path)),),
|
||||
args=(
|
||||
task
|
||||
or (lambda: _run_validation(openmodelica_path, model, model_name)),
|
||||
),
|
||||
name="bedit-simulation",
|
||||
daemon=True,
|
||||
)
|
||||
_worker.start()
|
||||
|
||||
|
||||
def run_simulation_async(
|
||||
openmodelica_path: str = "",
|
||||
model: str = "",
|
||||
model_name: str = "",
|
||||
task: Callable[[], None] | None = None,
|
||||
) -> None:
|
||||
"""Start one validation-and-simulation task and return immediately."""
|
||||
|
||||
global _worker
|
||||
with _worker_lock:
|
||||
if _worker is not None and _worker.is_alive():
|
||||
raise RuntimeError("An OpenModelica task is already running")
|
||||
_worker = Thread(
|
||||
target=_run_safely,
|
||||
args=(
|
||||
task
|
||||
or (lambda: _run_openmodelica(openmodelica_path, model, model_name)),
|
||||
),
|
||||
name="bedit-simulation",
|
||||
daemon=True,
|
||||
)
|
||||
@@ -47,15 +78,78 @@ def _run_safely(task: Callable[[], None]) -> None:
|
||||
_worker = None
|
||||
|
||||
|
||||
def _run_openmodelica(openmodelica_path: str = "") -> None:
|
||||
"""Create the OpenModelica session and perform the current runner stub work."""
|
||||
def _run_validation(
|
||||
openmodelica_path: str = "", model: str = "", model_name: str = ""
|
||||
) -> None:
|
||||
"""Create the OpenModelica session and perform a validation"""
|
||||
|
||||
from OMPython import OMCSessionZMQ
|
||||
|
||||
omhome = _openmodelica_home(openmodelica_path)
|
||||
omc = OMCSessionZMQ(omhome=omhome)
|
||||
_validate_model(omc, model, model_name)
|
||||
|
||||
|
||||
def _run_openmodelica(
|
||||
openmodelica_path: str = "", model: str = "", model_name: str = ""
|
||||
) -> None:
|
||||
"""Validate and run the current simulation stub in one OMC session."""
|
||||
|
||||
from OMPython import OMCSessionZMQ
|
||||
|
||||
omhome = _openmodelica_home(openmodelica_path)
|
||||
omc = OMCSessionZMQ(omhome=omhome)
|
||||
_validate_model(omc, model, model_name)
|
||||
|
||||
log.info("OpenModelica Version: %s", omc.sendExpression("getVersion()"))
|
||||
|
||||
loaded = omc.sendExpression(f"loadString({json.dumps(model)})")
|
||||
if loaded is not True:
|
||||
details = _get_error_string(omc)
|
||||
raise RuntimeError(f"OpenModelica could not load the model: {details}")
|
||||
|
||||
result = omc.sendExpression(f"checkModel({model_name})")
|
||||
details = _get_error_string(omc)
|
||||
if not isinstance(result, str) or "completed successfully" not in result:
|
||||
message = details or result or "Unknown OpenModelica validation error"
|
||||
raise RuntimeError(f"OpenModelica model validation failed: {message}")
|
||||
|
||||
def _validate_model(omc, model: str, model_name: str) -> None:
|
||||
if not model:
|
||||
raise ValueError("There is no compiled Modelica source to validate")
|
||||
if not model_name:
|
||||
raise ValueError("The compiled model has no Modelica name")
|
||||
|
||||
loaded = omc.sendExpression(f"loadString({json.dumps(model)})")
|
||||
if loaded is not True:
|
||||
details = _get_error_string(omc)
|
||||
raise RuntimeError(f"OpenModelica could not load the model: {details}")
|
||||
|
||||
result = omc.sendExpression(f"checkModel({model_name})")
|
||||
details = _get_error_string(omc)
|
||||
if not isinstance(result, str) or "completed successfully" not in result:
|
||||
message = details or result or "Unknown OpenModelica validation error"
|
||||
raise RuntimeError(f"OpenModelica model validation failed: {message}")
|
||||
log.info("%s", result)
|
||||
if details:
|
||||
log.warning("OpenModelica validation messages: %s", details)
|
||||
|
||||
|
||||
def _get_error_string(omc) -> str:
|
||||
"""Read OMC's deliberately unparsed error response as plain text."""
|
||||
|
||||
raw = omc.sendExpression("getErrorString()", parsed=False)
|
||||
if not isinstance(raw, str):
|
||||
return str(raw or "")
|
||||
raw = raw.strip()
|
||||
if not raw:
|
||||
return ""
|
||||
try:
|
||||
decoded = json.loads(raw)
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
return raw
|
||||
return decoded if isinstance(decoded, str) else str(decoded)
|
||||
|
||||
|
||||
def _openmodelica_home(openmodelica_path: str) -> str | None:
|
||||
"""Convert an optional omc executable path to the home expected by OMPython."""
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Any
|
||||
|
||||
from bedit.core.application_log import get_logger
|
||||
from bedit.core.simulation.compiler import compile_graph
|
||||
from bedit.core.simulation.runner import run_simulation_async
|
||||
from bedit.core.simulation.runner import run_simulation_async, run_validation_async
|
||||
|
||||
|
||||
log = get_logger(__name__)
|
||||
@@ -21,13 +21,30 @@ class Simulation:
|
||||
def compile(self, graph: dict[str, Any]) -> None:
|
||||
"""Compile a serialized component tree and retain the result."""
|
||||
|
||||
self._prepare_compilation(graph)
|
||||
run_validation_async(
|
||||
self.openmodelica_path,
|
||||
self.last_compilation_output,
|
||||
self.model_name,
|
||||
)
|
||||
|
||||
def _prepare_compilation(self, graph: dict[str, Any]) -> None:
|
||||
"""Generate and retain Modelica without starting a background operation."""
|
||||
|
||||
result = compile_graph(graph)
|
||||
self.last_compilation_input = result.graph
|
||||
self.id_list = result.objects_by_id
|
||||
self.last_compilation_output = result.modelica
|
||||
log.info("Generated Modelica model:\n%s", self.last_compilation_output)
|
||||
self.model_name = result.model_name
|
||||
|
||||
def run_simulation(self) -> None:
|
||||
def run_simulation(self, graph: dict[str, Any]) -> None:
|
||||
"""Start the simulation without blocking the calling UI thread."""
|
||||
|
||||
run_simulation_async(self.openmodelica_path)
|
||||
# Always regenerate before running, then validate and simulate as one
|
||||
# background operation so the two phases cannot compete for the worker.
|
||||
self._prepare_compilation(graph)
|
||||
run_simulation_async(
|
||||
self.openmodelica_path,
|
||||
self.last_compilation_output,
|
||||
self.model_name,
|
||||
)
|
||||
|
||||
@@ -431,7 +431,7 @@ class DocumentController(QObject):
|
||||
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()
|
||||
self.simulation.run_simulation(component.to_dict())
|
||||
|
||||
def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
|
||||
item = (
|
||||
|
||||
@@ -379,6 +379,8 @@ class Ui_MainWindow(object):
|
||||
self.menuToolbars.setObjectName(u"menuToolbars")
|
||||
self.menuHelp = QMenu(self.menubar)
|
||||
self.menuHelp.setObjectName(u"menuHelp")
|
||||
self.menuSimulation = QMenu(self.menubar)
|
||||
self.menuSimulation.setObjectName(u"menuSimulation")
|
||||
MainWindow.setMenuBar(self.menubar)
|
||||
self.fileToolbar = QToolBar(MainWindow)
|
||||
self.fileToolbar.setObjectName(u"fileToolbar")
|
||||
@@ -405,6 +407,7 @@ class Ui_MainWindow(object):
|
||||
self.menubar.addAction(self.menuFile.menuAction())
|
||||
self.menubar.addAction(self.menuEdit.menuAction())
|
||||
self.menubar.addAction(self.menuView.menuAction())
|
||||
self.menubar.addAction(self.menuSimulation.menuAction())
|
||||
self.menubar.addAction(self.menuHelp.menuAction())
|
||||
self.menuFile.addAction(self.actionNew)
|
||||
self.menuFile.addAction(self.actionOpen)
|
||||
@@ -430,6 +433,10 @@ class Ui_MainWindow(object):
|
||||
self.menuView.addAction(self.menuToolbars.menuAction())
|
||||
self.menuHelp.addAction(self.actionAbout)
|
||||
self.menuHelp.addAction(self.actionAboutQt)
|
||||
self.menuSimulation.addAction(self.actionSimulationSettings)
|
||||
self.menuSimulation.addAction(self.actionGraphParameters)
|
||||
self.menuSimulation.addAction(self.actionCompile)
|
||||
self.menuSimulation.addAction(self.actionRunSimulation)
|
||||
self.fileToolbar.addAction(self.actionNew)
|
||||
self.fileToolbar.addAction(self.actionOpen)
|
||||
self.fileToolbar.addAction(self.actionSave)
|
||||
@@ -618,6 +625,7 @@ class Ui_MainWindow(object):
|
||||
self.menuPanels.setTitle(QCoreApplication.translate("MainWindow", u"&Panels", None))
|
||||
self.menuToolbars.setTitle(QCoreApplication.translate("MainWindow", u"&Toolbars", None))
|
||||
self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"&Help", None))
|
||||
self.menuSimulation.setTitle(QCoreApplication.translate("MainWindow", u"Simulation", None))
|
||||
self.fileToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"File", None))
|
||||
self.editToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Edit", None))
|
||||
self.cameraToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Camera", None))
|
||||
|
||||
@@ -545,9 +545,19 @@
|
||||
<addaction name="actionAbout"/>
|
||||
<addaction name="actionAboutQt"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuSimulation">
|
||||
<property name="title">
|
||||
<string>Simulation</string>
|
||||
</property>
|
||||
<addaction name="actionSimulationSettings"/>
|
||||
<addaction name="actionGraphParameters"/>
|
||||
<addaction name="actionCompile"/>
|
||||
<addaction name="actionRunSimulation"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuEdit"/>
|
||||
<addaction name="menuView"/>
|
||||
<addaction name="menuSimulation"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="fileToolbar">
|
||||
@@ -654,8 +664,12 @@
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
<normaloff>:/icons/icons/configure.png</normaloff>:/icons/icons/configure.png</iconset>
|
||||
</property>
|
||||
<property name="text"><string>Graph Parameters</string></property>
|
||||
<property name="statusTip"><string>Edit parameters throughout the active graph</string></property>
|
||||
<property name="text">
|
||||
<string>Graph Parameters</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Edit parameters throughout the active graph</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCompile">
|
||||
<property name="icon">
|
||||
@@ -677,9 +691,15 @@
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
<normaloff>:/icons/icons/media-playback-start.png</normaloff>:/icons/icons/media-playback-start.png</iconset>
|
||||
</property>
|
||||
<property name="text"><string>Run</string></property>
|
||||
<property name="statusTip"><string>Run the simulation</string></property>
|
||||
<property name="shortcut"><string>F6</string></property>
|
||||
<property name="text">
|
||||
<string>Run</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Run the simulation</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F6</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNew">
|
||||
<property name="icon">
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"showName": false
|
||||
},
|
||||
"library": {
|
||||
"showSubtree": true
|
||||
"showSubtree": false
|
||||
},
|
||||
"implementation": {
|
||||
"kind": "graph",
|
||||
|
||||
Reference in New Issue
Block a user