diff --git a/BEdit/AGENTS.md b/BEdit/AGENTS.md
index 010b527..910da33 100644
--- a/BEdit/AGENTS.md
+++ b/BEdit/AGENTS.md
@@ -25,7 +25,7 @@ src/bedit/
│ ├── port_types.py # Port type definitions and compatibility
│ ├── serializer.py # JSON persistence
│ ├── libraries.py # Library file discovery and parsing
-│ └── simulation/ # Qt-free simulation service and compiler/runtime code
+│ └── simulation/ # Qt-free composition and OpenModelica interface code
└── gui/ # All Qt-dependent code
├── app.py # QApplication startup and palette
├── main_window.py # Top-level UI orchestration
@@ -105,16 +105,19 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
- File → Reload Simulation Code (`Ctrl+F5`) reloads modules under
`bedit.core.simulation`, replaces the shared application/controller service,
and preserves the previous instance attributes where possible.
-- Simulation compilation lives in `core/simulation/compiler.py`; the simulation
- service only owns application state and delegates compilation. Ports with
+- Modelica composition lives in `core/simulation/composer.py`; the simulation
+ service only owns application state and delegates composition. Ports with
`multipleConnections` are emitted as Modelica arrays. Their size is inferred
per component instance from graph connections and exposed while compiling as
`$portname_N$`; array connection endpoints receive stable one-based indices in
graph connection order.
-- Blocking simulator integration belongs in `core/simulation/runner.py` and runs
- on its daemon worker thread. Never perform OMPython startup or simulation work
- directly on the Qt GUI thread; background failures must go to the application
- log.
+- OpenModelica integration belongs in `core/simulation/openmodelica.py`. Its
+ persistent worker and OMC session start lazily on the first queued request.
+ Never perform OMPython work directly on the Qt GUI thread. Result and error
+ callbacks run on the worker thread and must use a Qt signal before touching UI.
+ One lazy temporary working directory is shared by all requests in the session.
+ Explicit application shutdown closes OMC and removes that directory plus the
+ current session's OMPython log and port files; `__del__` is only a fallback.
- The optional OpenModelica executable is persisted as
`simulation/openModelicaPath`. The GUI passes it into `Simulation`; core must
not read `QSettings`. An empty path uses OMPython/PATH discovery, while an
diff --git a/BEdit/src/bedit/core/simulation/__init__.py b/BEdit/src/bedit/core/simulation/__init__.py
index 41515e1..088f9ef 100644
--- a/BEdit/src/bedit/core/simulation/__init__.py
+++ b/BEdit/src/bedit/core/simulation/__init__.py
@@ -1,3 +1,4 @@
from bedit.core.simulation.service import Simulation
+from bedit.core.simulation.openmodelica import OpenModelicaInterface
-__all__ = ["Simulation"]
+__all__ = ["OpenModelicaInterface", "Simulation"]
diff --git a/BEdit/src/bedit/core/simulation/compiler.py b/BEdit/src/bedit/core/simulation/composer.py
similarity index 97%
rename from BEdit/src/bedit/core/simulation/compiler.py
rename to BEdit/src/bedit/core/simulation/composer.py
index dbc7a0f..f78c8c7 100644
--- a/BEdit/src/bedit/core/simulation/compiler.py
+++ b/BEdit/src/bedit/core/simulation/composer.py
@@ -14,8 +14,8 @@ _BEVALUE_PATTERN = re.compile(r"\$([A-Za-z_][A-Za-z0-9_]*)\$")
@dataclass(frozen=True)
-class CompilationResult:
- """The intermediate data and generated source produced by compilation."""
+class CompositionResult:
+ """The intermediate data and generated source produced by composition."""
graph: dict[str, Any]
objects_by_id: dict[str, Any]
@@ -23,16 +23,16 @@ class CompilationResult:
model_name: str
-def compile_graph(graph: dict[str, Any]) -> CompilationResult:
- """Clean, index, and emit a serialized component tree."""
+def compose_graph(graph: dict[str, Any]) -> CompositionResult:
+ """Clean, index, and compose a serialized component tree."""
cleaned_graph = cleanup_graph(deepcopy(graph))
objects_by_id = build_id_list(cleaned_graph)
- return CompilationResult(
+ return CompositionResult(
graph=cleaned_graph,
objects_by_id=objects_by_id,
modelica=emit_model(cleaned_graph, objects_by_id),
- model_name=model_name_for(cleaned_graph)
+ model_name=model_name_for(cleaned_graph),
)
@@ -85,7 +85,7 @@ def emit_model(
) -> str:
"""Emit a component and its nested definitions as Modelica source."""
- del id_list # Kept in the public API for compiler extensions and inspection.
+ del id_list # Kept in the public API for composer extensions and inspection.
indentation = "\t" * indent
body_indent = "\t" * (indent + 1)
model_name = model_name_for(graph)
@@ -283,7 +283,7 @@ def _port_count_macros(
def expand_bevalues(text: str, values: dict[str, str]) -> str:
- """Replace BEdit ``$name$`` macros and reject unresolved compiler values."""
+ """Replace BEdit ``$name$`` macros and reject unresolved composer values."""
def replace(match: re.Match[str]) -> str:
name = match.group(1)
diff --git a/BEdit/src/bedit/core/simulation/openmodelica.py b/BEdit/src/bedit/core/simulation/openmodelica.py
new file mode 100644
index 0000000..f487514
--- /dev/null
+++ b/BEdit/src/bedit/core/simulation/openmodelica.py
@@ -0,0 +1,247 @@
+import json
+import os
+import shutil
+import tempfile
+from collections.abc import Callable
+from dataclasses import dataclass
+from pathlib import Path
+from queue import Queue
+from threading import Lock, Thread, current_thread
+from typing import Any
+
+from bedit.core.application_log import get_logger
+
+
+log = get_logger(__name__)
+ResultCallback = Callable[[Any], None]
+ErrorCallback = Callable[[Exception], None]
+
+
+@dataclass(frozen=True)
+class _Request:
+ expression: str
+ parsed: bool
+ callback: ResultCallback | None
+ error_callback: ErrorCallback | None
+
+
+class OpenModelicaInterface:
+ """Asynchronous, persistent interface to one OpenModelica session.
+
+ The worker and OMC session are created lazily for the first request. Callback
+ functions execute on the worker thread and must not manipulate Qt widgets.
+ """
+
+ def __init__(self, executable_path: str = "") -> None:
+ self._executable_path = executable_path
+ self._lifecycle_lock = Lock()
+ self._queue: Queue[_Request | None] | None = None
+ self._worker: Thread | None = None
+ self._temp_dir: Path | None = None
+
+ @property
+ def executable_path(self) -> str:
+ return self._executable_path
+
+ def configure(self, executable_path: str) -> None:
+ """Use a new executable path for subsequent requests."""
+
+ if executable_path == self._executable_path:
+ return
+ self.shutdown(wait=True)
+ self._executable_path = executable_path
+
+ def get_version(
+ self,
+ callback: ResultCallback | None = None,
+ error_callback: ErrorCallback | None = None,
+ ) -> None:
+ """Request the OpenModelica version without blocking the caller."""
+ self.send_expression("getVersion()", callback, error_callback)
+
+ def load_model(
+ self,
+ model: str,
+ callback: ResultCallback | None = None,
+ error_callback: ErrorCallback | None = None,
+ ) -> None:
+ """Load a composed model into OpenModelica"""
+ self.send_expression(f"loadString({json.dumps(model)})", callback, error_callback)
+
+ def compile_model(
+ self,
+ model_name: str,
+ callback: ResultCallback | None = None,
+ error_callback: ErrorCallback | None = None,
+ ) -> None:
+ """Compile a loaded model in OpenModelica"""
+ self.send_expression(f"buildModel({model_name})", callback, error_callback)
+
+ def exec_system(
+ self,
+ cmd: str,
+ callback: ResultCallback | None = None,
+ error_callback: ErrorCallback | None = None,
+ ) -> None:
+ """Execute system command from OpenModelica"""
+ self.send_expression(f"system(\"{cmd}\", \"system_out.txt\")", callback, error_callback)
+
+ def send_expression(
+ self,
+ expression: str,
+ callback: ResultCallback | None = None,
+ error_callback: ErrorCallback | None = None,
+ *,
+ parsed: bool = True,
+ ) -> None:
+ """Queue an OMC expression for ordered execution on the worker thread."""
+
+ request = _Request(expression, parsed, callback, error_callback)
+ self._ensure_worker().put(request)
+
+ def shutdown(self, *, wait: bool = True) -> None:
+ """Ask the worker to close its OMC session after queued requests."""
+
+ with self._lifecycle_lock:
+ worker = self._worker
+ queue = self._queue
+ self._worker = None
+ self._queue = None
+ if queue is not None:
+ queue.put(None)
+ if wait and worker is not None and worker is not current_thread():
+ worker.join()
+ if worker is None:
+ self._cleanup_temp_dir()
+
+ def __del__(self) -> None:
+ """Best-effort fallback; normal application shutdown is explicit."""
+
+ try:
+ self.shutdown(wait=False)
+ except Exception:
+ pass
+
+ def _ensure_worker(self) -> Queue[_Request | None]:
+ with self._lifecycle_lock:
+ if self._worker is not None and self._worker.is_alive():
+ return self._queue
+ temp_dir = self._ensure_temp_dir_locked()
+ queue: Queue[_Request | None] = Queue()
+ worker = Thread(
+ target=self._worker_main,
+ args=(queue, self._executable_path, temp_dir),
+ name="bedit-openmodelica",
+ daemon=True,
+ )
+ self._queue = queue
+ self._worker = worker
+ worker.start()
+ return queue
+
+ def _worker_main(
+ self,
+ queue: Queue[_Request | None],
+ executable_path: str,
+ temp_dir: Path,
+ ) -> None:
+ omc = None
+ try:
+ while (request := queue.get()) is not None:
+ try:
+ if omc is None:
+ omc = _create_session(executable_path)
+ changed_directory = omc.sendExpression(
+ f"cd({json.dumps(str(temp_dir))})"
+ )
+ if not changed_directory:
+ raise RuntimeError(
+ f"OpenModelica could not use {str(temp_dir)!r}"
+ )
+ result = omc.sendExpression(
+ request.expression, parsed=request.parsed
+ )
+ except Exception as error:
+ if request.error_callback is None:
+ log.exception(
+ "OpenModelica request failed: %s", request.expression
+ )
+ else:
+ _deliver_callback(request.error_callback, error)
+ else:
+ if request.callback is not None:
+ _deliver_callback(request.callback, result)
+ finally:
+ transport_files = _ompython_transport_files(omc)
+ if omc is not None:
+ try:
+ omc.sendExpression("quit()")
+ except Exception:
+ log.debug("Could not close OpenModelica session", exc_info=True)
+ for path in transport_files:
+ try:
+ path.unlink(missing_ok=True)
+ except OSError:
+ log.debug("Could not remove OMPython file %s", path, exc_info=True)
+ self._cleanup_temp_dir(temp_dir)
+
+ def _ensure_temp_dir_locked(self) -> Path:
+ if self._temp_dir is None:
+ self._temp_dir = Path(tempfile.mkdtemp(prefix="bedit-openmodelica-"))
+ return self._temp_dir
+
+ def _cleanup_temp_dir(self, expected: Path | None = None) -> None:
+ with self._lifecycle_lock:
+ if expected is not None and self._temp_dir != expected:
+ temp_dir = expected
+ else:
+ temp_dir = self._temp_dir
+ self._temp_dir = None
+ if temp_dir is not None:
+ shutil.rmtree(temp_dir, ignore_errors=True)
+
+
+def _deliver_callback(callback: Callable[[Any], None], value: Any) -> None:
+ try:
+ callback(value)
+ except Exception:
+ log.exception("OpenModelica callback failed")
+
+
+def _create_session(executable_path: str):
+ from OMPython import OMCSessionZMQ
+
+ return OMCSessionZMQ(omhome=_openmodelica_home(executable_path))
+
+
+def _ompython_transport_files(omc) -> set[Path]:
+ """Return only the log and port files owned by this OMPython session."""
+
+ if omc is None:
+ return set()
+ process = getattr(omc, "omc_process", None)
+ if process is None:
+ return set()
+ files: set[Path] = set()
+ temp_dir = getattr(process, "_temp_dir", None)
+ file_base = getattr(process, "_omc_filebase", None)
+ if temp_dir is not None and file_base:
+ files.add(Path(temp_dir) / f"{file_base}.log")
+ try:
+ port_file = process._get_portfile_path()
+ except Exception:
+ port_file = None
+ if port_file is not None:
+ files.add(Path(port_file))
+ return files
+
+
+def _openmodelica_home(executable_path: str) -> str | None:
+ """Convert an optional omc executable path to the home expected by OMPython."""
+
+ if not executable_path.strip():
+ return None
+ path = Path(os.path.expandvars(executable_path)).expanduser()
+ if path.name.lower() in {"omc", "omc.exe"}:
+ return str(path.parent.parent)
+ return str(path)
diff --git a/BEdit/src/bedit/core/simulation/runner.py b/BEdit/src/bedit/core/simulation/runner.py
deleted file mode 100644
index e6d0052..0000000
--- a/BEdit/src/bedit/core/simulation/runner.py
+++ /dev/null
@@ -1,162 +0,0 @@
-import json
-import os
-from collections.abc import Callable
-from pathlib import Path
-from threading import Lock, Thread
-
-from bedit.core.application_log import get_logger
-
-
-log = get_logger(__name__)
-_worker_lock = Lock()
-_worker: Thread | None = None
-
-
-def run_validation_async(
- openmodelica_path: str = "",
- model: str = "",
- model_name: str = "",
- task: Callable[[], None] | None = None,
-) -> None:
- """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("An OpenModelica task is already running")
- _worker = Thread(
- target=_run_safely,
- 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,
- )
- _worker.start()
-
-
-def simulation_is_running() -> bool:
- """Return whether the background simulation worker is active."""
-
- with _worker_lock:
- return _worker is not None and _worker.is_alive()
-
-
-def _run_safely(task: Callable[[], None]) -> None:
- global _worker
- try:
- task()
- except Exception:
- log.exception("Simulation run failed")
- finally:
- with _worker_lock:
- _worker = None
-
-
-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."""
-
- if not openmodelica_path.strip():
- return None
- path = Path(os.path.expandvars(openmodelica_path)).expanduser()
- if path.name.lower() in {"omc", "omc.exe"}:
- return str(path.parent.parent)
- return str(path)
diff --git a/BEdit/src/bedit/core/simulation/service.py b/BEdit/src/bedit/core/simulation/service.py
index a24151a..2414ab8 100644
--- a/BEdit/src/bedit/core/simulation/service.py
+++ b/BEdit/src/bedit/core/simulation/service.py
@@ -1,50 +1,102 @@
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, run_validation_async
+from bedit.core.simulation.composer import compose_graph
+from bedit.core.simulation.openmodelica import (
+ ErrorCallback,
+ OpenModelicaInterface,
+ ResultCallback,
+)
log = get_logger(__name__)
class Simulation:
- """Application-owned simulation state and compiler facade."""
+ """Application-owned composition state and OpenModelica interface."""
def __init__(self, *, openmodelica_path: str = "") -> None:
self.state: dict[str, Any] = {}
- self.last_compilation_input: dict[str, Any] | None = None
- self.last_compilation_output: str | None = None
+ self.last_composition_input: dict[str, Any] | None = None
+ self.last_composition_output: str | None = None
self.id_list: dict[str, Any] = {}
- self.openmodelica_path = openmodelica_path
+ self.model_name: str | None = None
+ self._openmodelica_path = openmodelica_path
+ self.openmodelica = OpenModelicaInterface(openmodelica_path)
+ self.model_path : str | None = None
- def compile(self, graph: dict[str, Any]) -> None:
- """Compile a serialized component tree and retain the result."""
+ @property
+ def openmodelica_path(self) -> str:
+ return self._openmodelica_path
- self._prepare_compilation(graph)
- run_validation_async(
- self.openmodelica_path,
- self.last_compilation_output,
- self.model_name,
+ @openmodelica_path.setter
+ def openmodelica_path(self, value: str) -> None:
+ self._openmodelica_path = value
+ self.openmodelica.configure(value)
+
+ def compose(self, graph: dict[str, Any]) -> None:
+ """Compose and retain the active graph's Modelica representation."""
+
+ self.model_path = None
+
+ # Create openmodelica model
+ result = compose_graph(graph)
+ self.last_composition_input = result.graph
+ self.id_list = result.objects_by_id
+ self.last_composition_output = result.modelica
+ self.model_name = result.model_name
+ log.info("Composed OpenModelica model:\n%s", result.modelica)
+
+ # Compile openmodelica model
+ self.openmodelica.load_model(
+ self.last_composition_output,
+ lambda result: log.info("Loading OK: %s", result),
+ lambda error: log.info("Loading ERROR: %s", repr(error)),
)
- def _prepare_compilation(self, graph: dict[str, Any]) -> None:
- """Generate and retain Modelica without starting a background operation."""
+ def _model_compiled(result):
+ log.info("Compiling OK: %s", result)
+ self.model_path = result[0]
- result = compile_graph(graph)
- self.last_compilation_input = result.graph
- self.id_list = result.objects_by_id
- self.last_compilation_output = result.modelica
- self.model_name = result.model_name
+ self.openmodelica.compile_model(
+ self.model_name,
+ _model_compiled,
+ lambda error: log.info("Compiling ERROR: %s", repr(error)),
+ )
def run_simulation(self, graph: dict[str, Any]) -> None:
- """Start the simulation without blocking the calling UI thread."""
+ """Reserved for a future simulation request."""
- # 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,
+ if self.model_path is None:
+ self.compose(graph)
+
+ self.openmodelica.get_version(lambda val: log.info(val))
+ log.info(f"Running model binary {self.model_path}")
+
+ self.openmodelica.exec_system(
+ f'./{self.model_name} {self.build_simulation_arguments()}',
+ lambda result: log.info("Running OK: %s", result),
+ lambda error: log.info("Running ERROR: %s", repr(error)),
)
+
+ def shutdown(self, *, wait: bool = True) -> None:
+ """Close OpenModelica and clean its generated working directory."""
+ self.openmodelica.shutdown(wait=wait)
+ self.model_path = None
+
+ def build_simulation_arguments(self) -> str:
+ opts = self.last_composition_input['implementation']['graph']['simulation']
+
+
+ args = "-port=40696 -outputFormat=csv -logFormat=xmltcp "
+
+ args += f"-startTime={opts['startTime']} -stopTime={opts['stopTime']} "
+
+ step_size = opts['intervalTime']
+ if opts['intervalMode'] == 'numberOfIntervals':
+ step_size = (opts['stopTime']-opts['startTime'])/opts['numberOfIntervals']
+ args += f"-stepSize={step_size}"
+
+ log.info("Running model with: %s", args)
+
+ return args
diff --git a/BEdit/src/bedit/gui/controllers/document.py b/BEdit/src/bedit/gui/controllers/document.py
index 3f4b7be..8e3c71b 100644
--- a/BEdit/src/bedit/gui/controllers/document.py
+++ b/BEdit/src/bedit/gui/controllers/document.py
@@ -421,11 +421,11 @@ class DocumentController(QObject):
if old != new:
self.undo_stack.push(EditGraphParametersCommand(self, old, new))
- def compile_active_graph(self) -> None:
+ def compose_active_graph(self) -> None:
component = self.active_component
if component is None or component.implementation_kind != "graph":
- raise ValueError("Open a graph component before compiling")
- self.simulation.compile(component.to_dict())
+ raise ValueError("Open a graph component before composing")
+ self.simulation.compose(component.to_dict())
def run_simulation(self) -> None:
component = self.active_component
diff --git a/BEdit/src/bedit/gui/generated/ui_main_window.py b/BEdit/src/bedit/gui/generated/ui_main_window.py
index 54a3ae2..16bf1ef 100644
--- a/BEdit/src/bedit/gui/generated/ui_main_window.py
+++ b/BEdit/src/bedit/gui/generated/ui_main_window.py
@@ -41,11 +41,11 @@ class Ui_MainWindow(object):
icon1 = QIcon()
icon1.addFile(u":/icons/icons/configure.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionGraphParameters.setIcon(icon1)
- self.actionCompile = QAction(MainWindow)
- self.actionCompile.setObjectName(u"actionCompile")
+ self.actionCompose = QAction(MainWindow)
+ self.actionCompose.setObjectName(u"actionCompose")
icon2 = QIcon()
icon2.addFile(u":/icons/icons/run-build.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
- self.actionCompile.setIcon(icon2)
+ self.actionCompose.setIcon(icon2)
self.actionRunSimulation = QAction(MainWindow)
self.actionRunSimulation.setObjectName(u"actionRunSimulation")
icon3 = QIcon()
@@ -435,7 +435,7 @@ class Ui_MainWindow(object):
self.menuHelp.addAction(self.actionAboutQt)
self.menuSimulation.addAction(self.actionSimulationSettings)
self.menuSimulation.addAction(self.actionGraphParameters)
- self.menuSimulation.addAction(self.actionCompile)
+ self.menuSimulation.addAction(self.actionCompose)
self.menuSimulation.addAction(self.actionRunSimulation)
self.fileToolbar.addAction(self.actionNew)
self.fileToolbar.addAction(self.actionOpen)
@@ -451,7 +451,7 @@ class Ui_MainWindow(object):
self.cameraToolbar.addAction(self.actionCenterView)
self.simulationToolbar.addAction(self.actionSimulationSettings)
self.simulationToolbar.addAction(self.actionGraphParameters)
- self.simulationToolbar.addAction(self.actionCompile)
+ self.simulationToolbar.addAction(self.actionCompose)
self.simulationToolbar.addAction(self.actionRunSimulation)
self.retranslateUi(MainWindow)
@@ -472,12 +472,12 @@ class Ui_MainWindow(object):
#if QT_CONFIG(statustip)
self.actionGraphParameters.setStatusTip(QCoreApplication.translate("MainWindow", u"Edit parameters throughout the active graph", None))
#endif // QT_CONFIG(statustip)
- self.actionCompile.setText(QCoreApplication.translate("MainWindow", u"Compile", None))
+ self.actionCompose.setText(QCoreApplication.translate("MainWindow", u"Compose", None))
#if QT_CONFIG(statustip)
- self.actionCompile.setStatusTip(QCoreApplication.translate("MainWindow", u"Compile the active graph for simulation", None))
+ self.actionCompose.setStatusTip(QCoreApplication.translate("MainWindow", u"Compose the active graph as an OpenModelica model", None))
#endif // QT_CONFIG(statustip)
#if QT_CONFIG(shortcut)
- self.actionCompile.setShortcut(QCoreApplication.translate("MainWindow", u"F5", None))
+ self.actionCompose.setShortcut(QCoreApplication.translate("MainWindow", u"F5", None))
#endif // QT_CONFIG(shortcut)
self.actionRunSimulation.setText(QCoreApplication.translate("MainWindow", u"Run", None))
#if QT_CONFIG(statustip)
diff --git a/BEdit/src/bedit/gui/main_window.py b/BEdit/src/bedit/gui/main_window.py
index b3cd7c2..f103351 100644
--- a/BEdit/src/bedit/gui/main_window.py
+++ b/BEdit/src/bedit/gui/main_window.py
@@ -166,7 +166,7 @@ class MainWindow(QMainWindow):
self.show_simulation_settings
)
self.ui.actionGraphParameters.triggered.connect(self.show_graph_parameters)
- self.ui.actionCompile.triggered.connect(self.compile_active_graph)
+ self.ui.actionCompose.triggered.connect(self.compose_active_graph)
self.ui.actionRunSimulation.triggered.connect(self.run_simulation)
self.document_controller.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled)
self.document_controller.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled)
@@ -239,12 +239,12 @@ class MainWindow(QMainWindow):
QMessageBox.warning(self, "Cannot change graph parameters", str(error))
@Slot()
- def compile_active_graph(self) -> None:
+ def compose_active_graph(self) -> None:
try:
- self.document_controller.compile_active_graph()
+ self.document_controller.compose_active_graph()
except ValueError as error:
- self.log.error("Compile failed: %s", error)
- QMessageBox.warning(self, "Cannot compile", str(error))
+ self.log.error("Composition failed: %s", error)
+ QMessageBox.warning(self, "Cannot compose", str(error))
@Slot()
def run_simulation(self) -> None:
@@ -281,7 +281,7 @@ class MainWindow(QMainWindow):
self._set_graph_controls_visible(False)
self.ui.actionSimulationSettings.setEnabled(False)
self.ui.actionGraphParameters.setEnabled(False)
- self.ui.actionCompile.setEnabled(False)
+ self.ui.actionCompose.setEnabled(False)
self.ui.actionRunSimulation.setEnabled(False)
self._update_edit_actions()
return
@@ -292,7 +292,7 @@ class MainWindow(QMainWindow):
is_graph = component.implementation_kind == "graph"
self.ui.actionSimulationSettings.setEnabled(is_graph)
self.ui.actionGraphParameters.setEnabled(is_graph)
- self.ui.actionCompile.setEnabled(is_graph)
+ self.ui.actionCompose.setEnabled(is_graph)
self.ui.actionRunSimulation.setEnabled(is_graph)
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
self.ui.workspaceStack.setCurrentWidget(
@@ -760,6 +760,7 @@ class MainWindow(QMainWindow):
event.ignore()
return
self.settings.setValue("window/geometry", self.saveGeometry())
+ self.simulation.shutdown()
self.log.info("BEdit closed")
self.application_logger.removeHandler(self.log_handler)
event.accept()
diff --git a/BEdit/src/bedit/gui/simulation_reload.py b/BEdit/src/bedit/gui/simulation_reload.py
index 7d740ed..c898485 100644
--- a/BEdit/src/bedit/gui/simulation_reload.py
+++ b/BEdit/src/bedit/gui/simulation_reload.py
@@ -20,6 +20,8 @@ def _saved_instance_state(instance) -> dict:
def reload_simulation(current):
"""Reload the simulation package and return a fresh state-preserving instance."""
saved_state = _saved_instance_state(current)
+ saved_state.pop("openmodelica", None)
+ current.shutdown(wait=True)
importlib.invalidate_caches()
package = importlib.import_module(SIMULATION_PACKAGE)
discovered: list[ModuleType] = []
@@ -30,4 +32,5 @@ def reload_simulation(current):
package = importlib.reload(package)
replacement = package.Simulation()
vars(replacement).update(saved_state)
+ replacement.openmodelica.configure(replacement.openmodelica_path)
return replacement
diff --git a/BEdit/ui/main_window.ui b/BEdit/ui/main_window.ui
index 55ea9b1..7195e60 100644
--- a/BEdit/ui/main_window.ui
+++ b/BEdit/ui/main_window.ui
@@ -551,7 +551,7 @@
-
+
@@ -644,7 +644,7 @@
-
+
@@ -671,16 +671,16 @@
Edit parameters throughout the active graph
-
+
:/icons/icons/run-build.png:/icons/icons/run-build.png
- Compile
+ Compose
- Compile the active graph for simulation
+ Compose the active graph as an OpenModelica model
F5
diff --git a/BEdit/untitled.bedit.json b/BEdit/untitled.bedit.json
index 0dd03af..28b07ba 100644
--- a/BEdit/untitled.bedit.json
+++ b/BEdit/untitled.bedit.json
@@ -653,7 +653,7 @@
"startTime": 0.0,
"stopTime": 10.0,
"intervalMode": "numberOfIntervals",
- "numberOfIntervals": 5000,
+ "numberOfIntervals": 50,
"intervalTime": 0.002
}
}