new runner layout, still todo
This commit is contained in:
@@ -20,6 +20,7 @@ class CompilationResult:
|
|||||||
graph: dict[str, Any]
|
graph: dict[str, Any]
|
||||||
objects_by_id: dict[str, Any]
|
objects_by_id: dict[str, Any]
|
||||||
modelica: str
|
modelica: str
|
||||||
|
model_name: str
|
||||||
|
|
||||||
|
|
||||||
def compile_graph(graph: dict[str, Any]) -> CompilationResult:
|
def compile_graph(graph: dict[str, Any]) -> CompilationResult:
|
||||||
@@ -31,6 +32,7 @@ def compile_graph(graph: dict[str, Any]) -> CompilationResult:
|
|||||||
graph=cleaned_graph,
|
graph=cleaned_graph,
|
||||||
objects_by_id=objects_by_id,
|
objects_by_id=objects_by_id,
|
||||||
modelica=emit_model(cleaned_graph, 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
|
import os
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -11,18 +12,48 @@ _worker_lock = Lock()
|
|||||||
_worker: Thread | None = None
|
_worker: Thread | None = None
|
||||||
|
|
||||||
|
|
||||||
def run_simulation_async(
|
def run_validation_async(
|
||||||
openmodelica_path: str = "", task: Callable[[], None] | None = None
|
openmodelica_path: str = "",
|
||||||
|
model: str = "",
|
||||||
|
model_name: str = "",
|
||||||
|
task: Callable[[], None] | 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
|
global _worker
|
||||||
with _worker_lock:
|
with _worker_lock:
|
||||||
if _worker is not None and _worker.is_alive():
|
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(
|
_worker = Thread(
|
||||||
target=_run_safely,
|
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",
|
name="bedit-simulation",
|
||||||
daemon=True,
|
daemon=True,
|
||||||
)
|
)
|
||||||
@@ -47,15 +78,78 @@ def _run_safely(task: Callable[[], None]) -> None:
|
|||||||
_worker = None
|
_worker = None
|
||||||
|
|
||||||
|
|
||||||
def _run_openmodelica(openmodelica_path: str = "") -> None:
|
def _run_validation(
|
||||||
"""Create the OpenModelica session and perform the current runner stub work."""
|
openmodelica_path: str = "", model: str = "", model_name: str = ""
|
||||||
|
) -> None:
|
||||||
|
"""Create the OpenModelica session and perform a validation"""
|
||||||
|
|
||||||
from OMPython import OMCSessionZMQ
|
from OMPython import OMCSessionZMQ
|
||||||
|
|
||||||
omhome = _openmodelica_home(openmodelica_path)
|
omhome = _openmodelica_home(openmodelica_path)
|
||||||
omc = OMCSessionZMQ(omhome=omhome)
|
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()"))
|
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:
|
def _openmodelica_home(openmodelica_path: str) -> str | None:
|
||||||
"""Convert an optional omc executable path to the home expected by OMPython."""
|
"""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.application_log import get_logger
|
||||||
from bedit.core.simulation.compiler import compile_graph
|
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__)
|
log = get_logger(__name__)
|
||||||
@@ -21,13 +21,30 @@ class Simulation:
|
|||||||
def compile(self, graph: dict[str, Any]) -> None:
|
def compile(self, graph: dict[str, Any]) -> None:
|
||||||
"""Compile a serialized component tree and retain the result."""
|
"""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)
|
result = compile_graph(graph)
|
||||||
self.last_compilation_input = result.graph
|
self.last_compilation_input = result.graph
|
||||||
self.id_list = result.objects_by_id
|
self.id_list = result.objects_by_id
|
||||||
self.last_compilation_output = result.modelica
|
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."""
|
"""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
|
component = self.active_component
|
||||||
if component is None or component.implementation_kind != "graph":
|
if component is None or component.implementation_kind != "graph":
|
||||||
raise ValueError("Open a graph component before running a simulation")
|
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:
|
def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
|
||||||
item = (
|
item = (
|
||||||
|
|||||||
@@ -379,6 +379,8 @@ class Ui_MainWindow(object):
|
|||||||
self.menuToolbars.setObjectName(u"menuToolbars")
|
self.menuToolbars.setObjectName(u"menuToolbars")
|
||||||
self.menuHelp = QMenu(self.menubar)
|
self.menuHelp = QMenu(self.menubar)
|
||||||
self.menuHelp.setObjectName(u"menuHelp")
|
self.menuHelp.setObjectName(u"menuHelp")
|
||||||
|
self.menuSimulation = QMenu(self.menubar)
|
||||||
|
self.menuSimulation.setObjectName(u"menuSimulation")
|
||||||
MainWindow.setMenuBar(self.menubar)
|
MainWindow.setMenuBar(self.menubar)
|
||||||
self.fileToolbar = QToolBar(MainWindow)
|
self.fileToolbar = QToolBar(MainWindow)
|
||||||
self.fileToolbar.setObjectName(u"fileToolbar")
|
self.fileToolbar.setObjectName(u"fileToolbar")
|
||||||
@@ -405,6 +407,7 @@ class Ui_MainWindow(object):
|
|||||||
self.menubar.addAction(self.menuFile.menuAction())
|
self.menubar.addAction(self.menuFile.menuAction())
|
||||||
self.menubar.addAction(self.menuEdit.menuAction())
|
self.menubar.addAction(self.menuEdit.menuAction())
|
||||||
self.menubar.addAction(self.menuView.menuAction())
|
self.menubar.addAction(self.menuView.menuAction())
|
||||||
|
self.menubar.addAction(self.menuSimulation.menuAction())
|
||||||
self.menubar.addAction(self.menuHelp.menuAction())
|
self.menubar.addAction(self.menuHelp.menuAction())
|
||||||
self.menuFile.addAction(self.actionNew)
|
self.menuFile.addAction(self.actionNew)
|
||||||
self.menuFile.addAction(self.actionOpen)
|
self.menuFile.addAction(self.actionOpen)
|
||||||
@@ -430,6 +433,10 @@ class Ui_MainWindow(object):
|
|||||||
self.menuView.addAction(self.menuToolbars.menuAction())
|
self.menuView.addAction(self.menuToolbars.menuAction())
|
||||||
self.menuHelp.addAction(self.actionAbout)
|
self.menuHelp.addAction(self.actionAbout)
|
||||||
self.menuHelp.addAction(self.actionAboutQt)
|
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.actionNew)
|
||||||
self.fileToolbar.addAction(self.actionOpen)
|
self.fileToolbar.addAction(self.actionOpen)
|
||||||
self.fileToolbar.addAction(self.actionSave)
|
self.fileToolbar.addAction(self.actionSave)
|
||||||
@@ -618,6 +625,7 @@ class Ui_MainWindow(object):
|
|||||||
self.menuPanels.setTitle(QCoreApplication.translate("MainWindow", u"&Panels", None))
|
self.menuPanels.setTitle(QCoreApplication.translate("MainWindow", u"&Panels", None))
|
||||||
self.menuToolbars.setTitle(QCoreApplication.translate("MainWindow", u"&Toolbars", None))
|
self.menuToolbars.setTitle(QCoreApplication.translate("MainWindow", u"&Toolbars", None))
|
||||||
self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"&Help", 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.fileToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"File", None))
|
||||||
self.editToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Edit", None))
|
self.editToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Edit", None))
|
||||||
self.cameraToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Camera", None))
|
self.cameraToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Camera", None))
|
||||||
|
|||||||
@@ -545,9 +545,19 @@
|
|||||||
<addaction name="actionAbout"/>
|
<addaction name="actionAbout"/>
|
||||||
<addaction name="actionAboutQt"/>
|
<addaction name="actionAboutQt"/>
|
||||||
</widget>
|
</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="menuFile"/>
|
||||||
<addaction name="menuEdit"/>
|
<addaction name="menuEdit"/>
|
||||||
<addaction name="menuView"/>
|
<addaction name="menuView"/>
|
||||||
|
<addaction name="menuSimulation"/>
|
||||||
<addaction name="menuHelp"/>
|
<addaction name="menuHelp"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QToolBar" name="fileToolbar">
|
<widget class="QToolBar" name="fileToolbar">
|
||||||
@@ -654,8 +664,12 @@
|
|||||||
<iconset resource="../resources/resources.qrc">
|
<iconset resource="../resources/resources.qrc">
|
||||||
<normaloff>:/icons/icons/configure.png</normaloff>:/icons/icons/configure.png</iconset>
|
<normaloff>:/icons/icons/configure.png</normaloff>:/icons/icons/configure.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text"><string>Graph Parameters</string></property>
|
<property name="text">
|
||||||
<property name="statusTip"><string>Edit parameters throughout the active graph</string></property>
|
<string>Graph Parameters</string>
|
||||||
|
</property>
|
||||||
|
<property name="statusTip">
|
||||||
|
<string>Edit parameters throughout the active graph</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionCompile">
|
<action name="actionCompile">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
@@ -677,9 +691,15 @@
|
|||||||
<iconset resource="../resources/resources.qrc">
|
<iconset resource="../resources/resources.qrc">
|
||||||
<normaloff>:/icons/icons/media-playback-start.png</normaloff>:/icons/icons/media-playback-start.png</iconset>
|
<normaloff>:/icons/icons/media-playback-start.png</normaloff>:/icons/icons/media-playback-start.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text"><string>Run</string></property>
|
<property name="text">
|
||||||
<property name="statusTip"><string>Run the simulation</string></property>
|
<string>Run</string>
|
||||||
<property name="shortcut"><string>F6</string></property>
|
</property>
|
||||||
|
<property name="statusTip">
|
||||||
|
<string>Run the simulation</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>F6</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionNew">
|
<action name="actionNew">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
"showName": false
|
"showName": false
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"showSubtree": true
|
"showSubtree": false
|
||||||
},
|
},
|
||||||
"implementation": {
|
"implementation": {
|
||||||
"kind": "graph",
|
"kind": "graph",
|
||||||
|
|||||||
Reference in New Issue
Block a user