Compare commits
4 Commits
b3aabae5e8
...
a38d461a36
| Author | SHA1 | Date | |
|---|---|---|---|
| a38d461a36 | |||
| 22e9a0386c | |||
| 89ac2d8ff8 | |||
| 66737e323b |
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -5,7 +5,7 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Python Debugger: Module",
|
"name": "BEdit debug",
|
||||||
"type": "debugpy",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "bedit_gui",
|
"module": "bedit_gui",
|
||||||
@@ -18,6 +18,22 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"-f", "${workspaceFolder}/untitled.bedit.json"
|
"-f", "${workspaceFolder}/untitled.bedit.json"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BEsim debug",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "bedit_gui.simulation_application",
|
||||||
|
"preLaunchTask": "Qt: Generate files",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"env": {
|
||||||
|
"QT_QPA_PLATFORMTHEME": "qt6ct",
|
||||||
|
"QT_QPA_PLATFORM": "xcb"
|
||||||
|
},
|
||||||
|
"args": [
|
||||||
|
"-s", "run_bondgraph",
|
||||||
|
"${workspaceFolder}/untitled.besim.json"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
12
AGENTS.md
12
AGENTS.md
@@ -161,6 +161,18 @@ Text widgets use their native `copy()`, `cut()`, and `paste()` methods. Componen
|
|||||||
- Scope destructive shortcuts to the relevant widget where appropriate.
|
- Scope destructive shortcuts to the relevant widget where appropriate.
|
||||||
- Always guard the operation itself even when an action is disabled for presentation.
|
- Always guard the operation itself even when an action is disabled for presentation.
|
||||||
|
|
||||||
|
## Simulator application
|
||||||
|
|
||||||
|
- `bedit_gui/simulation_application.py` is the composition root for the separate `bedit-sim` Qt application.
|
||||||
|
- Keep compilation GUI-independent. Shared compile entry points belong in `bedit_simulation` and must not create or depend on a `QApplication` or window.
|
||||||
|
- The simulator opens BEdit `.beb`/`.json` documents or serialized simulation `.bes`/`.json` files.
|
||||||
|
- A component selector uses default simulation settings. A stored simulation-settings block already identifies its component; do not require both selectors.
|
||||||
|
- `SimulationDatabase.active_simulation` is the persisted active settings ID selected when the Simulation Settings dialog is accepted.
|
||||||
|
- Simulation files serialize `bedit_gui.simulation_models.SimulationRoot`. JSON uses the `root_type = "simulation_root"` discriminator; binary `.bes` files use their own BES header.
|
||||||
|
- `SimulationRoot` contains source-document identity, the selected component and copied settings, and eventually results. Compiled artifacts are transient application state and must not be serialized; reopen saved simulations by compiling them again. Never serialize live Python objects.
|
||||||
|
- BEdit launches the simulator as a separate process. Compile/Open Simulation integration may transfer a `.bes` file to that process.
|
||||||
|
- Keep simulator file workflows in their own services/controllers rather than adding them to `MainWindow` or the editor document controller.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
The old test suite and its VS Code/packaging references were deliberately removed. Do not recreate a test suite unless asked.
|
The old test suite and its VS Code/packaging references were deliberately removed. Do not recreate a test suite unless asked.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ dependencies = [
|
|||||||
bedit-graphviz = "bedit_util.graphviz:main"
|
bedit-graphviz = "bedit_util.graphviz:main"
|
||||||
bedit-simulate = "bedit_util.simulate:main"
|
bedit-simulate = "bedit_util.simulate:main"
|
||||||
bedit = "bedit_gui.application:main"
|
bedit = "bedit_gui.application:main"
|
||||||
|
besim = "bedit_gui.simulation_application:main"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from bedit_gui.controllers.document_controller import DocumentController
|
|||||||
from bedit_gui.controllers.log_controller import LogController
|
from bedit_gui.controllers.log_controller import LogController
|
||||||
from bedit_gui.controllers.settings_controller import SettingsController
|
from bedit_gui.controllers.settings_controller import SettingsController
|
||||||
from bedit_gui.controllers.simulation_settings_controller import SimulationSettingsController
|
from bedit_gui.controllers.simulation_settings_controller import SimulationSettingsController
|
||||||
|
from bedit_gui.controllers.simulation_controller import SimulationController
|
||||||
from bedit_gui.controllers.undo_controller import UndoController
|
from bedit_gui.controllers.undo_controller import UndoController
|
||||||
from bedit_gui.controllers.view_menu_controller import ViewMenuController
|
from bedit_gui.controllers.view_menu_controller import ViewMenuController
|
||||||
from bedit_gui.controllers.window_state_controller import WindowStateController
|
from bedit_gui.controllers.window_state_controller import WindowStateController
|
||||||
@@ -48,6 +49,7 @@ def main() -> int:
|
|||||||
DocumentController(document, window)
|
DocumentController(document, window)
|
||||||
SettingsController(window, settings)
|
SettingsController(window, settings)
|
||||||
SimulationSettingsController(document, window)
|
SimulationSettingsController(document, window)
|
||||||
|
SimulationController(document, window)
|
||||||
UndoController(document, window)
|
UndoController(document, window)
|
||||||
ViewMenuController(window)
|
ViewMenuController(window)
|
||||||
document_tree_controller = DocumentTreeController(document, window)
|
document_tree_controller = DocumentTreeController(document, window)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from PySide6.QtCore import QObject, Signal
|
|||||||
|
|
||||||
from bedit_gui.services.application_logging import configure_logging
|
from bedit_gui.services.application_logging import configure_logging
|
||||||
from bedit_gui.views.main_window import MainWindow
|
from bedit_gui.views.main_window import MainWindow
|
||||||
|
from bedit_gui.views.simulation_window import SimulationWindow
|
||||||
from bedit_gui.views.models import LogListModel
|
from bedit_gui.views.models import LogListModel
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ class LogController(QObject):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
window: MainWindow,
|
window: MainWindow | SimulationWindow,
|
||||||
level: int | str = logging.INFO,
|
level: int | str = logging.INFO,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(window)
|
super().__init__(window)
|
||||||
|
|||||||
99
src/bedit_gui/controllers/simulation_controller.py
Normal file
99
src/bedit_gui/controllers/simulation_controller.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
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.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:
|
||||||
|
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:
|
||||||
|
build = self.compiler(component, build_directory)
|
||||||
|
except (OSError, RuntimeError, ValueError) as exc:
|
||||||
|
logger.exception("Could not compile model %s", component_path)
|
||||||
|
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))
|
||||||
163
src/bedit_gui/controllers/simulation_file_controller.py
Normal file
163
src/bedit_gui/controllers/simulation_file_controller.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PySide6.QtCore import QObject, Qt
|
||||||
|
from PySide6.QtWidgets import QApplication, QDialog, 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.models import SimulationDatabase, SimulationID
|
||||||
|
from bedit_gui.services.application_logging import get_logger
|
||||||
|
from bedit_gui.simulation_models import CompiledModel, SimulationRoot
|
||||||
|
from bedit_gui.views.dialogs.simulation_settings_dialog import SimulationSettingsDialog
|
||||||
|
from bedit_gui.views.simulation_window import SimulationWindow
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
window.ui.actionSimulation_Options.triggered.connect(self.open_simulation_settings)
|
||||||
|
self._update_window()
|
||||||
|
|
||||||
|
def new(self) -> None:
|
||||||
|
self.root = None
|
||||||
|
self.compiled_model = None
|
||||||
|
self.path = None
|
||||||
|
self._update_window()
|
||||||
|
logger.info("Created new simulation")
|
||||||
|
|
||||||
|
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)
|
||||||
|
try:
|
||||||
|
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
|
||||||
|
except (OSError, RuntimeError, TypeError, ValueError):
|
||||||
|
logger.exception("Could not open simulation %s", file_path)
|
||||||
|
raise
|
||||||
|
self._log_compilation()
|
||||||
|
self._update_window()
|
||||||
|
logger.info("Opened simulation: %s", file_path)
|
||||||
|
|
||||||
|
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, TypeError, ValueError) as exc:
|
||||||
|
logger.exception("Could not save simulation %s", path)
|
||||||
|
QMessageBox.critical(self.window, "Could not save simulation", str(exc))
|
||||||
|
return
|
||||||
|
self.path = path
|
||||||
|
self._update_window()
|
||||||
|
logger.info("Saved simulation: %s", path)
|
||||||
|
|
||||||
|
def open_simulation_settings(self) -> None:
|
||||||
|
if self.root is None:
|
||||||
|
return
|
||||||
|
simulation_id = SimulationID()
|
||||||
|
database = SimulationDatabase(simulations={simulation_id: self.root.settings}, active_simulation=simulation_id)
|
||||||
|
components = self._source_components()
|
||||||
|
dialog = SimulationSettingsDialog(database, [(component_id, path) for component_id, _component, path in components], self.window, show_simulation_list=False)
|
||||||
|
if dialog.exec() != QDialog.DialogCode.Accepted:
|
||||||
|
return
|
||||||
|
updated = dialog.database().simulations[simulation_id]
|
||||||
|
component_changed = updated.component != self.root.component
|
||||||
|
self.root.settings = updated
|
||||||
|
self.root.component = updated.component
|
||||||
|
self.root.component_path = next((path for component_id, _component, path in components if component_id == updated.component), self.root.component_path)
|
||||||
|
self.root.settings_name = updated.name
|
||||||
|
if component_changed:
|
||||||
|
self.compiled_model = None
|
||||||
|
self._update_window()
|
||||||
|
logger.info("Updated simulation settings: %s", updated.name)
|
||||||
|
|
||||||
|
def _source_components(self) -> list[tuple]:
|
||||||
|
if self.root is not None and self.root.source_document is not None:
|
||||||
|
try:
|
||||||
|
return component_choices(document_files.load(self.root.source_document))
|
||||||
|
except (OSError, TypeError, ValueError):
|
||||||
|
logger.warning("Could not load source components from %s", self.root.source_document, exc_info=True)
|
||||||
|
if self.root is None:
|
||||||
|
return []
|
||||||
|
return [(self.root.component, None, self.root.component_path)]
|
||||||
|
|
||||||
|
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)
|
||||||
|
self.window.ui.actionSimulation_Options.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)
|
||||||
|
self.window.ui.actionSimulation_Options.setEnabled(True)
|
||||||
|
|
||||||
|
def _log_compilation(self) -> None:
|
||||||
|
if self.compiled_model is None:
|
||||||
|
return
|
||||||
|
logger.info("Compiled model %s: %s", self.compiled_model.model_name, self.compiled_model.executable)
|
||||||
|
if self.compiled_model.output.strip():
|
||||||
|
logger.info("Compiler output:\n%s", self.compiled_model.output.strip())
|
||||||
|
if self.compiled_model.errors.strip():
|
||||||
|
logger.warning("Compiler errors:\n%s", self.compiled_model.errors.strip())
|
||||||
@@ -16,7 +16,7 @@ from bedit_gui.commands.rename_component_command import RenameComponentCommand
|
|||||||
from bedit_gui.commands.rename_document_command import RenameDocumentCommand
|
from bedit_gui.commands.rename_document_command import RenameDocumentCommand
|
||||||
from bedit_gui.commands.simulation_database_command import ChangeSimulationDatabaseCommand
|
from bedit_gui.commands.simulation_database_command import ChangeSimulationDatabaseCommand
|
||||||
from bedit_gui.commands.component_command import AddEmptyEquationComponent, AddEmptyGraphComponent, DeleteComponent, PasteComponents
|
from bedit_gui.commands.component_command import AddEmptyEquationComponent, AddEmptyGraphComponent, DeleteComponent, PasteComponents
|
||||||
from bedit_gui.models import Icon, IconDatabase, SimulationDatabase
|
from bedit_gui.models import Icon, IconDatabase, Simulation, SimulationDatabase
|
||||||
from bedit_gui.services import document_files
|
from bedit_gui.services import document_files
|
||||||
|
|
||||||
|
|
||||||
@@ -131,6 +131,10 @@ class Document(QObject):
|
|||||||
def simulation_database(self) -> SimulationDatabase:
|
def simulation_database(self) -> SimulationDatabase:
|
||||||
return self.stored_simulation_database() or SimulationDatabase()
|
return self.stored_simulation_database() or SimulationDatabase()
|
||||||
|
|
||||||
|
def active_simulation(self) -> Simulation | None:
|
||||||
|
database = self.simulation_database()
|
||||||
|
return database.simulations.get(database.active_simulation) if database.active_simulation is not None else None
|
||||||
|
|
||||||
def change_simulation_database(self, database: SimulationDatabase) -> None:
|
def change_simulation_database(self, database: SimulationDatabase) -> None:
|
||||||
if database != self.stored_simulation_database():
|
if database != self.stored_simulation_database():
|
||||||
self.undo_stack.push(ChangeSimulationDatabaseCommand(self, database))
|
self.undo_stack.push(ChangeSimulationDatabaseCommand(self, database))
|
||||||
|
|||||||
@@ -176,11 +176,14 @@ class Simulation:
|
|||||||
class SimulationDatabase:
|
class SimulationDatabase:
|
||||||
format_version: int = 1
|
format_version: int = 1
|
||||||
simulations: dict[SimulationID, Simulation] = field(default_factory=dict)
|
simulations: dict[SimulationID, Simulation] = field(default_factory=dict)
|
||||||
|
active_simulation: SimulationID | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_data(cls, data: Mapping[str, Any]) -> SimulationDatabase:
|
def from_data(cls, data: Mapping[str, Any]) -> SimulationDatabase:
|
||||||
sims = {SimulationID(key): Simulation.from_data(value) for key, value in data.get("simulations", {}).items()}
|
sims = {SimulationID(key): Simulation.from_data(value) for key, value in data.get("simulations", {}).items()}
|
||||||
return cls(format_version=int(data.get("format_version", 1)), simulations=sims)
|
active = data.get("active_simulation")
|
||||||
|
active_id = SimulationID(str(active)) if active is not None else None
|
||||||
|
return cls(format_version=int(data.get("format_version", 1)), simulations=sims, active_simulation=active_id if active_id in sims else None)
|
||||||
|
|
||||||
def to_data(self) -> dict[str, Any]:
|
def to_data(self) -> dict[str, Any]:
|
||||||
return {"format_version": self.format_version, "simulations": {str(key): sim.to_data() for key, sim in self.simulations.items()}}
|
return {"format_version": self.format_version, "active_simulation": str(self.active_simulation) if self.active_simulation is not None else None, "simulations": {str(key): sim.to_data() for key, sim in self.simulations.items()}}
|
||||||
|
|||||||
BIN
src/bedit_gui/resources/icons/media-playback-stop.png
Normal file
BIN
src/bedit_gui/resources/icons/media-playback-stop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/bedit_gui/resources/icons/media-skip-backward.png
Normal file
BIN
src/bedit_gui/resources/icons/media-skip-backward.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,5 +1,7 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="icons">
|
<qresource prefix="icons">
|
||||||
|
<file>icons/media-skip-backward.png</file>
|
||||||
|
<file>icons/media-playback-stop.png</file>
|
||||||
<file>icons/edit-delete.png</file>
|
<file>icons/edit-delete.png</file>
|
||||||
<file>icons/dialog-close.png</file>
|
<file>icons/dialog-close.png</file>
|
||||||
<file>icons/list-remove.png</file>
|
<file>icons/list-remove.png</file>
|
||||||
|
|||||||
@@ -25,3 +25,24 @@ class ApplicationSettings:
|
|||||||
@log_level.setter
|
@log_level.setter
|
||||||
def log_level(self, level: int) -> None:
|
def log_level(self, level: int) -> None:
|
||||||
self._settings.setValue(self.LOG_LEVEL_KEY, level)
|
self._settings.setValue(self.LOG_LEVEL_KEY, level)
|
||||||
|
|
||||||
|
class SimulationApplicationSettings:
|
||||||
|
"""Typed access to persistent BEsim application settings."""
|
||||||
|
|
||||||
|
LOG_LEVEL_KEY = "logging/level"
|
||||||
|
DEFAULT_LOG_LEVEL = logging.INFO
|
||||||
|
|
||||||
|
def __init__(self, settings: QSettings | None = None) -> None:
|
||||||
|
self._settings = settings if settings is not None else QSettings()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log_level(self) -> int:
|
||||||
|
return self._settings.value(
|
||||||
|
self.LOG_LEVEL_KEY,
|
||||||
|
self.DEFAULT_LOG_LEVEL,
|
||||||
|
type=int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@log_level.setter
|
||||||
|
def log_level(self, level: int) -> None:
|
||||||
|
self._settings.setValue(self.LOG_LEVEL_KEY, level)
|
||||||
|
|||||||
79
src/bedit_gui/services/simulation_files.py
Normal file
79
src/bedit_gui/services/simulation_files.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import zlib
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import msgpack
|
||||||
|
|
||||||
|
from bedit_gui.simulation_models import SimulationRoot
|
||||||
|
|
||||||
|
BES_MAGIC = b"BES\x00"
|
||||||
|
FILE_FORMAT_VERSION = 1
|
||||||
|
_VERSION_SIZE = 4
|
||||||
|
|
||||||
|
|
||||||
|
def load(path: str | Path) -> SimulationRoot:
|
||||||
|
file_path = Path(path)
|
||||||
|
data = _load_json(file_path) if file_path.suffix.lower() == ".json" else _load_bes(file_path)
|
||||||
|
return SimulationRoot.from_data(data)
|
||||||
|
|
||||||
|
|
||||||
|
def save(root: SimulationRoot, path: str | Path) -> None:
|
||||||
|
file_path = Path(path)
|
||||||
|
if file_path.suffix.lower() == ".json":
|
||||||
|
_save_json(root.to_data(), file_path)
|
||||||
|
elif file_path.suffix.lower() == ".bes":
|
||||||
|
_save_bes(root.to_data(), file_path)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"unsupported simulation file extension {file_path.suffix!r}; expected '.json' or '.bes'")
|
||||||
|
|
||||||
|
|
||||||
|
def is_simulation_json(path: str | Path) -> bool:
|
||||||
|
file_path = Path(path)
|
||||||
|
if file_path.suffix.lower() != ".json":
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return _load_json(file_path).get("root_type") == "simulation_root"
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _load_json(path: Path) -> Mapping[str, Any]:
|
||||||
|
try:
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
||||||
|
raise ValueError(f"could not read simulation JSON {path}: {exc}") from exc
|
||||||
|
if not isinstance(data, Mapping):
|
||||||
|
raise TypeError(f"simulation JSON {path} must contain an object")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _save_json(data: Mapping[str, Any], path: Path) -> None:
|
||||||
|
path.write_text(json.dumps({"file_format_version": FILE_FORMAT_VERSION, **data}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_bes(path: Path) -> Mapping[str, Any]:
|
||||||
|
try:
|
||||||
|
payload = path.read_bytes()
|
||||||
|
header_end = len(BES_MAGIC) + _VERSION_SIZE
|
||||||
|
if not payload.startswith(BES_MAGIC):
|
||||||
|
raise ValueError("missing BES file header")
|
||||||
|
if len(payload) < header_end:
|
||||||
|
raise ValueError("truncated BES file header")
|
||||||
|
version = int.from_bytes(payload[len(BES_MAGIC):header_end], "big")
|
||||||
|
if version != FILE_FORMAT_VERSION:
|
||||||
|
raise ValueError(f"unsupported BES file version {version}")
|
||||||
|
data = msgpack.unpackb(zlib.decompress(payload[header_end:]), raw=False, strict_map_key=False)
|
||||||
|
except (OSError, ValueError, zlib.error, msgpack.exceptions.UnpackException) as exc:
|
||||||
|
raise ValueError(f"could not read BES simulation {path}: {exc}") from exc
|
||||||
|
if not isinstance(data, Mapping):
|
||||||
|
raise TypeError(f"BES simulation {path} must contain a map")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _save_bes(data: Mapping[str, Any], path: Path) -> None:
|
||||||
|
encoded = zlib.compress(msgpack.packb(dict(data), use_bin_type=True))
|
||||||
|
path.write_bytes(BES_MAGIC + FILE_FORMAT_VERSION.to_bytes(_VERSION_SIZE, "big") + encoded)
|
||||||
95
src/bedit_gui/services/simulation_loader.py
Normal file
95
src/bedit_gui/services/simulation_loader.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from bedit_core.models import Component, ComponentID, Document, GraphImplementation
|
||||||
|
from bedit_gui.models import Simulation, SimulationMethod
|
||||||
|
from bedit_gui.services import document_files
|
||||||
|
from bedit_gui.simulation_models import CompiledModel, SimulationRoot
|
||||||
|
from bedit_simulation import compile_component_sync
|
||||||
|
|
||||||
|
|
||||||
|
def component_choices(document: Document) -> list[tuple[ComponentID, Component, str]]:
|
||||||
|
choices: list[tuple[ComponentID, Component, str]] = []
|
||||||
|
|
||||||
|
def collect(items: dict[ComponentID, Component], path: tuple[str, ...] = ()) -> None:
|
||||||
|
for component_id, component in items.items():
|
||||||
|
component_path = (*path, component.name)
|
||||||
|
choices.append((component_id, component, ".".join(component_path)))
|
||||||
|
if isinstance(component.implementation, GraphImplementation):
|
||||||
|
collect(component.implementation.graph.components, component_path)
|
||||||
|
|
||||||
|
collect(document.root)
|
||||||
|
return choices
|
||||||
|
|
||||||
|
|
||||||
|
def load_and_compile_bedit(path: str | Path, *, component_selector: str | None = None, simulation_selector: str | None = None, working_directory: str | Path | None = None, omc_command: str = "omc") -> tuple[SimulationRoot, CompiledModel]:
|
||||||
|
"""Open a BEdit document, resolve one launch target, and compile it."""
|
||||||
|
if bool(component_selector) == bool(simulation_selector):
|
||||||
|
raise ValueError("specify exactly one component or simulation settings block")
|
||||||
|
source_path = Path(path).resolve()
|
||||||
|
document = document_files.load(source_path)
|
||||||
|
choices = component_choices(document)
|
||||||
|
settings_name: str | None = None
|
||||||
|
|
||||||
|
if component_selector is not None:
|
||||||
|
component_id, component, component_path = _find_component(choices, component_selector)
|
||||||
|
settings = _default_settings(component_id)
|
||||||
|
else:
|
||||||
|
database = document.metadata.get("simulation_database") if document.metadata is not None else None
|
||||||
|
if database is None or not hasattr(database, "simulations"):
|
||||||
|
raise ValueError("the BEdit document does not contain simulation settings")
|
||||||
|
matches = [(simulation_id, simulation) for simulation_id, simulation in database.simulations.items() if simulation.name == simulation_selector or str(simulation_id) == simulation_selector]
|
||||||
|
if len(matches) != 1:
|
||||||
|
raise ValueError(f"simulation settings {simulation_selector!r} were not found or are ambiguous")
|
||||||
|
_simulation_id, settings = matches[0]
|
||||||
|
component_id, component, component_path = _find_component(choices, str(settings.component))
|
||||||
|
settings_name = settings.name
|
||||||
|
|
||||||
|
build_directory = Path(working_directory) if working_directory is not None else Path(tempfile.mkdtemp(prefix="bedit-compiled-"))
|
||||||
|
root = SimulationRoot(
|
||||||
|
format_version=1,
|
||||||
|
source_document=str(source_path),
|
||||||
|
source_document_id=str(document.id),
|
||||||
|
component=component_id,
|
||||||
|
component_path=component_path,
|
||||||
|
settings_name=settings_name,
|
||||||
|
settings=settings,
|
||||||
|
)
|
||||||
|
return root, _compile(component, build_directory, omc_command)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_simulation_root(root: SimulationRoot, *, working_directory: str | Path | None = None, omc_command: str = "omc") -> CompiledModel:
|
||||||
|
if root.source_document is None:
|
||||||
|
raise ValueError("the simulation does not reference a BEdit source document and cannot be recompiled")
|
||||||
|
document = document_files.load(root.source_document)
|
||||||
|
_component_id, component, _component_path = _find_component(component_choices(document), str(root.component))
|
||||||
|
build_directory = Path(working_directory) if working_directory is not None else Path(tempfile.mkdtemp(prefix="bedit-compiled-"))
|
||||||
|
return _compile(component, build_directory, omc_command)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_component(choices: list[tuple[ComponentID, Component, str]], selector: str) -> tuple[ComponentID, Component, str]:
|
||||||
|
matches = [choice for choice in choices if str(choice[0]) == selector or choice[2] == selector]
|
||||||
|
if len(matches) != 1:
|
||||||
|
raise ValueError(f"component {selector!r} was not found or is ambiguous")
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _default_settings(component_id: ComponentID) -> Simulation:
|
||||||
|
return Simulation(
|
||||||
|
component=component_id,
|
||||||
|
name="Default",
|
||||||
|
start_time=0.0,
|
||||||
|
duration=1.0,
|
||||||
|
use_timed_steps=False,
|
||||||
|
number_of_steps=500,
|
||||||
|
step_size=0.002,
|
||||||
|
method=SimulationMethod.DASSL,
|
||||||
|
dassl_tolerance=1e-6,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _compile(component: Component, working_directory: Path, omc_command: str) -> CompiledModel:
|
||||||
|
build = compile_component_sync(component, working_directory, omc_command=omc_command)
|
||||||
|
return CompiledModel(build.model_name, str(build.executable.resolve()), str(build.executable.parent.resolve()), build.output, build.errors)
|
||||||
58
src/bedit_gui/simulation_application.py
Normal file
58
src/bedit_gui/simulation_application.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||||
|
|
||||||
|
from bedit_gui.controllers.simulation_file_controller import SimulationFileController
|
||||||
|
from bedit_gui.controllers.log_controller import LogController
|
||||||
|
from bedit_gui.simulation_models import CompiledModel
|
||||||
|
from bedit_gui.services.application_settings import SimulationApplicationSettings
|
||||||
|
from bedit_gui.views.simulation_window import SimulationWindow
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments(arguments: list[str] | None = None) -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Open and run BEdit simulations")
|
||||||
|
parser.add_argument("file", nargs="?", help="BEdit (.beb/.json) or simulation (.bes/.json) file")
|
||||||
|
parser.add_argument("-f", "--file", dest="file_option", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("--handoff", action="store_true", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("--model-name", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("--executable", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("--working-directory", help=argparse.SUPPRESS)
|
||||||
|
target = parser.add_mutually_exclusive_group()
|
||||||
|
target.add_argument("-c", "--component", help="Component ID or dotted path in a BEdit file")
|
||||||
|
target.add_argument("-s", "--simulation", help="Simulation settings name or ID in a BEdit file")
|
||||||
|
args = parser.parse_args(arguments)
|
||||||
|
args.file = args.file_option or args.file
|
||||||
|
if args.handoff and not all((args.model_name, args.executable, args.working_directory)):
|
||||||
|
parser.error("a simulator handoff requires compiled-model arguments")
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def main(arguments: list[str] | None = None) -> int:
|
||||||
|
args = parse_arguments(arguments)
|
||||||
|
app = QApplication(sys.argv if arguments is None else [sys.argv[0], *arguments])
|
||||||
|
|
||||||
|
app.setOrganizationName("BEsim")
|
||||||
|
app.setApplicationName("BEsim")
|
||||||
|
|
||||||
|
window = SimulationWindow()
|
||||||
|
controller = SimulationFileController(window)
|
||||||
|
|
||||||
|
settings = SimulationApplicationSettings()
|
||||||
|
LogController(window, settings.log_level)
|
||||||
|
|
||||||
|
window.showMaximized()
|
||||||
|
|
||||||
|
if args.file:
|
||||||
|
try:
|
||||||
|
compiled_model = CompiledModel(args.model_name, args.executable, args.working_directory) if args.handoff else None
|
||||||
|
controller.open(args.file, component=args.component, simulation=args.simulation, backed_by_file=not args.handoff, compiled_model=compiled_model)
|
||||||
|
except (OSError, ValueError, RuntimeError) as exc:
|
||||||
|
QMessageBox.critical(window, "Could not open simulation", str(exc))
|
||||||
|
return app.exec()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
61
src/bedit_gui/simulation_models.py
Normal file
61
src/bedit_gui/simulation_models.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from bedit_core.models import ComponentID
|
||||||
|
from bedit_gui.models import Simulation
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CompiledModel:
|
||||||
|
model_name: str
|
||||||
|
executable: str
|
||||||
|
working_directory: str
|
||||||
|
output: str = ""
|
||||||
|
errors: str = ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Mapping[str, Any]) -> CompiledModel:
|
||||||
|
return cls(model_name=str(data["model_name"]), executable=str(data["executable"]), working_directory=str(data["working_directory"]), output=str(data.get("output", "")), errors=str(data.get("errors", "")))
|
||||||
|
|
||||||
|
def to_data(self) -> dict[str, Any]:
|
||||||
|
return {"model_name": self.model_name, "executable": self.executable, "working_directory": self.working_directory, "output": self.output, "errors": self.errors}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SimulationRoot:
|
||||||
|
format_version: int
|
||||||
|
source_document: str | None
|
||||||
|
source_document_id: str | None
|
||||||
|
component: ComponentID
|
||||||
|
component_path: str
|
||||||
|
settings_name: str | None
|
||||||
|
settings: Simulation
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Mapping[str, Any]) -> SimulationRoot:
|
||||||
|
if data.get("root_type") != "simulation_root":
|
||||||
|
raise ValueError("file does not contain a simulation root")
|
||||||
|
return cls(
|
||||||
|
format_version=int(data.get("format_version", 1)),
|
||||||
|
source_document=str(data["source_document"]) if data.get("source_document") is not None else None,
|
||||||
|
source_document_id=str(data["source_document_id"]) if data.get("source_document_id") is not None else None,
|
||||||
|
component=ComponentID(str(data["component"])),
|
||||||
|
component_path=str(data.get("component_path", "")),
|
||||||
|
settings_name=str(data["settings_name"]) if data.get("settings_name") is not None else None,
|
||||||
|
settings=Simulation.from_data(data["settings"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_data(self) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"root_type": "simulation_root",
|
||||||
|
"format_version": self.format_version,
|
||||||
|
"source_document": self.source_document,
|
||||||
|
"source_document_id": self.source_document_id,
|
||||||
|
"component": str(self.component),
|
||||||
|
"component_path": self.component_path,
|
||||||
|
"settings_name": self.settings_name,
|
||||||
|
"settings": self.settings.to_data(),
|
||||||
|
}
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Help</string>
|
<string>Help</string>
|
||||||
</property>
|
</property>
|
||||||
|
<addaction name="actionAbout"/>
|
||||||
<addaction name="actionAbout_QT"/>
|
<addaction name="actionAbout_QT"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuSimulation">
|
<widget class="QMenu" name="menuSimulation">
|
||||||
@@ -460,6 +461,11 @@
|
|||||||
<enum>QAction::MenuRole::NoRole</enum>
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionAbout">
|
||||||
|
<property name="text">
|
||||||
|
<string>About</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../resources/resources.qrc"/>
|
<include location="../../resources/resources.qrc"/>
|
||||||
|
|||||||
309
src/bedit_gui/ui/forms/simulation_window.ui
Normal file
309
src/bedit_gui/ui/forms/simulation_window.ui
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>SimulationWindow</class>
|
||||||
|
<widget class="QMainWindow" name="SimulationWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>600</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>MainWindow</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QTabWidget" name="resultsTabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="tab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Tab 1</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>22</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<widget class="QMenu" name="menuFile">
|
||||||
|
<property name="title">
|
||||||
|
<string>File</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionNew_Simulation_Run"/>
|
||||||
|
<addaction name="actionOpen_Simulation_Run"/>
|
||||||
|
<addaction name="actionSave_Simulation_Run"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuSimulation">
|
||||||
|
<property name="title">
|
||||||
|
<string>Simulation</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionRestart_Simulation"/>
|
||||||
|
<addaction name="actionRun_Simulation"/>
|
||||||
|
<addaction name="actionStop_Simulation"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionSimulation_Options"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuEdit">
|
||||||
|
<property name="title">
|
||||||
|
<string>Edit</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionUndo"/>
|
||||||
|
<addaction name="actionRedo"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionSettings"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuTools">
|
||||||
|
<property name="title">
|
||||||
|
<string>Tools</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuHelp">
|
||||||
|
<property name="title">
|
||||||
|
<string>Help</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionAbout"/>
|
||||||
|
<addaction name="actionAbout_QT"/>
|
||||||
|
</widget>
|
||||||
|
<addaction name="menuFile"/>
|
||||||
|
<addaction name="menuEdit"/>
|
||||||
|
<addaction name="menuSimulation"/>
|
||||||
|
<addaction name="menuTools"/>
|
||||||
|
<addaction name="menuHelp"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
<widget class="QToolBar" name="fileToolbar">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>toolBar</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="toolBarArea">
|
||||||
|
<enum>TopToolBarArea</enum>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="toolBarBreak">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
<addaction name="actionNew_Simulation_Run"/>
|
||||||
|
<addaction name="actionOpen_Simulation_Run"/>
|
||||||
|
<addaction name="actionSave_Simulation_Run"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QToolBar" name="actionToolbar">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>toolBar</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="toolBarArea">
|
||||||
|
<enum>TopToolBarArea</enum>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="toolBarBreak">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
<addaction name="actionUndo"/>
|
||||||
|
<addaction name="actionRedo"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QToolBar" name="simToolbar">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>toolBar</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="toolBarArea">
|
||||||
|
<enum>TopToolBarArea</enum>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="toolBarBreak">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
<addaction name="actionRestart_Simulation"/>
|
||||||
|
<addaction name="actionRun_Simulation"/>
|
||||||
|
<addaction name="actionStop_Simulation"/>
|
||||||
|
<addaction name="actionSimulation_Options"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QDockWidget" name="logDockWidget">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Log</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="dockWidgetArea">
|
||||||
|
<number>8</number>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="dockWidgetContents">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QListView" name="listView"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QDockWidget" name="simulationTreeDock">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Signals</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="dockWidgetArea">
|
||||||
|
<number>1</number>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="dockWidgetContents_2">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="simulationTree">
|
||||||
|
<attribute name="headerVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">1</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<action name="actionRun_Simulation">
|
||||||
|
<property name="icon">
|
||||||
|
<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 Simulation</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionStop_Simulation">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/media-playback-stop.png</normaloff>:/icons/icons/media-playback-stop.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Stop Simulation</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionRestart_Simulation">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/media-skip-backward.png</normaloff>:/icons/icons/media-skip-backward.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Restart Simulation</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSimulation_Options">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/configure.png</normaloff>:/icons/icons/configure.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Simulation Options</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionNew_Simulation_Run">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/document-new.png</normaloff>:/icons/icons/document-new.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>New Simulation Run</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionOpen_Simulation_Run">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/document-open.png</normaloff>:/icons/icons/document-open.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Open Simulation Run</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSave_Simulation_Run">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/document-save.png</normaloff>:/icons/icons/document-save.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save Simulation Run</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionUndo">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/edit-undo.png</normaloff>:/icons/icons/edit-undo.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Undo</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+Z</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionRedo">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/edit-redo.png</normaloff>:/icons/icons/edit-redo.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Redo</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+Y</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSettings">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/preferences-system.png</normaloff>:/icons/icons/preferences-system.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Settings</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionAbout">
|
||||||
|
<property name="text">
|
||||||
|
<string>About</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionAbout_QT">
|
||||||
|
<property name="text">
|
||||||
|
<string>About QT</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../../resources/resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -4,7 +4,7 @@ from copy import deepcopy
|
|||||||
|
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
from PySide6.QtGui import QDoubleValidator
|
from PySide6.QtGui import QDoubleValidator
|
||||||
from PySide6.QtWidgets import QDialog, QListWidgetItem, QMessageBox, QWidget
|
from PySide6.QtWidgets import QDialog, QLayout, QListWidgetItem, QMessageBox, QWidget
|
||||||
|
|
||||||
from bedit_core.models import ComponentID
|
from bedit_core.models import ComponentID
|
||||||
from bedit_gui.models import Simulation, SimulationDatabase, SimulationID, SimulationMethod
|
from bedit_gui.models import Simulation, SimulationDatabase, SimulationID, SimulationMethod
|
||||||
@@ -14,7 +14,7 @@ from bedit_gui.ui.generated.ui_simulation_settings import Ui_Dialog
|
|||||||
class SimulationSettingsDialog(QDialog):
|
class SimulationSettingsDialog(QDialog):
|
||||||
"""Editor for the detached simulation database of a document."""
|
"""Editor for the detached simulation database of a document."""
|
||||||
|
|
||||||
def __init__(self, database: SimulationDatabase, components: list[tuple[ComponentID, str]], parent: QWidget | None = None) -> None:
|
def __init__(self, database: SimulationDatabase, components: list[tuple[ComponentID, str]], parent: QWidget | None = None, *, show_simulation_list: bool = True) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.ui = Ui_Dialog()
|
self.ui = Ui_Dialog()
|
||||||
@@ -25,6 +25,9 @@ class SimulationSettingsDialog(QDialog):
|
|||||||
self._components = components
|
self._components = components
|
||||||
self._loading = False
|
self._loading = False
|
||||||
|
|
||||||
|
if not show_simulation_list:
|
||||||
|
self._set_layout_visible(self.ui.simulationListLayout, False)
|
||||||
|
|
||||||
self.ui.startTimeSpinBox.setRange(-1e12, 1e12)
|
self.ui.startTimeSpinBox.setRange(-1e12, 1e12)
|
||||||
self.ui.simLengthSpinBox.setRange(0.0, 1e12)
|
self.ui.simLengthSpinBox.setRange(0.0, 1e12)
|
||||||
self.ui.stepSizeSpinBox.setRange(1e-9, 1e12)
|
self.ui.stepSizeSpinBox.setRange(1e-9, 1e12)
|
||||||
@@ -51,7 +54,7 @@ class SimulationSettingsDialog(QDialog):
|
|||||||
self.ui.simulationMethodComboBox.currentIndexChanged.connect(self._form_changed)
|
self.ui.simulationMethodComboBox.currentIndexChanged.connect(self._form_changed)
|
||||||
self.ui.toleranceEdit.textEdited.connect(self._form_changed)
|
self.ui.toleranceEdit.textEdited.connect(self._form_changed)
|
||||||
|
|
||||||
self._rebuild_list()
|
self._rebuild_list(self._database.active_simulation)
|
||||||
|
|
||||||
def database(self) -> SimulationDatabase:
|
def database(self) -> SimulationDatabase:
|
||||||
return deepcopy(self._database)
|
return deepcopy(self._database)
|
||||||
@@ -68,6 +71,7 @@ class SimulationSettingsDialog(QDialog):
|
|||||||
QMessageBox.warning(self, "Invalid simulation", "The DASSL tolerance must be greater than zero.")
|
QMessageBox.warning(self, "Invalid simulation", "The DASSL tolerance must be greater than zero.")
|
||||||
return
|
return
|
||||||
simulation.name = simulation.name.strip()
|
simulation.name = simulation.name.strip()
|
||||||
|
self._database.active_simulation = self._simulation_id()
|
||||||
super().accept()
|
super().accept()
|
||||||
|
|
||||||
def _rebuild_list(self, selected_id: SimulationID | None = None) -> None:
|
def _rebuild_list(self, selected_id: SimulationID | None = None) -> None:
|
||||||
@@ -172,6 +176,8 @@ class SimulationSettingsDialog(QDialog):
|
|||||||
row = self._simulation_ids.index(simulation_id)
|
row = self._simulation_ids.index(simulation_id)
|
||||||
del self._database.simulations[simulation_id]
|
del self._database.simulations[simulation_id]
|
||||||
self._simulation_ids.remove(simulation_id)
|
self._simulation_ids.remove(simulation_id)
|
||||||
|
if self._database.active_simulation == simulation_id:
|
||||||
|
self._database.active_simulation = None
|
||||||
selected = self._simulation_ids[min(row, len(self._simulation_ids) - 1)] if self._simulation_ids else None
|
selected = self._simulation_ids[min(row, len(self._simulation_ids) - 1)] if self._simulation_ids else None
|
||||||
self._rebuild_list(selected)
|
self._rebuild_list(selected)
|
||||||
|
|
||||||
@@ -193,3 +199,14 @@ class SimulationSettingsDialog(QDialog):
|
|||||||
self.ui.frame.setEnabled(enabled)
|
self.ui.frame.setEnabled(enabled)
|
||||||
self.ui.removeSimulationButton.setEnabled(enabled)
|
self.ui.removeSimulationButton.setEnabled(enabled)
|
||||||
self.ui.addSimulationButton.setEnabled(bool(self._components))
|
self.ui.addSimulationButton.setEnabled(bool(self._components))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _set_layout_visible(cls, layout: QLayout, visible: bool) -> None:
|
||||||
|
for index in range(layout.count()):
|
||||||
|
item = layout.itemAt(index)
|
||||||
|
widget = item.widget()
|
||||||
|
child_layout = item.layout()
|
||||||
|
if widget is not None:
|
||||||
|
widget.setVisible(visible)
|
||||||
|
elif child_layout is not None:
|
||||||
|
cls._set_layout_visible(child_layout, visible)
|
||||||
|
|||||||
13
src/bedit_gui/views/simulation_window.py
Normal file
13
src/bedit_gui/views/simulation_window.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import QMainWindow
|
||||||
|
|
||||||
|
from bedit_gui.ui.generated.ui_simulation_window import Ui_SimulationWindow
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationWindow(QMainWindow):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.ui = Ui_SimulationWindow()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
self.setWindowTitle("BEsim")
|
||||||
@@ -6,6 +6,7 @@ from .openmodelica import (
|
|||||||
ProcessResult,
|
ProcessResult,
|
||||||
)
|
)
|
||||||
from .results import SimulationResult, load_openmodelica_csv
|
from .results import SimulationResult, load_openmodelica_csv
|
||||||
|
from .compile import compile_component, compile_component_sync
|
||||||
from .simulation import (
|
from .simulation import (
|
||||||
ModelBuildResult,
|
ModelBuildResult,
|
||||||
ModelCheckResult,
|
ModelCheckResult,
|
||||||
@@ -18,17 +19,19 @@ from .simulation import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"OpenModelicaError",
|
|
||||||
"OpenModelicaRunner",
|
|
||||||
"ProcessResult",
|
|
||||||
"ModelBuildResult",
|
"ModelBuildResult",
|
||||||
"ModelCheckResult",
|
"ModelCheckResult",
|
||||||
"ModelInfo",
|
"ModelInfo",
|
||||||
|
"OpenModelicaError",
|
||||||
|
"OpenModelicaRunner",
|
||||||
|
"ProcessResult",
|
||||||
"Simulation",
|
"Simulation",
|
||||||
"SimulationOptions",
|
"SimulationOptions",
|
||||||
"SimulationProgress",
|
"SimulationProgress",
|
||||||
"SimulationStateError",
|
|
||||||
"SimulationResult",
|
"SimulationResult",
|
||||||
|
"SimulationStateError",
|
||||||
|
"compile_component",
|
||||||
|
"compile_component_sync",
|
||||||
"load_openmodelica_csv",
|
"load_openmodelica_csv",
|
||||||
"simulate",
|
"simulate",
|
||||||
]
|
]
|
||||||
|
|||||||
21
src/bedit_simulation/compile.py
Normal file
21
src/bedit_simulation/compile.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from bedit_core.models import Component
|
||||||
|
|
||||||
|
from .openmodelica import OpenModelicaRunner
|
||||||
|
from .simulation import ModelBuildResult, Simulation
|
||||||
|
|
||||||
|
|
||||||
|
async def compile_component(component: Component, working_directory: str | Path, *, omc_command: str = "omc", timeout: float | None = None) -> ModelBuildResult:
|
||||||
|
"""Compose and compile a component without creating a GUI application."""
|
||||||
|
simulation = Simulation(OpenModelicaRunner(omc_command, timeout=timeout))
|
||||||
|
await simulation.load(component)
|
||||||
|
return await simulation.compile(working_directory)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_component_sync(component: Component, working_directory: str | Path, *, omc_command: str = "omc", timeout: float | None = None) -> ModelBuildResult:
|
||||||
|
"""Synchronous entry point for scripts and non-async suite applications."""
|
||||||
|
return asyncio.run(compile_component(component, working_directory, omc_command=omc_command, timeout=timeout))
|
||||||
@@ -591,6 +591,7 @@
|
|||||||
},
|
},
|
||||||
"simulation_database": {
|
"simulation_database": {
|
||||||
"format_version": 1,
|
"format_version": 1,
|
||||||
|
"active_simulation": "7778ca68-7a36-401b-b4aa-236a44c5e771",
|
||||||
"simulations": {
|
"simulations": {
|
||||||
"7778ca68-7a36-401b-b4aa-236a44c5e771": {
|
"7778ca68-7a36-401b-b4aa-236a44c5e771": {
|
||||||
"component": "50e6ef97-f686-4400-bc01-e5a352e8cc22",
|
"component": "50e6ef97-f686-4400-bc01-e5a352e8cc22",
|
||||||
|
|||||||
21
untitled.besim.json
Normal file
21
untitled.besim.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"file_format_version": 1,
|
||||||
|
"root_type": "simulation_root",
|
||||||
|
"format_version": 1,
|
||||||
|
"source_document": "/home/joppe/Projects/BEdit/untitled.bedit.json",
|
||||||
|
"source_document_id": "3b6780c7-488b-471e-a784-392db7632090",
|
||||||
|
"component": "50e6ef97-f686-4400-bc01-e5a352e8cc22",
|
||||||
|
"component_path": "some_bondgraph",
|
||||||
|
"settings_name": "run bondgraph",
|
||||||
|
"settings": {
|
||||||
|
"component": "50e6ef97-f686-4400-bc01-e5a352e8cc22",
|
||||||
|
"name": "run bondgraph",
|
||||||
|
"start_time": 0.0,
|
||||||
|
"duration": 10.0,
|
||||||
|
"use_timed_steps": true,
|
||||||
|
"number_of_steps": 500,
|
||||||
|
"step_size": 0.001,
|
||||||
|
"method": "dassl",
|
||||||
|
"dassl_tolerance": 1e-06
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user