Added run button and parameter dialog

This commit is contained in:
2026-07-20 18:46:59 +02:00
parent 4cfe7c3a0e
commit 38e3d7aae9
16 changed files with 587 additions and 72 deletions

View File

@@ -0,0 +1,68 @@
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_simulation_async(
openmodelica_path: str = "", task: Callable[[], None] | None = None
) -> None:
"""Start one simulation task in a background thread and return immediately."""
global _worker
with _worker_lock:
if _worker is not None and _worker.is_alive():
raise RuntimeError("A simulation is already running")
_worker = Thread(
target=_run_safely,
args=(task or (lambda: _run_openmodelica(openmodelica_path)),),
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_openmodelica(openmodelica_path: str = "") -> None:
"""Create the OpenModelica session and perform the current runner stub work."""
from OMPython import OMCSessionZMQ
omhome = _openmodelica_home(openmodelica_path)
omc = OMCSessionZMQ(omhome=omhome)
log.info("OpenModelica Version: %s", omc.sendExpression("getVersion()"))
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)

View File

@@ -2,6 +2,7 @@ from typing import Any
from bedit.core.application_log import get_logger
from bedit.core.simulation.compiler import compile_graph
from bedit.core.simulation.runner import run_simulation_async
log = get_logger(__name__)
@@ -10,11 +11,12 @@ log = get_logger(__name__)
class Simulation:
"""Application-owned simulation state and compiler facade."""
def __init__(self) -> None:
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.id_list: dict[str, Any] = {}
self.openmodelica_path = openmodelica_path
def compile(self, graph: dict[str, Any]) -> None:
"""Compile a serialized component tree and retain the result."""
@@ -24,4 +26,8 @@ class Simulation:
self.id_list = result.objects_by_id
self.last_compilation_output = result.modelica
log.info("Generated Modelica model:\n%s", self.last_compilation_output)
log.info("Simulation settings:\n%s", self.last_compilation_input['implementation']['graph']['simulation'])
def run_simulation(self) -> None:
"""Start the simulation without blocking the calling UI thread."""
run_simulation_async(self.openmodelica_path)

View File

@@ -150,6 +150,19 @@ class EditSimulationSettingsCommand(QUndoCommand):
self.controller._set_simulation_settings(self.owner_id, self.old)
class EditGraphParametersCommand(QUndoCommand):
def __init__(self, controller, old: dict, new: dict) -> None:
super().__init__("Edit graph parameters")
self.controller = controller
self.old, self.new = old, new
def redo(self) -> None:
self.controller._set_graph_parameters(self.new)
def undo(self) -> None:
self.controller._set_graph_parameters(self.old)
class DeleteAnnotationsCommand(QUndoCommand):
def __init__(self, controller, owner_id: str, annotations: dict[str, Annotation]) -> None:
super().__init__("Delete annotations")

View File

@@ -13,6 +13,7 @@ from bedit.gui.controllers.commands import (
DeleteSelectionCommand,
DeleteAnnotationsCommand,
EditGraphItemCommand,
EditGraphParametersCommand,
EditSimulationSettingsCommand,
EditTextDefinitionCommand,
EditComponentAppearanceCommand,
@@ -393,12 +394,45 @@ class DocumentController(QObject):
EditSimulationSettingsCommand(self, component.id, old, new)
)
def edit_graph_parameter_values(
self, root_id: str, values: dict[str, dict[str, str]]
) -> None:
root = self.document.find_component(root_id) if self.document else None
if root is None:
return
subtree_ids = {component.id for component in self._component_subtree(root)}
if not set(values) <= subtree_ids:
raise ValueError("Parameter changes contain a component outside the active graph")
old: dict[str, list[dict]] = {}
new: dict[str, list[dict]] = {}
for component_id, parameter_values in values.items():
component = self.document.find_component(component_id)
known_ids = {parameter.id for parameter in component.parameters}
if not set(parameter_values) <= known_ids:
raise ValueError(f"Component {component.name!r} contains an unknown parameter")
updated = deepcopy(component.parameters)
for parameter in updated:
if parameter.id in parameter_values:
parameter.value = parameter_values[parameter.id]
old[component_id] = [parameter.to_dict() for parameter in component.parameters]
new[component_id] = [parameter.to_dict() for parameter in updated]
if old != new:
self.undo_stack.push(EditGraphParametersCommand(self, old, new))
def compile_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())
def run_simulation(self) -> None:
component = self.active_component
if component is None or component.implementation_kind != "graph":
raise ValueError("Open a graph component before running a simulation")
self.simulation.run_simulation()
def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
item = (
self.active_graph.connections
@@ -1082,6 +1116,22 @@ class DocumentController(QObject):
owner.graph.simulation_settings = deepcopy(settings)
self.documentReset.emit()
def _set_graph_parameters(self, values: dict[str, list[dict]]) -> None:
if self.document is None:
return
changed_text_components: list[str] = []
for component_id, parameters in values.items():
component = self.document.find_component(component_id)
if component is None:
continue
component.parameters = [Parameter.from_dict(item) for item in parameters]
if component.implementation_kind == "text":
changed_text_components.append(component_id)
self.document.validate()
self.documentReset.emit()
for component_id in changed_text_components:
self.textDefinitionChanged.emit(component_id)
def _replace_component(self, old_id: str, replacement: Component) -> None:
if self.document is None:
return

View File

@@ -0,0 +1,93 @@
from copy import deepcopy
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QDialog, QStyledItemDelegate, QTreeWidgetItem
from bedit.core.model import Component
from bedit.gui.generated.ui_graph_parameters_dialog import Ui_GraphParametersDialog
COMPONENT_ID_ROLE = Qt.ItemDataRole.UserRole
PARAMETER_ID_ROLE = Qt.ItemDataRole.UserRole + 1
class ValueColumnDelegate(QStyledItemDelegate):
"""Allow editing only in the parameter value column."""
def createEditor(self, parent, option, index): # noqa: N802 (Qt API name)
parameter_id = index.siblingAtColumn(0).data(PARAMETER_ID_ROLE)
if index.column() != 2 or parameter_id is None:
return None
return super().createEditor(parent, option, index)
class GraphParametersDialog(QDialog):
"""Edit every parameter value in an active component subtree."""
def __init__(self, root: Component, parent=None) -> None:
super().__init__(parent)
self.ui = Ui_GraphParametersDialog()
self.ui.setupUi(self)
self.ui.parameterTree.setItemDelegate(ValueColumnDelegate(self.ui.parameterTree))
self.setWindowTitle(f"Graph Parameters — {root.name}")
self._parameters = {
component.id: deepcopy(component.parameters)
for component in self._walk(root)
}
self._populate(root)
self.ui.parameterTree.expandAll()
self.ui.parameterTree.resizeColumnToContents(0)
self.ui.parameterTree.resizeColumnToContents(1)
@staticmethod
def _walk(component: Component):
yield component
if component.implementation_kind == "graph":
for child in component.graph.blocks.values():
yield from GraphParametersDialog._walk(child)
def _populate(self, root: Component) -> None:
self.ui.parameterTree.clear()
def add_component(component: Component, parent: QTreeWidgetItem | None) -> None:
item = QTreeWidgetItem([component.name, "", ""])
item.setData(0, COMPONENT_ID_ROLE, component.id)
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
if parent is None:
self.ui.parameterTree.addTopLevelItem(item)
else:
parent.addChild(item)
for parameter in self._parameters[component.id]:
parameter_item = QTreeWidgetItem(
[parameter.name, parameter.type, parameter.value]
)
parameter_item.setData(0, COMPONENT_ID_ROLE, component.id)
parameter_item.setData(0, PARAMETER_ID_ROLE, parameter.id)
parameter_item.setFlags(
parameter_item.flags() | Qt.ItemFlag.ItemIsEditable
)
item.addChild(parameter_item)
if component.implementation_kind == "graph":
for child in component.graph.blocks.values():
add_component(child, item)
add_component(root, None)
@property
def parameter_values(self) -> dict[str, dict[str, str]]:
values: dict[str, dict[str, str]] = {}
iterator = self.ui.parameterTree.invisibleRootItem()
def collect(parent: QTreeWidgetItem) -> None:
for index in range(parent.childCount()):
item = parent.child(index)
parameter_id = item.data(0, PARAMETER_ID_ROLE)
if parameter_id is not None:
component_id = item.data(0, COMPONENT_ID_ROLE)
values.setdefault(component_id, {})[parameter_id] = item.text(2)
collect(item)
collect(iterator)
return values

View File

@@ -26,6 +26,7 @@ class SettingsDialog(QDialog):
self.ui.addLibraryFileButton.clicked.connect(self._add_library_file)
self.ui.addLibraryFolderButton.clicked.connect(self._add_library_folder)
self.ui.removeLibraryPathButton.clicked.connect(self._remove_library_path)
self.ui.browseOpenModelicaButton.clicked.connect(self._browse_openmodelica)
self.ui.libraryPathsList.itemSelectionChanged.connect(self._update_remove_button)
self.ui.syntaxStylesTable.cellDoubleClicked.connect(self._choose_syntax_color)
self._load_settings()
@@ -46,6 +47,7 @@ class SettingsDialog(QDialog):
self.ui.graphGridSpinBox.setValue(self.graph_grid_size(self.settings))
self.ui.graphSnapSpinBox.setValue(self.graph_snap_size(self.settings))
self.ui.iconGridSpinBox.setValue(self.icon_grid_size(self.settings))
self.ui.openModelicaPathEdit.setText(self.openmodelica_path(self.settings))
self._load_syntax_styles()
self._update_remove_button()
@@ -130,6 +132,25 @@ class SettingsDialog(QDialog):
settings = settings if settings is not None else application_settings()
return settings.value("grid/iconSize", 8, type=int)
@staticmethod
def openmodelica_path(settings: QSettings | None = None) -> str:
settings = settings if settings is not None else application_settings()
return str(settings.value("simulation/openModelicaPath", "") or "")
def _browse_openmodelica(self) -> None:
current = self.ui.openModelicaPathEdit.text().strip()
start = str(Path(current).expanduser().parent) if current else ""
path, _ = QFileDialog.getOpenFileName(
self,
"Select OpenModelica executable",
start,
"OpenModelica compiler (omc omc.exe);;All files (*)",
)
if path:
# Keep symlink paths such as ~/.local/bin/omc intact; resolving them
# could turn the selected launcher into an unrelated container script.
self.ui.openModelicaPathEdit.setText(str(Path(path).expanduser().absolute()))
def _add_library_file(self) -> None:
path, _ = QFileDialog.getOpenFileName(
self,
@@ -176,6 +197,10 @@ class SettingsDialog(QDialog):
self.settings.setValue("grid/graphSize", self.ui.graphGridSpinBox.value())
self.settings.setValue("grid/graphSnapSize", self.ui.graphSnapSpinBox.value())
self.settings.setValue("grid/iconSize", self.ui.iconGridSpinBox.value())
self.settings.setValue(
"simulation/openModelicaPath",
self.ui.openModelicaPathEdit.text().strip(),
)
for row in range(self.ui.syntaxStylesTable.rowCount()):
category = self.ui.syntaxStylesTable.item(row, 0).data(
Qt.ItemDataRole.UserRole

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'graph_parameters_dialog.ui'
##
## Created by: Qt User Interface Compiler version 6.11.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
QHeaderView, QLabel, QSizePolicy, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget)
class Ui_GraphParametersDialog(object):
def setupUi(self, GraphParametersDialog):
if not GraphParametersDialog.objectName():
GraphParametersDialog.setObjectName(u"GraphParametersDialog")
GraphParametersDialog.resize(620, 480)
self.dialogLayout = QVBoxLayout(GraphParametersDialog)
self.dialogLayout.setObjectName(u"dialogLayout")
self.descriptionLabel = QLabel(GraphParametersDialog)
self.descriptionLabel.setObjectName(u"descriptionLabel")
self.dialogLayout.addWidget(self.descriptionLabel)
self.parameterTree = QTreeWidget(GraphParametersDialog)
self.parameterTree.setObjectName(u"parameterTree")
self.parameterTree.setAlternatingRowColors(True)
self.parameterTree.setRootIsDecorated(True)
self.parameterTree.setColumnCount(3)
self.dialogLayout.addWidget(self.parameterTree)
self.buttonBox = QDialogButtonBox(GraphParametersDialog)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok)
self.dialogLayout.addWidget(self.buttonBox)
self.retranslateUi(GraphParametersDialog)
self.buttonBox.accepted.connect(GraphParametersDialog.accept)
self.buttonBox.rejected.connect(GraphParametersDialog.reject)
QMetaObject.connectSlotsByName(GraphParametersDialog)
# setupUi
def retranslateUi(self, GraphParametersDialog):
GraphParametersDialog.setWindowTitle(QCoreApplication.translate("GraphParametersDialog", u"Graph Parameters", None))
self.descriptionLabel.setText(QCoreApplication.translate("GraphParametersDialog", u"Edit parameter values throughout the active component tree.", None))
___qtreewidgetitem = self.parameterTree.headerItem()
___qtreewidgetitem.setText(2, QCoreApplication.translate("GraphParametersDialog", u"Value", None))
___qtreewidgetitem.setText(1, QCoreApplication.translate("GraphParametersDialog", u"Type", None))
___qtreewidgetitem.setText(0, QCoreApplication.translate("GraphParametersDialog", u"Component / Parameter", None))
# retranslateUi

View File

@@ -36,84 +36,94 @@ class Ui_MainWindow(object):
icon = QIcon()
icon.addFile(u":/icons/icons/preferences-system.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSimulationSettings.setIcon(icon)
self.actionGraphParameters = QAction(MainWindow)
self.actionGraphParameters.setObjectName(u"actionGraphParameters")
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")
icon1 = QIcon()
icon1.addFile(u":/icons/icons/run-build.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCompile.setIcon(icon1)
icon2 = QIcon()
icon2.addFile(u":/icons/icons/run-build.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCompile.setIcon(icon2)
self.actionRunSimulation = QAction(MainWindow)
self.actionRunSimulation.setObjectName(u"actionRunSimulation")
icon3 = QIcon()
icon3.addFile(u":/icons/icons/media-playback-start.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRunSimulation.setIcon(icon3)
self.actionNew = QAction(MainWindow)
self.actionNew.setObjectName(u"actionNew")
icon2 = QIcon()
icon2.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionNew.setIcon(icon2)
icon4 = QIcon()
icon4.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionNew.setIcon(icon4)
self.actionRotateClockwise = QAction(MainWindow)
self.actionRotateClockwise.setObjectName(u"actionRotateClockwise")
icon3 = QIcon()
icon3.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRotateClockwise.setIcon(icon3)
icon5 = QIcon()
icon5.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRotateClockwise.setIcon(icon5)
self.actionZoomIn = QAction(MainWindow)
self.actionZoomIn.setObjectName(u"actionZoomIn")
icon4 = QIcon()
icon4.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionZoomIn.setIcon(icon4)
icon6 = QIcon()
icon6.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionZoomIn.setIcon(icon6)
self.actionZoomOut = QAction(MainWindow)
self.actionZoomOut.setObjectName(u"actionZoomOut")
icon5 = QIcon()
icon5.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionZoomOut.setIcon(icon5)
icon7 = QIcon()
icon7.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionZoomOut.setIcon(icon7)
self.actionCenterView = QAction(MainWindow)
self.actionCenterView.setObjectName(u"actionCenterView")
icon6 = QIcon()
icon6.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCenterView.setIcon(icon6)
icon8 = QIcon()
icon8.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCenterView.setIcon(icon8)
self.actionOpen = QAction(MainWindow)
self.actionOpen.setObjectName(u"actionOpen")
icon7 = QIcon()
icon7.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionOpen.setIcon(icon7)
icon9 = QIcon()
icon9.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionOpen.setIcon(icon9)
self.actionReloadLibraries = QAction(MainWindow)
self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
self.actionReloadSimulation = QAction(MainWindow)
self.actionReloadSimulation.setObjectName(u"actionReloadSimulation")
self.actionSave = QAction(MainWindow)
self.actionSave.setObjectName(u"actionSave")
icon8 = QIcon()
icon8.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSave.setIcon(icon8)
icon10 = QIcon()
icon10.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSave.setIcon(icon10)
self.actionSaveAs = QAction(MainWindow)
self.actionSaveAs.setObjectName(u"actionSaveAs")
icon9 = QIcon()
icon9.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSaveAs.setIcon(icon9)
icon11 = QIcon()
icon11.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSaveAs.setIcon(icon11)
self.actionExit = QAction(MainWindow)
self.actionExit.setObjectName(u"actionExit")
self.actionClose = QAction(MainWindow)
self.actionClose.setObjectName(u"actionClose")
self.actionUndo = QAction(MainWindow)
self.actionUndo.setObjectName(u"actionUndo")
icon10 = QIcon()
icon10.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionUndo.setIcon(icon10)
icon12 = QIcon()
icon12.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionUndo.setIcon(icon12)
self.actionRedo = QAction(MainWindow)
self.actionRedo.setObjectName(u"actionRedo")
icon11 = QIcon()
icon11.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRedo.setIcon(icon11)
icon13 = QIcon()
icon13.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRedo.setIcon(icon13)
self.actionCut = QAction(MainWindow)
self.actionCut.setObjectName(u"actionCut")
icon12 = QIcon()
icon12.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCut.setIcon(icon12)
icon14 = QIcon()
icon14.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCut.setIcon(icon14)
self.actionCopy = QAction(MainWindow)
self.actionCopy.setObjectName(u"actionCopy")
icon13 = QIcon()
icon13.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCopy.setIcon(icon13)
icon15 = QIcon()
icon15.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCopy.setIcon(icon15)
self.actionPaste = QAction(MainWindow)
self.actionPaste.setObjectName(u"actionPaste")
icon14 = QIcon()
icon14.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionPaste.setIcon(icon14)
icon16 = QIcon()
icon16.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionPaste.setIcon(icon16)
self.actionSelectAll = QAction(MainWindow)
self.actionSelectAll.setObjectName(u"actionSelectAll")
self.actionDelete = QAction(MainWindow)
@@ -203,18 +213,18 @@ class Ui_MainWindow(object):
self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2)
self.navigateUpButton = QToolButton(self.workspaceHeader)
self.navigateUpButton.setObjectName(u"navigateUpButton")
icon15 = QIcon()
icon15.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.navigateUpButton.setIcon(icon15)
icon17 = QIcon()
icon17.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.navigateUpButton.setIcon(icon17)
self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
self.navigateDownButton = QToolButton(self.workspaceHeader)
self.navigateDownButton.setObjectName(u"navigateDownButton")
self.navigateDownButton.setEnabled(False)
icon16 = QIcon()
icon16.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.navigateDownButton.setIcon(icon16)
icon18 = QIcon()
icon18.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.navigateDownButton.setIcon(icon18)
self.workspaceHeaderLayout.addWidget(self.navigateDownButton)
@@ -234,9 +244,9 @@ class Ui_MainWindow(object):
self.pointerToolButton = QToolButton(self.workspaceHeader)
self.pointerToolButton.setObjectName(u"pointerToolButton")
icon17 = QIcon()
icon17.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.pointerToolButton.setIcon(icon17)
icon19 = QIcon()
icon19.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.pointerToolButton.setIcon(icon19)
self.pointerToolButton.setCheckable(True)
self.pointerToolButton.setChecked(True)
@@ -244,43 +254,43 @@ class Ui_MainWindow(object):
self.connectToolButton = QToolButton(self.workspaceHeader)
self.connectToolButton.setObjectName(u"connectToolButton")
icon18 = QIcon()
icon18.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.connectToolButton.setIcon(icon18)
icon20 = QIcon()
icon20.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.connectToolButton.setIcon(icon20)
self.connectToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.connectToolButton)
self.boxToolButton = QToolButton(self.workspaceHeader)
self.boxToolButton.setObjectName(u"boxToolButton")
icon19 = QIcon()
icon19.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.boxToolButton.setIcon(icon19)
icon21 = QIcon()
icon21.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.boxToolButton.setIcon(icon21)
self.boxToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.boxToolButton)
self.lineToolButton = QToolButton(self.workspaceHeader)
self.lineToolButton.setObjectName(u"lineToolButton")
icon20 = QIcon()
icon20.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.lineToolButton.setIcon(icon20)
icon22 = QIcon()
icon22.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.lineToolButton.setIcon(icon22)
self.lineToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.lineToolButton)
self.textToolButton = QToolButton(self.workspaceHeader)
self.textToolButton.setObjectName(u"textToolButton")
icon21 = QIcon()
icon21.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.textToolButton.setIcon(icon21)
icon23 = QIcon()
icon23.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.textToolButton.setIcon(icon23)
self.textToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.textToolButton)
self.rotateToolButton = QToolButton(self.workspaceHeader)
self.rotateToolButton.setObjectName(u"rotateToolButton")
self.rotateToolButton.setIcon(icon3)
self.rotateToolButton.setIcon(icon5)
self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
@@ -433,7 +443,9 @@ class Ui_MainWindow(object):
self.cameraToolbar.addAction(self.actionZoomOut)
self.cameraToolbar.addAction(self.actionCenterView)
self.simulationToolbar.addAction(self.actionSimulationSettings)
self.simulationToolbar.addAction(self.actionGraphParameters)
self.simulationToolbar.addAction(self.actionCompile)
self.simulationToolbar.addAction(self.actionRunSimulation)
self.retranslateUi(MainWindow)
@@ -448,6 +460,10 @@ class Ui_MainWindow(object):
self.actionSimulationSettings.setText(QCoreApplication.translate("MainWindow", u"Simulation Settings", None))
#if QT_CONFIG(statustip)
self.actionSimulationSettings.setStatusTip(QCoreApplication.translate("MainWindow", u"Edit settings stored in the active graph", None))
#endif // QT_CONFIG(statustip)
self.actionGraphParameters.setText(QCoreApplication.translate("MainWindow", u"Graph Parameters", None))
#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))
#if QT_CONFIG(statustip)
@@ -455,6 +471,13 @@ class Ui_MainWindow(object):
#endif // QT_CONFIG(statustip)
#if QT_CONFIG(shortcut)
self.actionCompile.setShortcut(QCoreApplication.translate("MainWindow", u"F5", None))
#endif // QT_CONFIG(shortcut)
self.actionRunSimulation.setText(QCoreApplication.translate("MainWindow", u"Run", None))
#if QT_CONFIG(statustip)
self.actionRunSimulation.setStatusTip(QCoreApplication.translate("MainWindow", u"Run the simulation", None))
#endif // QT_CONFIG(statustip)
#if QT_CONFIG(shortcut)
self.actionRunSimulation.setShortcut(QCoreApplication.translate("MainWindow", u"F6", None))
#endif // QT_CONFIG(shortcut)
self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None))
#if QT_CONFIG(statustip)

View File

@@ -17,10 +17,10 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QAbstractItemView, QApplication, QDialog,
QDialogButtonBox, QFormLayout, QGroupBox, QHBoxLayout,
QHeaderView, QLabel, QListWidget, QListWidgetItem,
QPushButton, QSizePolicy, QSpacerItem, QSpinBox,
QTabWidget, QTableWidget, QTableWidgetItem, QVBoxLayout,
QWidget)
QHeaderView, QLabel, QLineEdit, QListWidget,
QListWidgetItem, QPushButton, QSizePolicy, QSpacerItem,
QSpinBox, QTabWidget, QTableWidget, QTableWidgetItem,
QVBoxLayout, QWidget)
class Ui_SettingsDialog(object):
def setupUi(self, SettingsDialog):
@@ -104,6 +104,48 @@ class Ui_SettingsDialog(object):
self.generalLayout.addItem(self.generalSpacer)
self.settingsTabs.addTab(self.generalTab, "")
self.simulationTab = QWidget()
self.simulationTab.setObjectName(u"simulationTab")
self.simulationTabLayout = QVBoxLayout(self.simulationTab)
self.simulationTabLayout.setObjectName(u"simulationTabLayout")
self.openModelicaGroupBox = QGroupBox(self.simulationTab)
self.openModelicaGroupBox.setObjectName(u"openModelicaGroupBox")
self.openModelicaLayout = QVBoxLayout(self.openModelicaGroupBox)
self.openModelicaLayout.setObjectName(u"openModelicaLayout")
self.openModelicaPathLabel = QLabel(self.openModelicaGroupBox)
self.openModelicaPathLabel.setObjectName(u"openModelicaPathLabel")
self.openModelicaLayout.addWidget(self.openModelicaPathLabel)
self.openModelicaPathLayout = QHBoxLayout()
self.openModelicaPathLayout.setObjectName(u"openModelicaPathLayout")
self.openModelicaPathEdit = QLineEdit(self.openModelicaGroupBox)
self.openModelicaPathEdit.setObjectName(u"openModelicaPathEdit")
self.openModelicaPathLayout.addWidget(self.openModelicaPathEdit)
self.browseOpenModelicaButton = QPushButton(self.openModelicaGroupBox)
self.browseOpenModelicaButton.setObjectName(u"browseOpenModelicaButton")
self.openModelicaPathLayout.addWidget(self.browseOpenModelicaButton)
self.openModelicaLayout.addLayout(self.openModelicaPathLayout)
self.openModelicaHintLabel = QLabel(self.openModelicaGroupBox)
self.openModelicaHintLabel.setObjectName(u"openModelicaHintLabel")
self.openModelicaHintLabel.setWordWrap(True)
self.openModelicaLayout.addWidget(self.openModelicaHintLabel)
self.simulationTabLayout.addWidget(self.openModelicaGroupBox)
self.simulationSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.simulationTabLayout.addItem(self.simulationSpacer)
self.settingsTabs.addTab(self.simulationTab, "")
self.syntaxTab = QWidget()
self.syntaxTab.setObjectName(u"syntaxTab")
self.syntaxTabLayout = QVBoxLayout(self.syntaxTab)
@@ -205,6 +247,12 @@ class Ui_SettingsDialog(object):
self.iconGridLabel.setText(QCoreApplication.translate("SettingsDialog", u"Icon grid size:", None))
self.iconGridSpinBox.setSuffix(QCoreApplication.translate("SettingsDialog", u" units", None))
self.settingsTabs.setTabText(self.settingsTabs.indexOf(self.generalTab), QCoreApplication.translate("SettingsDialog", u"General", None))
self.openModelicaGroupBox.setTitle(QCoreApplication.translate("SettingsDialog", u"OpenModelica", None))
self.openModelicaPathLabel.setText(QCoreApplication.translate("SettingsDialog", u"OpenModelica executable:", None))
self.openModelicaPathEdit.setPlaceholderText(QCoreApplication.translate("SettingsDialog", u"Leave empty to find omc on PATH", None))
self.browseOpenModelicaButton.setText(QCoreApplication.translate("SettingsDialog", u"Browse\u2026", None))
self.openModelicaHintLabel.setText(QCoreApplication.translate("SettingsDialog", u"Select the omc executable, for example ~/.local/bin/omc.", None))
self.settingsTabs.setTabText(self.settingsTabs.indexOf(self.simulationTab), QCoreApplication.translate("SettingsDialog", u"Simulation", None))
self.syntaxHintLabel.setText(QCoreApplication.translate("SettingsDialog", u"Double-click a colour cell to choose a colour. Word lists remain editable in data/syntax/openmodelica.json.", None))
___qtablewidgetitem = self.syntaxStylesTable.horizontalHeaderItem(0)
___qtablewidgetitem.setText(QCoreApplication.translate("SettingsDialog", u"Expression type", None))

View File

@@ -18,6 +18,7 @@ from bedit.core.serializer import DocumentSerializer
from bedit.core.simulation import Simulation
from bedit.gui.controllers.document import DocumentController
from bedit.gui.dialogs.component_options import ComponentOptionsDialog
from bedit.gui.dialogs.graph_parameters import GraphParametersDialog
from bedit.gui.dialogs.item_options import ItemOptionsDialog
from bedit.gui.models.library_repository import LibraryRepository
from bedit.gui.models.library_tree import (
@@ -55,7 +56,9 @@ class MainWindow(QMainWindow):
self._applying_text_definition = False
self.libraries = LibraryRepository(self)
self.simulation = Simulation()
self.simulation = Simulation(
openmodelica_path=SettingsDialog.openmodelica_path(self.settings)
)
self.document_controller = DocumentController(self, simulation=self.simulation)
self.library_tree_model = LibraryTreeModel(
self.libraries,
@@ -162,7 +165,9 @@ class MainWindow(QMainWindow):
self.ui.actionSimulationSettings.triggered.connect(
self.show_simulation_settings
)
self.ui.actionGraphParameters.triggered.connect(self.show_graph_parameters)
self.ui.actionCompile.triggered.connect(self.compile_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)
self.document_controller.modifiedChanged.connect(lambda _modified: self._update_title())
@@ -218,6 +223,21 @@ class MainWindow(QMainWindow):
if dialog.exec() == dialog.DialogCode.Accepted:
self.document_controller.edit_simulation_settings(dialog.settings)
@Slot()
def show_graph_parameters(self) -> None:
component = self.document_controller.active_component
if component is None or component.implementation_kind != "graph":
return
dialog = GraphParametersDialog(component, self)
if dialog.exec() == dialog.DialogCode.Accepted:
try:
self.document_controller.edit_graph_parameter_values(
component.id, dialog.parameter_values
)
except ValueError as error:
self.log.error("Could not change graph parameters: %s", error)
QMessageBox.warning(self, "Cannot change graph parameters", str(error))
@Slot()
def compile_active_graph(self) -> None:
try:
@@ -226,6 +246,14 @@ class MainWindow(QMainWindow):
self.log.error("Compile failed: %s", error)
QMessageBox.warning(self, "Cannot compile", str(error))
@Slot()
def run_simulation(self) -> None:
try:
self.document_controller.run_simulation()
except Exception as error:
self.log.exception("Simulation run failed")
QMessageBox.warning(self, "Cannot run simulation", str(error))
def _restore_window_geometry(self) -> None:
geometry = self.settings.value("window/geometry")
if geometry is not None:
@@ -252,7 +280,9 @@ class MainWindow(QMainWindow):
self.ui.workspaceStack.setCurrentWidget(self.ui.emptyPage)
self._set_graph_controls_visible(False)
self.ui.actionSimulationSettings.setEnabled(False)
self.ui.actionGraphParameters.setEnabled(False)
self.ui.actionCompile.setEnabled(False)
self.ui.actionRunSimulation.setEnabled(False)
self._update_edit_actions()
return
self.ui.graphBreadcrumbLabel.setText(" ".join(self.document_controller.breadcrumb()))
@@ -261,7 +291,9 @@ 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.actionRunSimulation.setEnabled(is_graph)
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
self.ui.workspaceStack.setCurrentWidget(
self.ui.graphPage if is_graph else self.ui.textPage
@@ -483,8 +515,14 @@ class MainWindow(QMainWindow):
dialog = SettingsDialog(self)
dialog.settingsChanged.connect(self.reload_libraries)
dialog.settingsChanged.connect(self.refresh_editor_settings)
dialog.settingsChanged.connect(self.refresh_simulation_preferences)
dialog.exec()
def refresh_simulation_preferences(self) -> None:
self.simulation.openmodelica_path = SettingsDialog.openmodelica_path(
self.settings
)
def refresh_editor_settings(self) -> None:
scene = self.ui.graphView.scene()
if scene is not None: