Compare commits
2 Commits
748bb08531
...
b3aabae5e8
| Author | SHA1 | Date | |
|---|---|---|---|
| b3aabae5e8 | |||
| a03c6d624e |
@@ -9,6 +9,7 @@ from bedit_gui.controllers.clipboard_controller import ClipboardController, Docu
|
|||||||
from bedit_gui.controllers.document_controller import DocumentController
|
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.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
|
||||||
@@ -46,6 +47,7 @@ def main() -> int:
|
|||||||
LogController(window, settings.log_level)
|
LogController(window, settings.log_level)
|
||||||
DocumentController(document, window)
|
DocumentController(document, window)
|
||||||
SettingsController(window, settings)
|
SettingsController(window, settings)
|
||||||
|
SimulationSettingsController(document, window)
|
||||||
UndoController(document, window)
|
UndoController(document, window)
|
||||||
ViewMenuController(window)
|
ViewMenuController(window)
|
||||||
document_tree_controller = DocumentTreeController(document, window)
|
document_tree_controller = DocumentTreeController(document, window)
|
||||||
|
|||||||
21
src/bedit_gui/commands/simulation_database_command.py
Normal file
21
src/bedit_gui/commands/simulation_database_command.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from PySide6.QtGui import QUndoCommand
|
||||||
|
|
||||||
|
from bedit_gui.models import SimulationDatabase
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSimulationDatabaseCommand(QUndoCommand):
|
||||||
|
def __init__(self, document: object, database: SimulationDatabase) -> None:
|
||||||
|
super().__init__("Edit simulation settings")
|
||||||
|
self.document = document
|
||||||
|
self.old_database = document.stored_simulation_database()
|
||||||
|
self.new_database = deepcopy(database)
|
||||||
|
|
||||||
|
def redo(self) -> None:
|
||||||
|
self.document._set_simulation_database(self.new_database)
|
||||||
|
|
||||||
|
def undo(self) -> None:
|
||||||
|
self.document._set_simulation_database(self.old_database)
|
||||||
@@ -2,7 +2,8 @@ from collections.abc import Callable
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
from PySide6.QtCore import QObject, QPoint, QSize, Qt
|
from PySide6.QtCore import QEvent, QObject, QPoint, QSize, Qt
|
||||||
|
from PySide6.QtGui import QMouseEvent
|
||||||
from PySide6.QtWidgets import QAbstractItemView, QDialog, QHeaderView, QMenu
|
from PySide6.QtWidgets import QAbstractItemView, QDialog, QHeaderView, QMenu
|
||||||
|
|
||||||
from bedit_core.models import Component, ComponentID, EquationImplementation, GraphImplementation, Port, PortID, Parameter, ParameterID
|
from bedit_core.models import Component, ComponentID, EquationImplementation, GraphImplementation, Port, PortID, Parameter, ParameterID
|
||||||
@@ -79,15 +80,20 @@ class DocumentTreeController(QObject):
|
|||||||
window.ui.documentTree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
window.ui.documentTree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
||||||
window.ui.documentTree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
window.ui.documentTree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
||||||
window.ui.documentTree.setColumnWidth(1, 56)
|
window.ui.documentTree.setColumnWidth(1, 56)
|
||||||
window.ui.documentTree.setContextMenuPolicy(
|
self._tree_viewport = window.ui.documentTree.viewport()
|
||||||
Qt.ContextMenuPolicy.CustomContextMenu
|
self._tree_viewport.installEventFilter(self)
|
||||||
)
|
|
||||||
window.ui.documentTree.customContextMenuRequested.connect(
|
|
||||||
self._show_context_menu
|
|
||||||
)
|
|
||||||
|
|
||||||
self._on_document_changed(document.model)
|
self._on_document_changed(document.model)
|
||||||
|
|
||||||
|
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
|
||||||
|
if watched is self._tree_viewport and event.type() in (QEvent.Type.MouseButtonPress, QEvent.Type.MouseButtonRelease):
|
||||||
|
assert isinstance(event, QMouseEvent)
|
||||||
|
if event.button() == Qt.MouseButton.RightButton:
|
||||||
|
if event.type() == QEvent.Type.MouseButtonRelease:
|
||||||
|
self._show_context_menu(event.position().toPoint())
|
||||||
|
return True
|
||||||
|
return super().eventFilter(watched, event)
|
||||||
|
|
||||||
def _on_document_changed(self, model: CoreDocument) -> None:
|
def _on_document_changed(self, model: CoreDocument) -> None:
|
||||||
"""Rebuild the tree whenever New/Open replaces the core document."""
|
"""Rebuild the tree whenever New/Open replaces the core document."""
|
||||||
self._show_equation_component(None)
|
self._show_equation_component(None)
|
||||||
|
|||||||
48
src/bedit_gui/controllers/simulation_settings_controller.py
Normal file
48
src/bedit_gui/controllers/simulation_settings_controller.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
from PySide6.QtCore import QObject
|
||||||
|
from PySide6.QtWidgets import QDialog
|
||||||
|
|
||||||
|
from bedit_core.models import Component, ComponentID, GraphImplementation
|
||||||
|
from bedit_gui.documents import Document
|
||||||
|
from bedit_gui.models import SimulationDatabase
|
||||||
|
from bedit_gui.views.dialogs.simulation_settings_dialog import SimulationSettingsDialog
|
||||||
|
from bedit_gui.views.main_window import MainWindow
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationSettingsDialogLike(Protocol):
|
||||||
|
def exec(self) -> int: ...
|
||||||
|
def database(self) -> SimulationDatabase: ...
|
||||||
|
|
||||||
|
|
||||||
|
SimulationSettingsDialogFactory = Callable[[SimulationDatabase, list[tuple[ComponentID, str]], MainWindow], SimulationSettingsDialogLike]
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationSettingsController(QObject):
|
||||||
|
def __init__(self, document: Document, window: MainWindow, dialog_factory: SimulationSettingsDialogFactory = SimulationSettingsDialog) -> None:
|
||||||
|
super().__init__(window)
|
||||||
|
self.document = document
|
||||||
|
self.window = window
|
||||||
|
self.dialog_factory = dialog_factory
|
||||||
|
window.ui.actionSimulation_Settings.triggered.connect(self.open_settings)
|
||||||
|
|
||||||
|
def open_settings(self) -> None:
|
||||||
|
dialog = self.dialog_factory(self.document.simulation_database(), self._components(), self.window)
|
||||||
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
|
self.document.change_simulation_database(dialog.database())
|
||||||
|
|
||||||
|
def _components(self) -> list[tuple[ComponentID, str]]:
|
||||||
|
components: list[tuple[ComponentID, str]] = []
|
||||||
|
|
||||||
|
def collect(items: dict[ComponentID, Component], path: tuple[str, ...] = ()) -> None:
|
||||||
|
for component_id, component in items.items():
|
||||||
|
component_path = (*path, component.name)
|
||||||
|
components.append((component_id, ".".join(component_path)))
|
||||||
|
if isinstance(component.implementation, GraphImplementation):
|
||||||
|
collect(component.implementation.graph.components, component_path)
|
||||||
|
|
||||||
|
collect(self.document.model.root)
|
||||||
|
return components
|
||||||
@@ -14,8 +14,9 @@ from bedit_gui.commands.port_commands import AddPortCommand, ChangePortCommand,
|
|||||||
from bedit_gui.commands.param_commands import AddParamCommand, ChangeParamCommand, RemoveParamCommand
|
from bedit_gui.commands.param_commands import AddParamCommand, ChangeParamCommand, RemoveParamCommand
|
||||||
from bedit_gui.commands.rename_component_command import RenameComponentCommand
|
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.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
|
from bedit_gui.models import Icon, IconDatabase, SimulationDatabase
|
||||||
from bedit_gui.services import document_files
|
from bedit_gui.services import document_files
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ class Document(QObject):
|
|||||||
modified_changed = Signal(bool)
|
modified_changed = Signal(bool)
|
||||||
icon_changed = Signal(object, object)
|
icon_changed = Signal(object, object)
|
||||||
equation_text_changed = Signal(object, str)
|
equation_text_changed = Signal(object, str)
|
||||||
|
simulation_database_changed = Signal(object)
|
||||||
|
|
||||||
def __init__(self, parent: QObject | None = None) -> None:
|
def __init__(self, parent: QObject | None = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -122,6 +124,44 @@ class Document(QObject):
|
|||||||
def change_icon(self, component_id: ComponentID, icon: Icon) -> None:
|
def change_icon(self, component_id: ComponentID, icon: Icon) -> None:
|
||||||
self.undo_stack.push(ChangeIconCommand(self, component_id, icon))
|
self.undo_stack.push(ChangeIconCommand(self, component_id, icon))
|
||||||
|
|
||||||
|
def stored_simulation_database(self) -> SimulationDatabase | None:
|
||||||
|
database = self._simulation_database(False)
|
||||||
|
return deepcopy(database) if database is not None else None
|
||||||
|
|
||||||
|
def simulation_database(self) -> SimulationDatabase:
|
||||||
|
return self.stored_simulation_database() or SimulationDatabase()
|
||||||
|
|
||||||
|
def change_simulation_database(self, database: SimulationDatabase) -> None:
|
||||||
|
if database != self.stored_simulation_database():
|
||||||
|
self.undo_stack.push(ChangeSimulationDatabaseCommand(self, database))
|
||||||
|
|
||||||
|
def _set_simulation_database(self, database: SimulationDatabase | None) -> None:
|
||||||
|
if database is None:
|
||||||
|
if self.model.metadata is not None:
|
||||||
|
self.model.metadata.pop("simulation_database", None)
|
||||||
|
else:
|
||||||
|
if self.model.metadata is None:
|
||||||
|
self.model.metadata = {}
|
||||||
|
self.model.metadata["simulation_database"] = deepcopy(database)
|
||||||
|
self.simulation_database_changed.emit(self.stored_simulation_database())
|
||||||
|
|
||||||
|
def _simulation_database(self, create: bool) -> SimulationDatabase | None:
|
||||||
|
metadata = self.model.metadata
|
||||||
|
value = metadata.get("simulation_database") if metadata is not None else None
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = SimulationDatabase.from_data(value)
|
||||||
|
metadata["simulation_database"] = value
|
||||||
|
if isinstance(value, SimulationDatabase):
|
||||||
|
return value
|
||||||
|
if not create:
|
||||||
|
return None
|
||||||
|
if metadata is None:
|
||||||
|
metadata = {}
|
||||||
|
self.model.metadata = metadata
|
||||||
|
database = SimulationDatabase()
|
||||||
|
metadata["simulation_database"] = database
|
||||||
|
return database
|
||||||
|
|
||||||
def _set_component_icon(self, component_id: ComponentID, icon: Icon | None) -> None:
|
def _set_component_icon(self, component_id: ComponentID, icon: Icon | None) -> None:
|
||||||
if icon is None:
|
if icon is None:
|
||||||
database = self._icon_database(False)
|
database = self._icon_database(False)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
"""GUI document data stored inside the core document metadata field."""
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
@@ -12,6 +11,9 @@ from bedit_core.models import ComponentID, ID, PortID
|
|||||||
class ShapeID(ID):
|
class ShapeID(ID):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class SimulationID(ID):
|
||||||
|
pass
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Shape:
|
class Shape:
|
||||||
layer: int
|
layer: int
|
||||||
@@ -32,7 +34,6 @@ class Shape:
|
|||||||
def to_data(self) -> dict[str, Any]:
|
def to_data(self) -> dict[str, Any]:
|
||||||
return {"layer": self.layer, "type": self.type, "pos": list(self.pos)}
|
return {"layer": self.layer, "type": self.type, "pos": list(self.pos)}
|
||||||
|
|
||||||
|
|
||||||
class LineType(Enum):
|
class LineType(Enum):
|
||||||
NONE = "none"
|
NONE = "none"
|
||||||
SOLID = "solid"
|
SOLID = "solid"
|
||||||
@@ -40,7 +41,6 @@ class LineType(Enum):
|
|||||||
DOTTED = "dotted"
|
DOTTED = "dotted"
|
||||||
DASH_DOT = "dash_dot"
|
DASH_DOT = "dash_dot"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Line(Shape):
|
class Line(Shape):
|
||||||
type: str = field(init=False, default="line")
|
type: str = field(init=False, default="line")
|
||||||
@@ -58,7 +58,6 @@ class Line(Shape):
|
|||||||
def to_data(self) -> dict[str, Any]:
|
def to_data(self) -> dict[str, Any]:
|
||||||
return {**super().to_data(), "end": list(self.end), "line_type": self.line_type.value, "line_thickness": self.line_thickness, "line_color": self.line_color}
|
return {**super().to_data(), "end": list(self.end), "line_type": self.line_type.value, "line_thickness": self.line_thickness, "line_color": self.line_color}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Rectangle(Shape):
|
class Rectangle(Shape):
|
||||||
type: str = field(init=False, default="rectangle")
|
type: str = field(init=False, default="rectangle")
|
||||||
@@ -111,7 +110,6 @@ class Icon:
|
|||||||
def to_data(self) -> dict[str, Any]:
|
def to_data(self) -> dict[str, Any]:
|
||||||
return {"shapes": {str(key): shape.to_data() for key, shape in self.shapes.items()}, "port_positions": {str(key): list(position) for key, position in self.port_positions.items()}}
|
return {"shapes": {str(key): shape.to_data() for key, shape in self.shapes.items()}, "port_positions": {str(key): list(position) for key, position in self.port_positions.items()}}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class IconDatabase:
|
class IconDatabase:
|
||||||
format_version: int = 1
|
format_version: int = 1
|
||||||
@@ -124,3 +122,65 @@ class IconDatabase:
|
|||||||
|
|
||||||
def to_data(self) -> dict[str, Any]:
|
def to_data(self) -> dict[str, Any]:
|
||||||
return {"format_version": self.format_version, "icons": {str(key): icon.to_data() for key, icon in self.icons.items()}}
|
return {"format_version": self.format_version, "icons": {str(key): icon.to_data() for key, icon in self.icons.items()}}
|
||||||
|
|
||||||
|
class SimulationMethod(Enum):
|
||||||
|
DASSL = "dassl"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Simulation:
|
||||||
|
component: ComponentID
|
||||||
|
name: str
|
||||||
|
|
||||||
|
start_time: float
|
||||||
|
duration: float
|
||||||
|
use_timed_steps: bool
|
||||||
|
number_of_steps: int
|
||||||
|
step_size: float
|
||||||
|
|
||||||
|
method: SimulationMethod
|
||||||
|
|
||||||
|
dassl_tolerance: float
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Mapping[str, Any]) -> Simulation:
|
||||||
|
component = data.get("component")
|
||||||
|
use_timed_steps = data.get("use_timed_steps", False)
|
||||||
|
if isinstance(use_timed_steps, str):
|
||||||
|
use_timed_steps = use_timed_steps.lower() == "true"
|
||||||
|
return cls(
|
||||||
|
component=ComponentID(str(component)) if component else ComponentID(),
|
||||||
|
name=str(data.get("name", "")),
|
||||||
|
start_time=float(data.get("start_time", 0.0)),
|
||||||
|
duration=float(data.get("duration", 1.0)),
|
||||||
|
use_timed_steps=bool(use_timed_steps),
|
||||||
|
number_of_steps=int(data.get("number_of_steps", 500)),
|
||||||
|
step_size=float(data.get("step_size", 0.001)),
|
||||||
|
method=SimulationMethod(data.get("method", "dassl")),
|
||||||
|
dassl_tolerance=float(data.get("dassl_tolerance", 1e-6)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_data(self) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"component": str(self.component),
|
||||||
|
"name": self.name,
|
||||||
|
"start_time": self.start_time,
|
||||||
|
"duration": self.duration,
|
||||||
|
"use_timed_steps": self.use_timed_steps,
|
||||||
|
"number_of_steps": self.number_of_steps,
|
||||||
|
"step_size": self.step_size,
|
||||||
|
"method": self.method.value,
|
||||||
|
"dassl_tolerance": self.dassl_tolerance,
|
||||||
|
}
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SimulationDatabase:
|
||||||
|
format_version: int = 1
|
||||||
|
simulations: dict[SimulationID, Simulation] = field(default_factory=dict)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Mapping[str, Any]) -> SimulationDatabase:
|
||||||
|
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)
|
||||||
|
|
||||||
|
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()}}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
|||||||
from bedit_core.models import Document
|
from bedit_core.models import Document
|
||||||
from bedit_core.serialization import load as load_document
|
from bedit_core.serialization import load as load_document
|
||||||
from bedit_core.serialization import save as save_document
|
from bedit_core.serialization import save as save_document
|
||||||
from bedit_gui.models import IconDatabase
|
from bedit_gui.models import IconDatabase, SimulationDatabase
|
||||||
|
|
||||||
|
|
||||||
def load(path: str | Path) -> Document:
|
def load(path: str | Path) -> Document:
|
||||||
@@ -14,6 +14,8 @@ def load(path: str | Path) -> Document:
|
|||||||
document = load_document(path)
|
document = load_document(path)
|
||||||
if document.metadata is not None and isinstance(document.metadata.get("icon_database"), dict):
|
if document.metadata is not None and isinstance(document.metadata.get("icon_database"), dict):
|
||||||
document.metadata["icon_database"] = IconDatabase.from_data(document.metadata["icon_database"])
|
document.metadata["icon_database"] = IconDatabase.from_data(document.metadata["icon_database"])
|
||||||
|
if document.metadata is not None and isinstance(document.metadata.get("simulation_database"), dict):
|
||||||
|
document.metadata["simulation_database"] = SimulationDatabase.from_data(document.metadata["simulation_database"])
|
||||||
return document
|
return document
|
||||||
|
|
||||||
|
|
||||||
@@ -22,4 +24,6 @@ def save(document: Document, path: str | Path) -> None:
|
|||||||
saved_document = deepcopy(document)
|
saved_document = deepcopy(document)
|
||||||
if saved_document.metadata is not None and isinstance(saved_document.metadata.get("icon_database"), IconDatabase):
|
if saved_document.metadata is not None and isinstance(saved_document.metadata.get("icon_database"), IconDatabase):
|
||||||
saved_document.metadata["icon_database"] = saved_document.metadata["icon_database"].to_data()
|
saved_document.metadata["icon_database"] = saved_document.metadata["icon_database"].to_data()
|
||||||
|
if saved_document.metadata is not None and isinstance(saved_document.metadata.get("simulation_database"), SimulationDatabase):
|
||||||
|
saved_document.metadata["simulation_database"] = saved_document.metadata["simulation_database"].to_data()
|
||||||
save_document(saved_document, path)
|
save_document(saved_document, path)
|
||||||
|
|||||||
@@ -74,9 +74,20 @@
|
|||||||
</property>
|
</property>
|
||||||
<addaction name="actionAbout_QT"/>
|
<addaction name="actionAbout_QT"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuSimulation">
|
||||||
|
<property name="title">
|
||||||
|
<string>Simulation</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionSimulation_Settings"/>
|
||||||
|
<addaction name="actionEdit_Parameters"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionCompile_Model"/>
|
||||||
|
<addaction name="actionOpen_Simulation_Window"/>
|
||||||
|
</widget>
|
||||||
<addaction name="menuFile"/>
|
<addaction name="menuFile"/>
|
||||||
<addaction name="menuEdit"/>
|
<addaction name="menuEdit"/>
|
||||||
<addaction name="menuView"/>
|
<addaction name="menuView"/>
|
||||||
|
<addaction name="menuSimulation"/>
|
||||||
<addaction name="menuHelp"/>
|
<addaction name="menuHelp"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QStatusBar" name="statusbar"/>
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
@@ -153,6 +164,21 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</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="actionSimulation_Settings"/>
|
||||||
|
<addaction name="actionEdit_Parameters"/>
|
||||||
|
<addaction name="actionCompile_Model"/>
|
||||||
|
<addaction name="actionOpen_Simulation_Window"/>
|
||||||
|
</widget>
|
||||||
<action name="actionOpen_File">
|
<action name="actionOpen_File">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../../resources/resources.qrc">
|
<iconset resource="../../resources/resources.qrc">
|
||||||
@@ -386,6 +412,54 @@
|
|||||||
<enum>QAction::MenuRole::NoRole</enum>
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionSimulation_Settings">
|
||||||
|
<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 Settings</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionEdit_Parameters">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/view-form-table.png</normaloff>:/icons/icons/view-form-table.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Edit Parameters</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionCompile_Model">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/run-build.png</normaloff>:/icons/icons/run-build.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Compile Model</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionOpen_Simulation_Window">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/office-chart-line.png</normaloff>:/icons/icons/office-chart-line.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Open Simulation Window</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../resources/resources.qrc"/>
|
<include location="../../resources/resources.qrc"/>
|
||||||
|
|||||||
258
src/bedit_gui/ui/forms/simulation_settings.ui
Normal file
258
src/bedit_gui/ui/forms/simulation_settings.ui
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>562</width>
|
||||||
|
<height>451</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="simulationListLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="simulationList"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="simulationButtonLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="addSimulationButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="removeSimulationButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QFrame" name="frame">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::Shape::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Shadow::Raised</enum>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="basicForm">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="nameLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="componentLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Component:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="nameEdit"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="componentCompoBox"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="line">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="timeForm">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="startTimeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Start time:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="simLengthLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Simulation length:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QDoubleSpinBox" name="startTimeSpinBox">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> s</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QDoubleSpinBox" name="simLengthSpinBox">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> s</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="stepSizeButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Step size</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="nrOfStepsButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Number of steps</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="stepSizeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Step size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="stepLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QDoubleSpinBox" name="stepSizeSpinBox">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> s</string>
|
||||||
|
</property>
|
||||||
|
<property name="decimals">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.001000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QSpinBox" name="nrOfStepsSpinBox"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="nrOfStepsLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Number of steps:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QComboBox" name="simulationMethodComboBox"/>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="simMethodLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Simulation method:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="line_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="dasslForm">
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="toleranceEdit">
|
||||||
|
<property name="text">
|
||||||
|
<string>1e-06</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="toleranceLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Tolerance:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
195
src/bedit_gui/views/dialogs/simulation_settings_dialog.py
Normal file
195
src/bedit_gui/views/dialogs/simulation_settings_dialog.py
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
from PySide6.QtGui import QDoubleValidator
|
||||||
|
from PySide6.QtWidgets import QDialog, QListWidgetItem, QMessageBox, QWidget
|
||||||
|
|
||||||
|
from bedit_core.models import ComponentID
|
||||||
|
from bedit_gui.models import Simulation, SimulationDatabase, SimulationID, SimulationMethod
|
||||||
|
from bedit_gui.ui.generated.ui_simulation_settings import Ui_Dialog
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationSettingsDialog(QDialog):
|
||||||
|
"""Editor for the detached simulation database of a document."""
|
||||||
|
|
||||||
|
def __init__(self, database: SimulationDatabase, components: list[tuple[ComponentID, str]], parent: QWidget | None = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.ui = Ui_Dialog()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
self.setWindowTitle("Simulation Settings")
|
||||||
|
self._database = deepcopy(database)
|
||||||
|
self._simulation_ids = list(self._database.simulations)
|
||||||
|
self._components = components
|
||||||
|
self._loading = False
|
||||||
|
|
||||||
|
self.ui.startTimeSpinBox.setRange(-1e12, 1e12)
|
||||||
|
self.ui.simLengthSpinBox.setRange(0.0, 1e12)
|
||||||
|
self.ui.stepSizeSpinBox.setRange(1e-9, 1e12)
|
||||||
|
self.ui.nrOfStepsSpinBox.setRange(1, 1_000_000_000)
|
||||||
|
tolerance_validator = QDoubleValidator(0.0, 1e12, 16, self)
|
||||||
|
tolerance_validator.setNotation(QDoubleValidator.Notation.ScientificNotation)
|
||||||
|
self.ui.toleranceEdit.setValidator(tolerance_validator)
|
||||||
|
for component_id, path in components:
|
||||||
|
self.ui.componentCompoBox.addItem(path, str(component_id))
|
||||||
|
for method in SimulationMethod:
|
||||||
|
self.ui.simulationMethodComboBox.addItem(method.value.upper(), method.value)
|
||||||
|
|
||||||
|
self.ui.simulationList.currentRowChanged.connect(self._selection_changed)
|
||||||
|
self.ui.addSimulationButton.clicked.connect(self._add_simulation)
|
||||||
|
self.ui.removeSimulationButton.clicked.connect(self._remove_simulation)
|
||||||
|
self.ui.nameEdit.textEdited.connect(self._form_changed)
|
||||||
|
self.ui.componentCompoBox.currentIndexChanged.connect(self._form_changed)
|
||||||
|
self.ui.startTimeSpinBox.valueChanged.connect(self._form_changed)
|
||||||
|
self.ui.simLengthSpinBox.valueChanged.connect(self._form_changed)
|
||||||
|
self.ui.stepSizeButton.toggled.connect(self._form_changed)
|
||||||
|
self.ui.nrOfStepsButton.toggled.connect(self._form_changed)
|
||||||
|
self.ui.stepSizeSpinBox.valueChanged.connect(self._form_changed)
|
||||||
|
self.ui.nrOfStepsSpinBox.valueChanged.connect(self._form_changed)
|
||||||
|
self.ui.simulationMethodComboBox.currentIndexChanged.connect(self._form_changed)
|
||||||
|
self.ui.toleranceEdit.textEdited.connect(self._form_changed)
|
||||||
|
|
||||||
|
self._rebuild_list()
|
||||||
|
|
||||||
|
def database(self) -> SimulationDatabase:
|
||||||
|
return deepcopy(self._database)
|
||||||
|
|
||||||
|
def accept(self) -> None:
|
||||||
|
for simulation in self._database.simulations.values():
|
||||||
|
if not simulation.name.strip():
|
||||||
|
QMessageBox.warning(self, "Invalid simulation", "Every simulation must have a name.")
|
||||||
|
return
|
||||||
|
if simulation.component not in {component_id for component_id, _ in self._components}:
|
||||||
|
QMessageBox.warning(self, "Invalid simulation", f"Select an existing component for {simulation.name}.")
|
||||||
|
return
|
||||||
|
if simulation.dassl_tolerance <= 0:
|
||||||
|
QMessageBox.warning(self, "Invalid simulation", "The DASSL tolerance must be greater than zero.")
|
||||||
|
return
|
||||||
|
simulation.name = simulation.name.strip()
|
||||||
|
super().accept()
|
||||||
|
|
||||||
|
def _rebuild_list(self, selected_id: SimulationID | None = None) -> None:
|
||||||
|
self.ui.simulationList.clear()
|
||||||
|
for simulation_id in self._simulation_ids:
|
||||||
|
item = QListWidgetItem(self._database.simulations[simulation_id].name)
|
||||||
|
item.setData(Qt.ItemDataRole.UserRole, simulation_id)
|
||||||
|
self.ui.simulationList.addItem(item)
|
||||||
|
if self._simulation_ids:
|
||||||
|
if selected_id not in self._database.simulations:
|
||||||
|
selected_id = self._simulation_ids[0]
|
||||||
|
self.ui.simulationList.setCurrentRow(self._simulation_ids.index(selected_id))
|
||||||
|
else:
|
||||||
|
self._set_editor_enabled(False)
|
||||||
|
|
||||||
|
def _selection_changed(self, row: int) -> None:
|
||||||
|
simulation_id = self._simulation_id(row)
|
||||||
|
self._set_editor_enabled(simulation_id is not None)
|
||||||
|
self.ui.removeSimulationButton.setEnabled(simulation_id is not None)
|
||||||
|
if simulation_id is not None:
|
||||||
|
self._load_simulation(self._database.simulations[simulation_id])
|
||||||
|
|
||||||
|
def _simulation_id(self, row: int | None = None) -> SimulationID | None:
|
||||||
|
if row is None:
|
||||||
|
row = self.ui.simulationList.currentRow()
|
||||||
|
if row < 0 or row >= len(self._simulation_ids):
|
||||||
|
return None
|
||||||
|
return self._simulation_ids[row]
|
||||||
|
|
||||||
|
def _load_simulation(self, simulation: Simulation) -> None:
|
||||||
|
self._loading = True
|
||||||
|
self.ui.nameEdit.setText(simulation.name)
|
||||||
|
component_index = self.ui.componentCompoBox.findData(str(simulation.component))
|
||||||
|
if component_index < 0:
|
||||||
|
self.ui.componentCompoBox.addItem(f"Missing component ({simulation.component})", str(simulation.component))
|
||||||
|
component_index = self.ui.componentCompoBox.count() - 1
|
||||||
|
self.ui.componentCompoBox.setCurrentIndex(component_index)
|
||||||
|
self.ui.startTimeSpinBox.setValue(simulation.start_time)
|
||||||
|
self.ui.simLengthSpinBox.setValue(simulation.duration)
|
||||||
|
self.ui.stepSizeButton.setChecked(simulation.use_timed_steps)
|
||||||
|
self.ui.nrOfStepsButton.setChecked(not simulation.use_timed_steps)
|
||||||
|
self.ui.stepSizeSpinBox.setValue(simulation.step_size)
|
||||||
|
self.ui.nrOfStepsSpinBox.setValue(simulation.number_of_steps)
|
||||||
|
self.ui.simulationMethodComboBox.setCurrentIndex(self.ui.simulationMethodComboBox.findData(simulation.method.value))
|
||||||
|
self.ui.toleranceEdit.setText(str(simulation.dassl_tolerance))
|
||||||
|
self._loading = False
|
||||||
|
self._update_step_inputs()
|
||||||
|
|
||||||
|
def _form_changed(self, *_args: object) -> None:
|
||||||
|
if self._loading:
|
||||||
|
return
|
||||||
|
simulation_id = self._simulation_id()
|
||||||
|
if simulation_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._update_step_inputs()
|
||||||
|
simulation = self._database.simulations[simulation_id]
|
||||||
|
try:
|
||||||
|
tolerance = float(self.ui.toleranceEdit.text())
|
||||||
|
except ValueError:
|
||||||
|
tolerance = simulation.dassl_tolerance
|
||||||
|
component_data = self.ui.componentCompoBox.currentData()
|
||||||
|
component = ComponentID(component_data) if isinstance(component_data, str) and component_data else simulation.component
|
||||||
|
method_data = self.ui.simulationMethodComboBox.currentData()
|
||||||
|
method = SimulationMethod(method_data) if isinstance(method_data, str) else simulation.method
|
||||||
|
self._database.simulations[simulation_id] = Simulation(
|
||||||
|
component=component,
|
||||||
|
name=self.ui.nameEdit.text(),
|
||||||
|
start_time=self.ui.startTimeSpinBox.value(),
|
||||||
|
duration=self.ui.simLengthSpinBox.value(),
|
||||||
|
use_timed_steps=self.ui.stepSizeButton.isChecked(),
|
||||||
|
number_of_steps=self.ui.nrOfStepsSpinBox.value(),
|
||||||
|
step_size=self.ui.stepSizeSpinBox.value(),
|
||||||
|
method=method,
|
||||||
|
dassl_tolerance=tolerance,
|
||||||
|
)
|
||||||
|
self.ui.simulationList.item(self.ui.simulationList.currentRow()).setText(self.ui.nameEdit.text())
|
||||||
|
|
||||||
|
def _add_simulation(self) -> None:
|
||||||
|
if not self._components:
|
||||||
|
return
|
||||||
|
simulation_id = SimulationID()
|
||||||
|
simulation = Simulation(
|
||||||
|
component=self._components[0][0],
|
||||||
|
name=self._unique_name("Simulation"),
|
||||||
|
start_time=0.0,
|
||||||
|
duration=1.0,
|
||||||
|
use_timed_steps=False,
|
||||||
|
number_of_steps=500,
|
||||||
|
step_size=0.001,
|
||||||
|
method=SimulationMethod.DASSL,
|
||||||
|
dassl_tolerance=1e-6,
|
||||||
|
)
|
||||||
|
self._database.simulations[simulation_id] = simulation
|
||||||
|
self._simulation_ids.append(simulation_id)
|
||||||
|
self._rebuild_list(simulation_id)
|
||||||
|
|
||||||
|
def _remove_simulation(self) -> None:
|
||||||
|
simulation_id = self._simulation_id()
|
||||||
|
if simulation_id is None:
|
||||||
|
return
|
||||||
|
row = self._simulation_ids.index(simulation_id)
|
||||||
|
del self._database.simulations[simulation_id]
|
||||||
|
self._simulation_ids.remove(simulation_id)
|
||||||
|
selected = self._simulation_ids[min(row, len(self._simulation_ids) - 1)] if self._simulation_ids else None
|
||||||
|
self._rebuild_list(selected)
|
||||||
|
|
||||||
|
def _unique_name(self, base: str) -> str:
|
||||||
|
names = {simulation.name for simulation in self._database.simulations.values()}
|
||||||
|
if base not in names:
|
||||||
|
return base
|
||||||
|
index = 2
|
||||||
|
while f"{base} {index}" in names:
|
||||||
|
index += 1
|
||||||
|
return f"{base} {index}"
|
||||||
|
|
||||||
|
def _update_step_inputs(self) -> None:
|
||||||
|
timed = self.ui.stepSizeButton.isChecked()
|
||||||
|
self.ui.stepSizeSpinBox.setEnabled(timed)
|
||||||
|
self.ui.nrOfStepsSpinBox.setEnabled(not timed)
|
||||||
|
|
||||||
|
def _set_editor_enabled(self, enabled: bool) -> None:
|
||||||
|
self.ui.frame.setEnabled(enabled)
|
||||||
|
self.ui.removeSimulationButton.setEnabled(enabled)
|
||||||
|
self.ui.addSimulationButton.setEnabled(bool(self._components))
|
||||||
@@ -588,6 +588,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"simulation_database": {
|
||||||
|
"format_version": 1,
|
||||||
|
"simulations": {
|
||||||
|
"7778ca68-7a36-401b-b4aa-236a44c5e771": {
|
||||||
|
"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