Compare commits

...

4 Commits

Author SHA1 Message Date
48ac9e2f59 proper modelica text model editing 2026-07-21 17:16:06 +02:00
2f6487e510 Added direct graph navigation without activating Matplotlib toolbar modes:
Mouse wheel zooms in and out around the cursor.
Left-click dragging pans the graph.
Matplotlib toolbar modes still work and take precedence when activated.
The existing Home button can reset the view.
2026-07-21 15:29:58 +02:00
eb8f77ce64 better signal graphing 2026-07-21 15:24:24 +02:00
35d933ccbb Added signal graphing 2026-07-21 15:14:12 +02:00
24 changed files with 1617 additions and 142 deletions

View File

@@ -115,6 +115,14 @@
"options": {"cwd": "${workspaceFolder}"}, "options": {"cwd": "${workspaceFolder}"},
"problemMatcher": [] "problemMatcher": []
}, },
{
"label": "Qt: Compile Simulation Window UI to Python",
"type": "shell",
"command": "pyside6-uic",
"args": ["--from-imports", "${workspaceFolder}/ui/simulation_window.ui", "-o", "${workspaceFolder}/src/bedit/gui/generated/ui_simulation_window.py"],
"options": {"cwd": "${workspaceFolder}"},
"problemMatcher": []
},
{ {
"label": "Qt: Build Designer Files", "label": "Qt: Build Designer Files",
"dependsOrder": "sequence", "dependsOrder": "sequence",
@@ -125,7 +133,8 @@
"Qt: Compile Component Options UI to Python", "Qt: Compile Component Options UI to Python",
"Qt: Compile Port Options UI to Python", "Qt: Compile Port Options UI to Python",
"Qt: Compile Shape Options UI to Python", "Qt: Compile Shape Options UI to Python",
"Qt: Compile Icon Editor UI to Python" "Qt: Compile Icon Editor UI to Python",
"Qt: Compile Simulation Window UI to Python"
], ],
"problemMatcher": [], "problemMatcher": [],
"group": { "group": {

View File

@@ -104,6 +104,11 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
named BEdit `$name$` macro completions editable there; arbitrary `$name$` named BEdit `$name$` macro completions editable there; arbitrary `$name$`
expressions are highlighted as BEvalues. Highlight colors and bold/italic expressions are highlighted as BEvalues. Highlight colors and bold/italic
styles are persisted under `syntax/<category>/` in application settings. styles are persisted under `syntax/<category>/` in application settings.
- Text component sources may contain private Modelica declarations in
`source.declarations`. The text-definition editor exposes declarations above
`source.initialEquations` and `source.equations`, with the same highlighting
and completion in all three fields. The composer emits each in its matching
Modelica section.
- Application-wide messages use `core.application_log.get_logger()`. The main - Application-wide messages use `core.application_log.get_logger()`. The main
window installs the Qt log-panel handler; core code must only use standard window installs the Qt log-panel handler; core code must only use standard
Python logging and must not import the GUI handler. Python logging and must not import the GUI handler.
@@ -116,6 +121,8 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
per component instance from graph connections and exposed while compiling as per component instance from graph connections and exposed while compiling as
`$portname_N$`; array connection endpoints receive stable one-based indices in `$portname_N$`; array connection endpoints receive stable one-based indices in
graph connection order. graph connection order.
- Simulation → Export Model composes through `Simulation.compose_source()` without
building the model, then writes the generated source as a `.mo` file.
- OpenModelica integration belongs in `core/simulation/openmodelica.py`. Its - OpenModelica integration belongs in `core/simulation/openmodelica.py`. Its
persistent worker and OMC session start lazily on the first queued request. persistent worker and OMC session start lazily on the first queued request.
Never perform OMPython work directly on the Qt GUI thread. Result and error Never perform OMPython work directly on the Qt GUI thread. Result and error
@@ -131,6 +138,8 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
progress, log, and future result views. Extend graph presentation through its progress, log, and future result views. Extend graph presentation through its
Designer-owned `resultsLayout` and the Designer-owned `resultsLayout` and the
`clear_result_views()`/`load_result_views()` hooks. `clear_result_views()`/`load_result_views()` hooks.
- Simulation-window geometry, dock/toolbar state, and central-results visibility
persist under `simulationWindow/` through `application_settings()`.
- Standalone simulation results are modeled in `core/simulation/results.py`. - Standalone simulation results are modeled in `core/simulation/results.py`.
Its versioned schema retains model status, messages, metadata, and plottable Its versioned schema retains model status, messages, metadata, and plottable
traces so the simulation window can open results without an active document. traces so the simulation window can open results without an active document.
@@ -139,6 +148,22 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
- After a successful OpenModelica run, `<model>_res.csv` is parsed on the worker - After a successful OpenModelica run, `<model>_res.csv` is parsed on the worker
before temporary-directory cleanup. `SimulationResults.data` stores every CSV before temporary-directory cleanup. `SimulationResults.data` stores every CSV
column as a numeric array, including `time`, for later plotting and persistence. column as a numeric array, including `time`, for later plotting and persistence.
- The simulation window's dockable Signals tree derives hierarchy from dot-separated
result-column names and bracketed array indices (`a[1]` becomes `a → 1`). Leaf
items retain the exact full column name in `UserRole`; plotting code should
consume `SimulationWindow.selected_signal_names()`.
- Simulation graph tabs persist as `SimulationResults.graphs`; every graph has a
stable ID, editable title, and its own `traces` list. Runtime graph widgets belong
in `GraphWorkspacePage.plot_layout`, not in the serialized core model. The
Signals tree checkboxes edit the active graph's traces, and each page embeds a
Matplotlib QtAgg canvas. Each graph persists its own `x_axis` signal (default
`time`), selectable from the Signals tree context menu.
- Embedded Matplotlib canvases provide cursor-centered wheel zoom and direct
left-button drag panning without activating navigation-toolbar modes. Their
custom navigation toolbar restores an explicit data-derived home view.
- Rerunning the same composed model retains graph tabs, ordering, titles, X axes,
and surviving trace settings while replacing numeric data. Missing signals are
pruned from traces/X-axis selection and newly returned columns appear in the tree.
- The optional OpenModelica executable is persisted as - The optional OpenModelica executable is persisted as
`simulation/openModelicaPath`. The GUI passes it into `Simulation`; core must `simulation/openModelicaPath`. The GUI passes it into `Simulation`; core must
not read `QSettings`. An empty path uses OMPython/PATH discovery, while an not read `QSettings`. An empty path uses OMPython/PATH discovery, while an

View File

@@ -9,6 +9,7 @@ description = "A starter Qt desktop application"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
"matplotlib>=3.8,<4",
"msgpack>=1.0,<2", "msgpack>=1.0,<2",
"PySide6>=6.7,<7", "PySide6>=6.7,<7",
"OMPython>4.0", "OMPython>4.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,5 +1,7 @@
<RCC> <RCC>
<qresource prefix="icons"> <qresource prefix="icons">
<file>icons/list-remove.png</file>
<file>icons/list-add.png</file>
<file>icons/view-form-table.png</file> <file>icons/view-form-table.png</file>
<file>icons/office-chart-line.png</file> <file>icons/office-chart-line.png</file>
<file>icons/run-build.png</file> <file>icons/run-build.png</file>

View File

@@ -862,6 +862,75 @@ N\xa7\xd3>==\xfd\xa1\xc3\xe1\x98\x92$\xe9I\x00\
\xbf\xc9\xd3\x01\x9f\xa3\x0f\x17Z/\xe3\x7f\xe1/\x17\x85\ \xbf\xc9\xd3\x01\x9f\xa3\x0f\x17Z/\xe3\x7f\xe1/\x17\x85\
\xd6\x06q(\x0e\x10\x00\x00\x00\x00IEND\xaeB\ \xd6\x06q(\x0e\x10\x00\x00\x00\x00IEND\xaeB\
`\x82\ `\x82\
\x00\x00\x04)\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\
\xa7\x93\x00\x00\x00\x09pHYs\x00\x007\x5c\x00\x00\
7\x5c\x01\xcb\xc7\xa4\xb9\x00\x00\x03\x9bIDATx\
\xda\xedT=o\x1bG\x10}\xbb\xb7w$\xf5A\x9e\
lJEB\x18\xb1\x0a\x1bv\x13\xdb\x85\xaa\x18P$\
\xa4\x89\x81\x94\xd6_\x08`\xb8r\x93 \x85\x02\x01\xa9\
\x9c\x1f\xe0>\x85\x81\x94F\x00\xa7\x11\xd88\x85\x1bK\
6@)\x94-\xc8\x96\x10\x18\x81\x22\xf3C\x22i\xf2\
\xf6v\xf3B.s\xd1\xb1H\x80\xb83\x07x\x98\xbd\
\xdd\x99y3\xb3\xb7\x83\xb1\x8ce,\xef\xbd\x88\x1f\x97\
\x97\xe1\x05\x01\x820D~~\x1e\xdb\xf7\xee\xa1st\
\x94\x188H\x07/\x05\xe5\xe0\x0f\xb5C\xf0\xb7N\xd6\
\xb9\xb3g\x11\xde\xba\x85\xf8\xd5+\xc4\xb5\x1a`\x0c\xc4\
\x83B\x01\x82\xe4\xe1\xcd\x9b\xf2\x8f'Ol.\x9f\xb7\
\x96\x07\xd1\xc9\x09\xbeX_\xc7\xbb\x90\xd7\xd7\xafCN\
M\x01RB7\x9bbraA4\xee\xdf7q\xa3\
\x01\xf1\xc3\xf9\xf3\xf0\xc3P\xac\x90\xfc\xd1\xda\x9a|v\
\xf7\xee4\xc9\xbdw\xda\x01\x87\xec\xf4t<\xb7\xba\xda\
\xcc\xdc\xb9c\xebW\xae\x88\xb8^\xb7\xa2|\xfb\xb6\xf8\
\xe0\xeaU\xdbl4>?\xda\xd8\xf8\xf6\xa0\x5c\xfe\xa8\
yp \xff=\x01G\x9eN\x22!\x1fM\xa0T2\
S\x8b\x8b{\xb9\x85\x85U\xe4r?G\x9b\x9bB\xd5\
\xaaU[\x7f\xfe|)\xbcp\xe1Avv\xd6\x93a\
h\xdb\x07\x07B\xfc\xe7\x04\x122\xe3`\x07p~\x09\
\x04c\xa3X\x9c\xd5;;?\x89\xdd\xdd\xcf\xa41e\
e\x94B\xb3Z\xfd\xca\xfa\xbeWZZ\x8a>^Y\
Q\xf3\xec\x88\xa7\x94#'\xac\x85L\x05\x1c\x09\x9e\xe8\
S~\x88cxR\x22\xc8\xe7\xa1\xe7\xe6`\x84\x88\xf4\
\xe3\xc7\xbe\xa8V\xbf6\x17/\x96\xd5\xeb\x87\x0f\x0bZ\
\xa9K\xad(\xc2\x87\xd7\xaey\xc1\x8b\x17b\xe6\xf8\x18\
\xca\xf3\xe0Y\x9bT<B\x98\x00B\x0c\xaa\xa6\xbd!\
bc\xfa\xdad2\x00\x89\xdbL`\xfb\xe9S\xfc\xba\
\xb5\x85Oo\xdc\xf0J\x95\x0a\xec\xfe\xfe\xa5\xdc\xcb\x97\
\x05\x15[\xabzQ\xe4\xd7\xf6\xf7qD\x833$\xb7\
LF1\xa8JZ\x9dJ\xc2\x81$\x18VJX\x12\
\x81\xa4rr\x12&\x9bE\xa7\xd3\xc1\xef\x9b\x9b\xf8m\
{\x1b\xf5\xb7o\xd1e\xcc\xe3j\x15zo\x0f\xf4\xf1\
\xd9\x0d\xa5\x0c\x00\x87~\xe6\xacf\x00\x0a\x83\x9e\xd6\xff\
l=\xc9$\xbb$\x95\x02\x82\x00\xa0\x8e\xe9\xd7k\xb7\
\xd1\x22A\x83o\xbd~x\x88\xb6\xf3\xcb\xf2\xacEm\
\xc8A\xe2~\xd2\xc6\x15\xa7\xb9\x88b\x00o8\x1c\xba\
t\x0a\xba]L\xb1\x82\xac\xef#\xabT\x9f\xcc#\x04\
\x1d\xe5\x10\x0c \xb4\x86\xe9\xf5`834\xdft\xaf\
^G\xf7\xafJ\x1di\x86v\x9a~]\xdaj\x12c\
b\x02\xbe\xbb\x22IN^\x99V\x87B4&\xac\xad\
\x00(\xedll\xe8\xe2\xb9s\xbeGbEC\x9fW\
\xe1\xc71|!\x86W\xd2\xdfW\x0c\xe0q_\x11\xc3\
\xffD\xce\xcc@\x16\x8b\x10\x1e\xbf\x5c\x85Y\x22\x00P\
\xa0\xd6\xd4>\x8b9\xb3\xbb\xab\xa3A\xcf*5r\x8b\
5\xf4\xe5\x93\x0e\xb0~\xc2\x03\x1eZ#%\xfd\x93\xb6\
\xb3\xda\xd3O\x8f\x04\xbe\xd3\xa7\xde<\xed2\xc0\x08\xb2\
\x0e\x19\x06\xa5\xbf\xe0\xbaG\xbfe\x00\x8f\xd4174\
\x17\x0c\xb0\x04\xe0;\x03\x5cf\x8b\xa4M?\xad\xd4\xcf\
G\x0dV\x9f\x9e\x0f\xce~ v\xf8o\x11zpn\
\x14\xb0\xc5\xbdo\xda\xc0/\x92\xa6j\xd2\xcd\x8c<7\
\x9e\x01\x8b4\xca\xd3X%\x09$\xc3%\x91\xd1\xa4b\
\x87\xd40J\xdbk\xee5\xc9\x09=\xd8\xb2\xea\x8dK\
\xb6\x05\xc8\x90Z\xd0\x00\xceY\x11\xdf\xe3\xffI%\xf5\
mI\xdc\x224`\xfaE~\xe9\x0e\xa4\xd3=B\xa7\
F\xe9\xe8\xe8\x1dE\x90B&\xd1)\x0c\xc4`,c\
\x19\xcbX\x06\xf2'\xe3\xdf\x9d\x06\x06\xf6\x92\x0e\x00\x00\
\x00\x22zTXtSoftware\x00\x00\
x\xda+//\xd7\xcb\xcc\xcb.NN,H\xd5\xcb\
/J\x07\x006\xd8\x06X\x10S\xca\x5c\x00\x00\x00\x00\
IEND\xaeB`\x82\
\x00\x00\x02V\ \x00\x00\x02V\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -2383,6 +2452,101 @@ u1\xb8\xdd0`x\xe8F\xf9\x1e\xe19\xea\x8a\x03\
|f\x1e\x89\xd1\x9ef\xbdhx\x0fE4mL\xaf\ |f\x1e\x89\xd1\x9ef\xbdhx\x0fE4mL\xaf\
b/B\x06\x12o\xbf\x00\xa3\x17WYZq\xd9W\ b/B\x06\x12o\xbf\x00\xa3\x17WYZq\xd9W\
\x00\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x05\xcf\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
\x00\x00\x00\x09pHYs\x00\x00\x03v\x00\x00\x03v\
\x01}\xd5\x82\xcc\x00\x00\x00\x19tEXtSof\
tware\x00www.inksca\
pe.org\x9b\xee<\x1a\x00\x00\x05LID\
ATx\xda\xc5\x97\xdboTE\x1c\xc7\xbf3s\xce\
\x9e\xeeni\x0b\xbd@\x01C1\x05B\x10\x0d\xf4\x22\
\x18\x95D0i\xc4\x17|21\xc6\x17\xc3\x93\x89\xd1\
\xc8\x8b\xfaP#oj\x8c\x89O\xfc\x03\xc6K\x0c\xf1\
\x01!B\xa8\xc1j\x88D\x90@)\x14\xac\xb5\x86K\
K\xaf\xdb\xdd={\xce\xcc\xef\xe7\xec\x9cficb\
\xd2\x98\x13f\xf3\xdd93\xbf\x93\x9d\xcf\xfengW\
03\x1e\xe6\xf0\xb0\xc2\xf1\xca\xe9\x17wok\xda~\
lh\xe6\xe2\xd6\xbf\x0a#\xab\x94\x92h\xcb\xad\x9d{\
z\xdd\xfe\x9b\xf9 \xf7\xce\xe1\xad\xef\x0d`\x05Cb\
\x05\xa3\xe7\xcb\xce\xc7\x0emz\xf9\xfc\xab\x9d\x87\xbb\xf6\
\xb4\xec[\x15k\x82\x8e\x09\x9d\xf9\xed\x8d\x07\xda\x0fu\
=\xd5\xd2w\xe6\x83K\x87{S\xf3@N\xe4>\x9d\
)M{>\xd5\xa1\xab\xad\x17\x1b\x1a\xda\xdd~V\xe6\
\xb1)\xb7\x05\xe7\xee\x9e\x94\xf3\xa5\xe2'\x00\x9eI\x05\
\xe0^a\xa2\xe7\xdc\xdf\x03\xe8\x5c\xb3\x05\xf3\xb9k\x10\
A\xc5\xed\x87\x98\xc4\x99\xdb\xdf\xe1\xd4\xd8\xb7\x18-\x0e\
\xefL\xcd\x03aTi\x1c\x1c\xff\x11\xdd\x1bw\xa3I\
\x99\xe5F3\x87+\xf7/\xc2W\xb215\x00\x133\
\x08\x11E(\xc7\xcb\x01<\xa3\x9d]2#5\x00\
\xd2\x04!$\x8c!\x94u\xb4\xccVG1\x8c6\x10\
@\x9a\x00\x0c\x08\x86!B\x18G\x80X\xea\x01\x03c\
\xed\x02\x94\x1e\x80Y\x04\xd0d`,\x80\x00j\x10\x01\
i\x18\xc3\x10\xd6\x9ej\x0e\x08\xc5 bD:^\xe6\
\x01M\xfa\xffy\xe0\xe8\xe0\xd1\x17\xaeN\x5c\xed\x1f\x18\
\x1d\xd869w\xafA\x92\x84d\xc0\xbeC\xb1\x80\x94\
\x02JHw81!2\x0e\xa0\xc6\xa0\x8dv9b\
\xd0ul\x03{\xbe\x84\xaf\x14<_\xc1\xf7\xecu\
F\xa15\xd76\xff\xc4\xda\x9e\xeb\x1d\xab\xb6\xf7\xbf\xb4\
\xf9\xf5\x13\xb5N\xd8\xf7E_\xefTi\xea\xf8\xc6\xfa\
\x8d=mA[\x03\x99$\xdel\xd8\xcd\xb48\x1bw\
M\xd0\xdaX\x00\x8d\xd8\x98%\x8a@D\xee^c\xc8\
\xddk(\xb9\xdfp\xb2W\xef564\xaa\xd6\x9e\xf9\
h\xea\xf8G\xc3o\xb8\x8e\xe9\x1eF\x1d\x9fw\xfc\xb0\
\xabm\xd7\x81\xbd\xeb\xf7\x02\xc2`.\x9aAu\x9f\x85\
\x15\x5c\xdc\x935\x0c\x84\x00r\xb9,\xee\x9a?!\x93\
$p{\x1b\xbcGQ\xa9\x84P\xd2\x83'\xfd\x9a\x94\
\xf2\x90\xb1{\x81\xca\xc2g\xcf\xda\x0d\xc6\xc2!\xdc\x89\
n\x9e\xf9\xec\xc9\xef\x0f\xb8\x10\x8c\xcf\x8dw#\x06z\
\xda\xbb0T\xb8\x00-4 Q\x93\x84\x80\x14\x02J\
J7\xcb(Y\x0b'\xc7\x87\xb1\xe8\x06X\x00L\x9c\
x\x81\xaa\x22\x042\x8b\xbcX\x8d\x91\x89Q\x9c\x1a:\
\x8d\xf7\xf7\xbd\x85\xdb4\x8cyLv\xd5r\x80\x88\x9a\
\xc6\xa6\xc602u\x1dEQN\x0eVp\xb3\x10H\
\x0e\x96\x02\xcc\xb2\x06\xc2\xe2\x01Dm0\x83\x00x\xc2\
\xc3\xba\xecz\xcc\x95\x8b\xb81}\x0bgG\xbfF\x1c\
\x11\x00\xd8\xf50\x8a\xc1md|\xd9T\x03\x00[\x91\
k\xb5\xa8\xa88\x01 \x07\x91$\x1fK+\x07\x90\x1c\
\x0e\x86\xaa\x02A8\xefdT\x1d\x9a\x83V\x17\x8e0\
\x0a1\xb10\x81o\xfe\xf8\x0a\x85J\x11\xa5(\xb6\xa0\
\x12\x90\xe42V\xb3v\xf9@\x9e\xc0R\x80Y\x10\x9a\
f\x0a\xb3\xb8^\xbc\x09?\xf0\x9c\x82L\x80l\x10\xc0\
>\xe7\x91\xf7\xb2\x8eI2\x01,]G\x0cD\x00\x22\
\x8d\xf9h\x1a\xc3\x93\xbf!\xd4\x15h\x22D\xc6\x00\xd2\
\xc0S\xca\xca@\x19W.N\x19\xcf\xc3\x02\xbb\xf0\xcc\
.-\xc3A\x10\x0e\x0e\x0e\xff\x82\x1d\xebw .\xc5\
@9\x09A$\x04H\x85(\x8b\x18\x9e'\xdc\x87\xd6\
5H\xdc\xc1-\x04\x9erkea\x1e\xf1;Q\xd1\
\x06\xbc\x987\xe43L\x96\xa09)[cU\xefe\
1e\x93\x97du\x8f\x7f~\x00\xa0q\x04\xc0\xfe\x85\
\xf2B\xdd\xf9k\xe7\x81x1\x04\x9cP\x0b+)\x92\
^ \x94\xc0\xb3{z\xe1\xafV\xc8T3\xbc\x0a!\
%\xc2B\x8c\x9f.\xffj!%\xaa=@YyK\
\xe4\xfb\x0a\x85\x8cB&c\xd7\x19\xaf\xacY\x1f\xa9\xf5\
\x01\xfe\x90\xaf\x81\xd0\x03\xe0,\x08\x0b0@M\x1a`\
;SU\x94\x5ck\x13\xbbC}%\x1d@\xe0{\xae\
D\x99\x01\x02\xbb\xba\xa7\x7f\x89\xacx\xce\xdaN\xda\xb9\
\xfbD\xdf\xc8\xd0\xb2N\xc8\x1f\xf3\x15\x00\xcf\xfdg\xdb\
|M2\x0b\xd7\xf5\x9cG\xbc*\x80\xb2\x00\x9e\x87J\
r\x00$\x0b\x8c\xbd;#R\xf9MH&\xa9\xf3\x98\
b\xf0b|\x94L\xbc\x00h\x07@)\xfe\x1ep\x87\
3\x0bT\xe2\x18\xc2\x18\x18J\xbe\xb5X\xecF\xc9+\
E\x80\x04\x02(\xeb\x0a8\xd2P2\xb2J\x1aR1\
\xa2\xa4\x9d\xa4\x0e \x18\xa5(D\x14F\xae\xe6+\xb1\
F!\xac ,\xf9 k\xb3<)\x02\x88D\x0bq\
\x08*VP\xac\xc4\xae\x17\xb8\xf2,f\xc0\x92\xacd\
z\x00^F\x15\x1b\xb2A\xbeE\x05\xb8Q\x9aK\xda\
\xb4U\xf5\xb5)\xee@=1B.\x15R\xfbg\x94\
\xad\xf7\x7f\xdf\xd9\xde\x82\x83\xcdk\xb03n\x86\x9cR\
\xc0\xb4D\xb7\xea\xc5V\xde\x82<\xea!H^H\x0d\
\xdf\x1c\xbc\xb9ym\xa0W7*\xb4\xe6\xf3 c\
\xc0Dx\xa2\xf9q\xc8@Add$`\xdeN\x0d\
\xe0N\xff\xec\x05\xd9\x1c=?\xee\xf3\xe5\xfb\x22Z\xa0\
,\x83\xeb\x18*\xab\xc2 \x9b\xb9\x12\xa0n\xffD\x7f\
\xf1\x12V0\x1e\xfa\xdf\xf3\x7f\x00j\xf0\xda\xe8\xbc\xba\
\xd0\x0a\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x04<\ \x00\x00\x04<\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -2649,6 +2813,10 @@ qt_resource_name = b"\
\x0f\xba\x1e'\ \x0f\xba\x1e'\
\x00d\ \x00d\
\x00r\x00a\x00w\x00-\x00p\x00a\x00t\x00h\x00.\x00p\x00n\x00g\ \x00r\x00a\x00w\x00-\x00p\x00a\x00t\x00h\x00.\x00p\x00n\x00g\
\x00\x0f\
\x020\x8b\xe7\
\x00l\
\x00i\x00s\x00t\x00-\x00r\x00e\x00m\x00o\x00v\x00e\x00.\x00p\x00n\x00g\
\x00\x0d\ \x00\x0d\
\x0b\xe6\x1f\xa7\ \x0b\xe6\x1f\xa7\
\x00d\ \x00d\
@@ -2723,6 +2891,10 @@ qt_resource_name = b"\
\x00o\ \x00o\
\x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\ \x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\
\x00.\x00p\x00n\x00g\ \x00.\x00p\x00n\x00g\
\x00\x0c\
\x09\xc6\x19'\
\x00l\
\x00i\x00s\x00t\x00-\x00a\x00d\x00d\x00.\x00p\x00n\x00g\
\x00\x10\ \x00\x10\
\x03\xe6\xd3g\ \x03\xe6\xd3g\
\x00d\ \x00d\
@@ -2749,67 +2921,71 @@ qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1e\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x02\x00\x00\x00 \x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00N\xe8\ \x00\x00\x02\x22\x00\x00\x00\x00\x00\x01\x00\x00S\x15\
\x00\x00\x01\x9f{C\xf1'\ \x00\x00\x01\x9f{C\xf1'\
\x00\x00\x02>\x00\x00\x00\x00\x00\x01\x00\x00Z\x1a\ \x00\x00\x02b\x00\x00\x00\x00\x00\x01\x00\x00^G\
\x00\x00\x01\x9f\x7f\xa8\xa8)\ \x00\x00\x01\x9f\x7f\xa8\xa8)\
\x00\x00\x02\xec\x00\x00\x00\x00\x00\x01\x00\x00tV\ \x00\x00\x03\x10\x00\x00\x00\x00\x00\x01\x00\x00x\x83\
\x00\x00\x01\x9f{0\xc99\ \x00\x00\x01\x9f{0\xc99\
\x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x8d\xaa\ \x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x004B\
\x00\x00\x01\x9f\x84\xbd\x03\xb8\
\x00\x00\x03\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x91\xd7\
\x00\x00\x01\x9f\x84\x8f\x00\xb9\ \x00\x00\x01\x9f\x84\x8f\x00\xb9\
\x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\ \x00\x00\x01\xe8\x00\x00\x00\x00\x00\x01\x00\x00H_\
\x00\x00\x01\x9f\x7f&\x83\xcd\ \x00\x00\x01\x9f\x7f&\x83\xcd\
\x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<J\ \x00\x00\x01\xc8\x00\x00\x00\x00\x00\x01\x00\x00@w\
\x00\x00\x01\x9f{C\xf1\x18\ \x00\x00\x01\x9f{C\xf1\x18\
\x00\x00\x03\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x91\x1b\ \x00\x00\x04\x16\x00\x00\x00\x00\x00\x01\x00\x00\x9b\x1b\
\x00\x00\x01\x9f\x7fY\xceg\ \x00\x00\x01\x9f\x7fY\xceg\
\x00\x00\x02\xa2\x00\x00\x00\x00\x00\x01\x00\x00f\xc8\ \x00\x00\x02\xc6\x00\x00\x00\x00\x00\x01\x00\x00j\xf5\
\x00\x00\x01\x9f\x7fV\xd5\xc0\ \x00\x00\x01\x9f\x7fV\xd5\xc0\
\x00\x00\x03Z\x00\x00\x00\x00\x00\x01\x00\x00\x84\xc5\ \x00\x00\x03~\x00\x00\x00\x00\x00\x01\x00\x00\x88\xf2\
\x00\x00\x01\x9f\x7f&\x83r\ \x00\x00\x01\x9f\x7f&\x83r\
\x00\x00\x02\xce\x00\x00\x00\x00\x00\x01\x00\x00m[\ \x00\x00\x02\xf2\x00\x00\x00\x00\x00\x01\x00\x00q\x88\
\x00\x00\x01\x9f\x7f&\x83\xb1\ \x00\x00\x01\x9f\x7f&\x83\xb1\
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00,\x08\ \x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00,\x08\
\x00\x00\x01\x9f\x7fY\xce\x82\ \x00\x00\x01\x9f\x7fY\xce\x82\
\x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00Kh\ \x00\x00\x02\x04\x00\x00\x00\x00\x00\x01\x00\x00O\x95\
\x00\x00\x01\x9f{C\xf1.\ \x00\x00\x01\x9f{C\xf1.\
\x00\x00\x03\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x95[\ \x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00\x9f[\
\x00\x00\x01\x9f\x84\x8f\xa6\x10\ \x00\x00\x01\x9f\x84\x8f\xa6\x10\
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\ \x00\x00\x01\xa8\x00\x00\x00\x00\x00\x01\x00\x00:\xc9\
\x00\x00\x01\x9f{{\xa5\xd5\ \x00\x00\x01\x9f{{\xa5\xd5\
\x00\x00\x04&\x00\x00\x00\x00\x00\x01\x00\x00\x97\x0c\ \x00\x00\x04h\x00\x00\x00\x00\x00\x01\x00\x00\xa1\x0c\
\x00\x00\x01\x9f\x7f&\x83\x10\ \x00\x00\x01\x9f\x7f&\x83\x10\
\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x95H\
\x00\x00\x01\x9f\x84\xbd\x03\xde\
\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x01\x00\x00(e\ \x00\x00\x00\xfe\x00\x00\x00\x00\x00\x01\x00\x00(e\
\x00\x00\x01\x9f\x7f&\x83~\ \x00\x00\x01\x9f\x7f&\x83~\
\x00\x00\x036\x00\x00\x00\x00\x00\x01\x00\x00\x80\xe2\ \x00\x00\x03Z\x00\x00\x00\x00\x00\x01\x00\x00\x85\x0f\
\x00\x00\x01\x9f\x7f&\x83T\ \x00\x00\x01\x9f\x7f&\x83T\
\x00\x00\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x14M\ \x00\x00\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x14M\
\x00\x00\x01\x9f\x7fY\xce^\ \x00\x00\x01\x9f\x7fY\xce^\
\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x19Y\ \x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x19Y\
\x00\x00\x01\x9f{0\xc9B\ \x00\x00\x01\x9f{0\xc9B\
\x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x004B\ \x00\x00\x01\x88\x00\x00\x00\x00\x00\x01\x00\x008o\
\x00\x00\x01\x9f\x7f&\x83)\ \x00\x00\x01\x9f\x7f&\x83)\
\x00\x00\x03\x14\x00\x00\x00\x00\x00\x01\x00\x00{`\ \x00\x00\x038\x00\x00\x00\x00\x00\x01\x00\x00\x7f\x8d\
\x00\x00\x01\x9f{C\xf1N\ \x00\x00\x01\x9f{C\xf1N\
\x00\x00\x02p\x00\x00\x00\x00\x00\x01\x00\x00b\x99\ \x00\x00\x02\x94\x00\x00\x00\x00\x00\x01\x00\x00f\xc6\
\x00\x00\x01\x9f\x7f&\x82\xf2\ \x00\x00\x01\x9f\x7f&\x82\xf2\
\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00!\xc5\ \x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00!\xc5\
\x00\x00\x01\x9f{\x8d\xf34\ \x00\x00\x01\x9f{\x8d\xf34\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9f{0\xc9+\ \x00\x00\x01\x9f{0\xc9+\
\x00\x00\x02\x1e\x00\x00\x00\x00\x00\x01\x00\x00RH\ \x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x00Vu\
\x00\x00\x01\x9f{C\xf1=\ \x00\x00\x01\x9f{C\xf1=\
\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\ \x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
\x00\x00\x01\x9f\x7f&\x83\x99\ \x00\x00\x01\x9f\x7f&\x83\x99\
\x00\x00\x04P\x00\x00\x00\x00\x00\x01\x00\x00\x99\xa2\ \x00\x00\x04\x92\x00\x00\x00\x00\x00\x01\x00\x00\xa3\xa2\
\x00\x00\x01\x9f{{\xa5\xe3\ \x00\x00\x01\x9f{{\xa5\xe3\
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\ \x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
\x00\x00\x01\x9f\x7f\xac\xf2\xc6\ \x00\x00\x01\x9f\x7f\xac\xf2\xc6\
\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00/{\ \x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00/{\
\x00\x00\x01\x9f\x7fV\xd5\xe5\ \x00\x00\x01\x9f\x7fV\xd5\xe5\
\x00\x00\x03|\x00\x00\x00\x00\x00\x01\x00\x00\x88\xb7\ \x00\x00\x03\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xe4\
\x00\x00\x01\x9f{0\xc9R\ \x00\x00\x01\x9f{0\xc9R\
" "

View File

@@ -344,11 +344,19 @@ class Component:
if kind == "text": if kind == "text":
raw_source = implementation.get("source", {}) raw_source = implementation.get("source", {})
equations = raw_source.get("equations", "") equations = raw_source.get("equations", "")
declarations = raw_source.get("declarations", "")
initial_equations = raw_source.get("initialEquations", "")
parameters = data.get("parameters", raw_source.get("parameters", [])) parameters = data.get("parameters", raw_source.get("parameters", []))
if not isinstance(equations, str): if not isinstance(equations, str):
raise ValueError("Text component equations must be a string") raise ValueError("Text component equations must be a string")
if not isinstance(declarations, str):
raise ValueError("Text component declarations must be a string")
if not isinstance(initial_equations, str):
raise ValueError("Text component initial equations must be a string")
source = { source = {
"equations": equations, "equations": equations,
"declarations": declarations,
"initialEquations": initial_equations,
} }
if not isinstance(parameters, list): if not isinstance(parameters, list):
raise ValueError("Component parameters must be a list") raise ValueError("Component parameters must be a list")

View File

@@ -4,6 +4,7 @@ from bedit.core.simulation.results import (
BerSimulationResultsSerializer, BerSimulationResultsSerializer,
JsonSimulationResultsSerializer, JsonSimulationResultsSerializer,
SimulationExecutionResult, SimulationExecutionResult,
SimulationGraph,
SimulationResults, SimulationResults,
SimulationResultsSerializer, SimulationResultsSerializer,
SimulationTrace, SimulationTrace,
@@ -14,6 +15,7 @@ __all__ = [
"Simulation", "Simulation",
"SimulationResults", "SimulationResults",
"SimulationExecutionResult", "SimulationExecutionResult",
"SimulationGraph",
"SimulationResultsSerializer", "SimulationResultsSerializer",
"SimulationTrace", "SimulationTrace",
"BerSimulationResultsSerializer", "BerSimulationResultsSerializer",

View File

@@ -123,6 +123,14 @@ def emit_model(
f"{body_indent}parameter {parameter_type} {parameter_name} = {value};" f"{body_indent}parameter {parameter_type} {parameter_name} = {value};"
) )
if implementation_kind == "text":
declarations = str(implementation.get("source", {}).get("declarations", ""))
declarations = expand_bevalues(declarations, macros)
lines.extend(
f"{body_indent}{line}" if line.strip() else ""
for line in declarations.splitlines()
)
if implementation_kind == "graph": if implementation_kind == "graph":
for block in nested_graph.get("blocks", []): for block in nested_graph.get("blocks", []):
block_type = model_name_for(block) block_type = model_name_for(block)
@@ -134,8 +142,9 @@ def emit_model(
f"{body_indent}{junction_type} {_junction_name(junction['id'])};" f"{body_indent}{junction_type} {_junction_name(junction['id'])};"
) )
lines.append(f"{indentation}equation")
if implementation_kind == "graph": if implementation_kind == "graph":
lines.append(f"{indentation}equation")
blocks = {block["id"]: block for block in nested_graph.get("blocks", [])} blocks = {block["id"]: block for block in nested_graph.get("blocks", [])}
junctions = { junctions = {
junction["id"]: junction for junction in nested_graph.get("junctions", []) junction["id"]: junction for junction in nested_graph.get("junctions", [])
@@ -151,8 +160,21 @@ def emit_model(
# Connector types may require different equations in future. # Connector types may require different equations in future.
lines.append(f"{body_indent}{target} = {source};") lines.append(f"{body_indent}{target} = {source};")
else: else:
initial_equations = str(
implementation.get("source", {}).get("initialEquations", "")
)
initial_equations = expand_bevalues(initial_equations, macros)
equations = str(implementation.get("source", {}).get("equations", "")) equations = str(implementation.get("source", {}).get("equations", ""))
equations = expand_bevalues(equations, macros) equations = expand_bevalues(equations, macros)
if initial_equations.strip():
lines.append(f"{indentation}initial equation")
lines.extend(
f"{body_indent}{line}" if line.strip() else ""
for line in initial_equations.splitlines()
)
lines.append(f"{indentation}equation")
lines.extend( lines.extend(
f"{body_indent}{line}" if line.strip() else "" f"{body_indent}{line}" if line.strip() else ""
for line in equations.splitlines() for line in equations.splitlines()
@@ -161,7 +183,6 @@ def emit_model(
lines.append(f"{indentation}end {model_name};") lines.append(f"{indentation}end {model_name};")
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"
def cleanup_graph(graph: dict[str, Any]) -> dict[str, Any]: def cleanup_graph(graph: dict[str, Any]) -> dict[str, Any]:
"""Remove annotations and UI-only data from a serialized component tree.""" """Remove annotations and UI-only data from a serialized component tree."""

View File

@@ -4,6 +4,7 @@ import zlib
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from uuid import uuid4
import msgpack import msgpack
@@ -25,6 +26,16 @@ class SimulationTrace:
properties: dict[str, Any] = field(default_factory=dict) properties: dict[str, Any] = field(default_factory=dict)
@dataclass
class SimulationGraph:
"""One graph workspace tab and its configured traces."""
id: str = field(default_factory=lambda: str(uuid4()))
title: str = "Graph 1"
x_axis: str = "time"
traces: list[SimulationTrace] = field(default_factory=list)
@dataclass @dataclass
class SimulationResults: class SimulationResults:
"""Serializable state displayed by the standalone simulation window.""" """Serializable state displayed by the standalone simulation window."""
@@ -33,7 +44,9 @@ class SimulationResults:
status: dict[str, Any] = field(default_factory=dict) status: dict[str, Any] = field(default_factory=dict)
messages: list[dict[str, str]] = field(default_factory=list) messages: list[dict[str, str]] = field(default_factory=list)
data: dict[str, list[float]] = field(default_factory=dict) data: dict[str, list[float]] = field(default_factory=dict)
traces: list[SimulationTrace] = field(default_factory=list) graphs: list[SimulationGraph] = field(
default_factory=lambda: [SimulationGraph()]
)
metadata: dict[str, Any] = field(default_factory=dict) metadata: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, Any]:
@@ -44,7 +57,7 @@ class SimulationResults:
"status": dict(self.status), "status": dict(self.status),
"messages": [dict(message) for message in self.messages], "messages": [dict(message) for message in self.messages],
"data": {name: list(values) for name, values in self.data.items()}, "data": {name: list(values) for name, values in self.data.items()},
"traces": [asdict(trace) for trace in self.traces], "graphs": [asdict(graph) for graph in self.graphs],
"metadata": dict(self.metadata), "metadata": dict(self.metadata),
} }
@@ -55,7 +68,17 @@ class SimulationResults:
if data.get("version") != RESULTS_VERSION: if data.get("version") != RESULTS_VERSION:
raise ValueError(f"Unsupported simulation-results version: {data.get('version')!r}") raise ValueError(f"Unsupported simulation-results version: {data.get('version')!r}")
try: try:
traces = [SimulationTrace(**trace) for trace in data.get("traces", [])] graphs = [
SimulationGraph(
id=str(graph["id"]),
title=str(graph["title"]),
x_axis=str(graph.get("x_axis", "time")),
traces=[
SimulationTrace(**trace) for trace in graph.get("traces", [])
],
)
for graph in data.get("graphs", [])
]
return cls( return cls(
model_name=str(data.get("modelName", "")), model_name=str(data.get("modelName", "")),
status=dict(data.get("status", {})), status=dict(data.get("status", {})),
@@ -64,10 +87,10 @@ class SimulationResults:
str(name): [float(value) for value in values] str(name): [float(value) for value in values]
for name, values in dict(data.get("data", {})).items() for name, values in dict(data.get("data", {})).items()
}, },
traces=traces, graphs=graphs,
metadata=dict(data.get("metadata", {})), metadata=dict(data.get("metadata", {})),
) )
except (TypeError, ValueError) as error: except (KeyError, TypeError, ValueError) as error:
raise ValueError("Malformed simulation-results data") from error raise ValueError("Malformed simulation-results data") from error

View File

@@ -47,13 +47,7 @@ class Simulation:
"""Compose and retain the active graph's Modelica representation.""" """Compose and retain the active graph's Modelica representation."""
self.model_path = None self.model_path = None
self.compose_source(graph)
# Create openmodelica model
result = compose_graph(graph)
self.last_composition_input = result.graph
self.id_list = result.objects_by_id
self.last_composition_output = result.modelica
self.model_name = result.model_name
def _model_compiled(result): def _model_compiled(result):
log.info("Compiling OK: %s", result) log.info("Compiling OK: %s", result)
@@ -79,6 +73,17 @@ class Simulation:
error_callback, error_callback,
) )
def compose_source(self, graph: dict[str, Any]) -> tuple[str, str]:
"""Compose Modelica source without asking OpenModelica to build it."""
result = compose_graph(graph)
self.last_composition_input = result.graph
self.id_list = result.objects_by_id
self.last_composition_output = result.modelica
self.model_name = result.model_name
# log.info("Composed OpenModelica model:\n%s", result.modelica)
return result.model_name, result.modelica
def run_simulation( def run_simulation(
self, self,
graph: dict[str, Any], graph: dict[str, Any],

View File

@@ -168,7 +168,13 @@ class DocumentController(QObject):
name=self._available_component_name(base_name, self.document.roots.values(), number), name=self._available_component_name(base_name, self.document.roots.values(), number),
implementation_kind=kind, implementation_kind=kind,
icon=Icon(text="Graph" if kind == "graph" else "Text"), icon=Icon(text="Graph" if kind == "graph" else "Text"),
source={"equations": ""} if kind == "text" else {}, source={
"declarations": "",
"initialEquations": "",
"equations": "",
}
if kind == "text"
else {},
) )
self.undo_stack.push(AddComponentCommand(self, None, component)) self.undo_stack.push(AddComponentCommand(self, None, component))
self.activate_component(component.id) self.activate_component(component.id)
@@ -187,7 +193,13 @@ class DocumentController(QObject):
name=self._available_component_name(base_name, owner.graph.blocks.values(), number), name=self._available_component_name(base_name, owner.graph.blocks.values(), number),
implementation_kind=kind, implementation_kind=kind,
icon=Icon(text="Graph" if kind == "graph" else "Text"), icon=Icon(text="Graph" if kind == "graph" else "Text"),
source={"equations": ""} if kind == "text" else {}, source={
"declarations": "",
"initialEquations": "",
"equations": "",
}
if kind == "text"
else {},
) )
self.undo_stack.push(AddComponentCommand(self, owner_id, component)) self.undo_stack.push(AddComponentCommand(self, owner_id, component))
return component.id return component.id
@@ -427,6 +439,12 @@ class DocumentController(QObject):
raise ValueError("Open a graph component before composing") raise ValueError("Open a graph component before composing")
self.simulation.compose(component.to_dict()) self.simulation.compose(component.to_dict())
def compose_active_graph_source(self) -> tuple[str, str]:
component = self.active_component
if component is None or component.implementation_kind != "graph":
raise ValueError("Open a graph component before exporting a model")
return self.simulation.compose_source(component.to_dict())
def run_simulation( def run_simulation(
self, self,
progress_callback=None, progress_callback=None,
@@ -636,6 +654,8 @@ class DocumentController(QObject):
self, self,
inputs: list[Port], inputs: list[Port],
outputs: list[Port], outputs: list[Port],
declarations: str,
initial_equations: str,
equations: str, equations: str,
parameters: list[Parameter], parameters: list[Parameter],
) -> None: ) -> None:
@@ -682,6 +702,8 @@ class DocumentController(QObject):
"outputs": [port.to_dict() for port in outputs], "outputs": [port.to_dict() for port in outputs],
"source": { "source": {
"equations": equations, "equations": equations,
"declarations": declarations,
"initialEquations": initial_equations,
}, },
"parameters": [parameter.to_dict() for parameter in parameters], "parameters": [parameter.to_dict() for parameter in parameters],
} }

View File

@@ -14,7 +14,7 @@ PROPERTIES_ROLE = Qt.ItemDataRole.UserRole + 1
class TextDefinitionEditor(QWidget): class TextDefinitionEditor(QWidget):
"""Editor for a text component's equations, ports, and parameters.""" """Editor for a text component's Modelica source, ports, and parameters."""
modifiedChanged = Signal(bool) modifiedChanged = Signal(bool)
definitionEdited = Signal() definitionEdited = Signal()
@@ -32,7 +32,10 @@ class TextDefinitionEditor(QWidget):
QHeaderView.ResizeMode.Stretch QHeaderView.ResizeMode.Stretch
) )
self.ui.columnSplitter.setSizes([560, 340]) self.ui.columnSplitter.setSizes([560, 340])
self.ui.sourceSplitter.setSizes([180, 180, 240])
self.ui.definitionSplitter.setSizes([300, 300]) self.ui.definitionSplitter.setSizes([300, 300])
self.ui.declarationsEdit.textChanged.connect(self._mark_modified)
self.ui.initialEquationsEdit.textChanged.connect(self._mark_modified)
self.ui.equationsEdit.textChanged.connect(self._mark_modified) self.ui.equationsEdit.textChanged.connect(self._mark_modified)
self.ui.portsTable.cellChanged.connect(self._symbols_modified) self.ui.portsTable.cellChanged.connect(self._symbols_modified)
self.ui.parametersTable.cellChanged.connect(self._symbols_modified) self.ui.parametersTable.cellChanged.connect(self._symbols_modified)
@@ -52,6 +55,14 @@ class TextDefinitionEditor(QWidget):
def equations(self) -> str: def equations(self) -> str:
return self.ui.equationsEdit.toPlainText() return self.ui.equationsEdit.toPlainText()
@property
def declarations(self) -> str:
return self.ui.declarationsEdit.toPlainText()
@property
def initial_equations(self) -> str:
return self.ui.initialEquationsEdit.toPlainText()
@property @property
def ports(self) -> tuple[list[Port], list[Port]]: def ports(self) -> tuple[list[Port], list[Port]]:
inputs: list[Port] = [] inputs: list[Port] = []
@@ -87,12 +98,16 @@ class TextDefinitionEditor(QWidget):
def set_definition( def set_definition(
self, self,
declarations: str,
initial_equations: str,
equations: str, equations: str,
inputs: list[Port], inputs: list[Port],
outputs: list[Port], outputs: list[Port],
parameters: list[Parameter], parameters: list[Parameter],
) -> None: ) -> None:
self._loading = True self._loading = True
self.ui.declarationsEdit.setPlainText(declarations)
self.ui.initialEquationsEdit.setPlainText(initial_equations)
self.ui.equationsEdit.setPlainText(equations) self.ui.equationsEdit.setPlainText(equations)
self.ui.portsTable.setRowCount(0) self.ui.portsTable.setRowCount(0)
for port in inputs: for port in inputs:
@@ -102,11 +117,7 @@ class TextDefinitionEditor(QWidget):
self.ui.parametersTable.setRowCount(0) self.ui.parametersTable.setRowCount(0)
for parameter in parameters: for parameter in parameters:
self._append_parameter(parameter) self._append_parameter(parameter)
self.ui.equationsEdit.set_symbols( self._set_editor_symbols(inputs, outputs, parameters)
[port.name for port in inputs],
[port.name for port in outputs],
[parameter.name for parameter in parameters],
)
self._loading = False self._loading = False
self.set_modified(False) self.set_modified(False)
self._update_buttons() self._update_buttons()
@@ -128,11 +139,22 @@ class TextDefinitionEditor(QWidget):
def _refresh_editor_symbols(self) -> None: def _refresh_editor_symbols(self) -> None:
inputs, outputs = self.ports inputs, outputs = self.ports
self.ui.equationsEdit.set_symbols( self._set_editor_symbols(inputs, outputs, self.parameters)
def _set_editor_symbols(
self,
inputs: list[Port],
outputs: list[Port],
parameters: list[Parameter],
) -> None:
symbols = (
[port.name for port in inputs], [port.name for port in inputs],
[port.name for port in outputs], [port.name for port in outputs],
[parameter.name for parameter in self.parameters], [parameter.name for parameter in parameters],
) )
self.ui.declarationsEdit.set_symbols(*symbols)
self.ui.initialEquationsEdit.set_symbols(*symbols)
self.ui.equationsEdit.set_symbols(*symbols)
def _new_combo(self, values: list[tuple[str, str]], current: str) -> QComboBox: def _new_combo(self, values: list[tuple[str, str]], current: str) -> QComboBox:
combo = QComboBox(self) combo = QComboBox(self)

View File

@@ -862,6 +862,75 @@ N\xa7\xd3>==\xfd\xa1\xc3\xe1\x98\x92$\xe9I\x00\
\xbf\xc9\xd3\x01\x9f\xa3\x0f\x17Z/\xe3\x7f\xe1/\x17\x85\ \xbf\xc9\xd3\x01\x9f\xa3\x0f\x17Z/\xe3\x7f\xe1/\x17\x85\
\xd6\x06q(\x0e\x10\x00\x00\x00\x00IEND\xaeB\ \xd6\x06q(\x0e\x10\x00\x00\x00\x00IEND\xaeB\
`\x82\ `\x82\
\x00\x00\x04)\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\
\xa7\x93\x00\x00\x00\x09pHYs\x00\x007\x5c\x00\x00\
7\x5c\x01\xcb\xc7\xa4\xb9\x00\x00\x03\x9bIDATx\
\xda\xedT=o\x1bG\x10}\xbb\xb7w$\xf5A\x9e\
lJEB\x18\xb1\x0a\x1bv\x13\xdb\x85\xaa\x18P$\
\xa4\x89\x81\x94\xd6_\x08`\xb8r\x93 \x85\x02\x01\xa9\
\x9c\x1f\xe0>\x85\x81\x94F\x00\xa7\x11\xd88\x85\x1bK\
6@)\x94-\xc8\x96\x10\x18\x81\x22\xf3C\x22i\xf2\
\xf6v\xf3B.s\xd1\xb1H\x80\xb83\x07x\x98\xbd\
\xdd\x99y3\xb3\xb7\x83\xb1\x8ce,\xef\xbd\x88\x1f\x97\
\x97\xe1\x05\x01\x820D~~\x1e\xdb\xf7\xee\xa1st\
\x94\x188H\x07/\x05\xe5\xe0\x0f\xb5C\xf0\xb7N\xd6\
\xb9\xb3g\x11\xde\xba\x85\xf8\xd5+\xc4\xb5\x1a`\x0c\xc4\
\x83B\x01\x82\xe4\xe1\xcd\x9b\xf2\x8f'Ol.\x9f\xb7\
\x96\x07\xd1\xc9\x09\xbeX_\xc7\xbb\x90\xd7\xd7\xafCN\
M\x01RB7\x9bbraA4\xee\xdf7q\xa3\
\x01\xf1\xc3\xf9\xf3\xf0\xc3P\xac\x90\xfc\xd1\xda\x9a|v\
\xf7\xee4\xc9\xbdw\xda\x01\x87\xec\xf4t<\xb7\xba\xda\
\xcc\xdc\xb9c\xebW\xae\x88\xb8^\xb7\xa2|\xfb\xb6\xf8\
\xe0\xeaU\xdbl4>?\xda\xd8\xf8\xf6\xa0\x5c\xfe\xa8\
yp \xff=\x01G\x9eN\x22!\x1fM\xa0T2\
S\x8b\x8b{\xb9\x85\x85U\xe4r?G\x9b\x9bB\xd5\
\xaaU[\x7f\xfe|)\xbcp\xe1Avv\xd6\x93a\
h\xdb\x07\x07B\xfc\xe7\x04\x122\xe3`\x07p~\x09\
\x04c\xa3X\x9c\xd5;;?\x89\xdd\xdd\xcf\xa41e\
e\x94B\xb3Z\xfd\xca\xfa\xbeWZZ\x8a>^Y\
Q\xf3\xec\x88\xa7\x94#'\xac\x85L\x05\x1c\x09\x9e\xe8\
S~\x88cxR\x22\xc8\xe7\xa1\xe7\xe6`\x84\x88\xf4\
\xe3\xc7\xbe\xa8V\xbf6\x17/\x96\xd5\xeb\x87\x0f\x0bZ\
\xa9K\xad(\xc2\x87\xd7\xaey\xc1\x8b\x17b\xe6\xf8\x18\
\xca\xf3\xe0Y\x9bT<B\x98\x00B\x0c\xaa\xa6\xbd!\
bc\xfa\xdad2\x00\x89\xdbL`\xfb\xe9S\xfc\xba\
\xb5\x85Oo\xdc\xf0J\x95\x0a\xec\xfe\xfe\xa5\xdc\xcb\x97\
\x05\x15[\xabzQ\xe4\xd7\xf6\xf7qD\x833$\xb7\
LF1\xa8JZ\x9dJ\xc2\x81$\x18VJX\x12\
\x81\xa4rr\x12&\x9bE\xa7\xd3\xc1\xef\x9b\x9b\xf8m\
{\x1b\xf5\xb7o\xd1e\xcc\xe3j\x15zo\x0f\xf4\xf1\
\xd9\x0d\xa5\x0c\x00\x87~\xe6\xacf\x00\x0a\x83\x9e\xd6\xff\
l=\xc9$\xbb$\x95\x02\x82\x00\xa0\x8e\xe9\xd7k\xb7\
\xd1\x22A\x83o\xbd~x\x88\xb6\xf3\xcb\xf2\xacEm\
\xc8A\xe2~\xd2\xc6\x15\xa7\xb9\x88b\x00o8\x1c\xba\
t\x0a\xba]L\xb1\x82\xac\xef#\xabT\x9f\xcc#\x04\
\x1d\xe5\x10\x0c \xb4\x86\xe9\xf5`834\xdft\xaf\
^G\xf7\xafJ\x1di\x86v\x9a~]\xdaj\x12c\
b\x02\xbe\xbb\x22IN^\x99V\x87B4&\xac\xad\
\x00(\xedll\xe8\xe2\xb9s\xbeGbEC\x9fW\
\xe1\xc71|!\x86W\xd2\xdfW\x0c\xe0q_\x11\xc3\
\xffD\xce\xcc@\x16\x8b\x10\x1e\xbf\x5c\x85Y\x22\x00P\
\xa0\xd6\xd4>\x8b9\xb3\xbb\xab\xa3A\xcf*5r\x8b\
5\xf4\xe5\x93\x0e\xb0~\xc2\x03\x1eZ#%\xfd\x93\xb6\
\xb3\xda\xd3O\x8f\x04\xbe\xd3\xa7\xde<\xed2\xc0\x08\xb2\
\x0e\x19\x06\xa5\xbf\xe0\xbaG\xbfe\x00\x8f\xd4174\
\x17\x0c\xb0\x04\xe0;\x03\x5cf\x8b\xa4M?\xad\xd4\xcf\
G\x0dV\x9f\x9e\x0f\xce~ v\xf8o\x11zpn\
\x14\xb0\xc5\xbdo\xda\xc0/\x92\xa6j\xd2\xcd\x8c<7\
\x9e\x01\x8b4\xca\xd3X%\x09$\xc3%\x91\xd1\xa4b\
\x87\xd40J\xdbk\xee5\xc9\x09=\xd8\xb2\xea\x8dK\
\xb6\x05\xc8\x90Z\xd0\x00\xceY\x11\xdf\xe3\xffI%\xf5\
mI\xdc\x224`\xfaE~\xe9\x0e\xa4\xd3=B\xa7\
F\xe9\xe8\xe8\x1dE\x90B&\xd1)\x0c\xc4`,c\
\x19\xcbX\x06\xf2'\xe3\xdf\x9d\x06\x06\xf6\x92\x0e\x00\x00\
\x00\x22zTXtSoftware\x00\x00\
x\xda+//\xd7\xcb\xcc\xcb.NN,H\xd5\xcb\
/J\x07\x006\xd8\x06X\x10S\xca\x5c\x00\x00\x00\x00\
IEND\xaeB`\x82\
\x00\x00\x02V\ \x00\x00\x02V\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -2383,6 +2452,101 @@ u1\xb8\xdd0`x\xe8F\xf9\x1e\xe19\xea\x8a\x03\
|f\x1e\x89\xd1\x9ef\xbdhx\x0fE4mL\xaf\ |f\x1e\x89\xd1\x9ef\xbdhx\x0fE4mL\xaf\
b/B\x06\x12o\xbf\x00\xa3\x17WYZq\xd9W\ b/B\x06\x12o\xbf\x00\xa3\x17WYZq\xd9W\
\x00\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x05\xcf\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
\x00\x00\x00\x09pHYs\x00\x00\x03v\x00\x00\x03v\
\x01}\xd5\x82\xcc\x00\x00\x00\x19tEXtSof\
tware\x00www.inksca\
pe.org\x9b\xee<\x1a\x00\x00\x05LID\
ATx\xda\xc5\x97\xdboTE\x1c\xc7\xbf3s\xce\
\x9e\xeeni\x0b\xbd@\x01C1\x05B\x10\x0d\xf4\x22\
\x18\x95D0i\xc4\x17|21\xc6\x17\xc3\x93\x89\xd1\
\xc8\x8b\xfaP#oj\x8c\x89O\xfc\x03\xc6K\x0c\xf1\
\x01!B\xa8\xc1j\x88D\x90@)\x14\xac\xb5\x86K\
K\xaf\xdb\xdd={\xce\xcc\xef\xe7\xec\x9cficb\
\xd2\x98\x13f\xf3\xdd93\xbf\x93\x9d\xcf\xfengW\
03\x1e\xe6\xf0\xb0\xc2\xf1\xca\xe9\x17wok\xda~\
lh\xe6\xe2\xd6\xbf\x0a#\xab\x94\x92h\xcb\xad\x9d{\
z\xdd\xfe\x9b\xf9 \xf7\xce\xe1\xad\xef\x0d`\x05Cb\
\x05\xa3\xe7\xcb\xce\xc7\x0emz\xf9\xfc\xab\x9d\x87\xbb\xf6\
\xb4\xec[\x15k\x82\x8e\x09\x9d\xf9\xed\x8d\x07\xda\x0fu\
=\xd5\xd2w\xe6\x83K\x87{S\xf3@N\xe4>\x9d\
)M{>\xd5\xa1\xab\xad\x17\x1b\x1a\xda\xdd~V\xe6\
\xb1)\xb7\x05\xe7\xee\x9e\x94\xf3\xa5\xe2'\x00\x9eI\x05\
\xe0^a\xa2\xe7\xdc\xdf\x03\xe8\x5c\xb3\x05\xf3\xb9k\x10\
A\xc5\xed\x87\x98\xc4\x99\xdb\xdf\xe1\xd4\xd8\xb7\x18-\x0e\
\xefL\xcd\x03aTi\x1c\x1c\xff\x11\xdd\x1bw\xa3I\
\x99\xe5F3\x87+\xf7/\xc2W\xb215\x00\x133\
\x08\x11E(\xc7\xcb\x01<\xa3\x9d]2#5\x00\
\xd2\x04!$\x8c!\x94u\xb4\xccVG1\x8c6\x10\
@\x9a\x00\x0c\x08\x86!B\x18G\x80X\xea\x01\x03c\
\xed\x02\x94\x1e\x80Y\x04\xd0d`,\x80\x00j\x10\x01\
i\x18\xc3\x10\xd6\x9ej\x0e\x08\xc5 bD:^\xe6\
\x01M\xfa\xffy\xe0\xe8\xe0\xd1\x17\xaeN\x5c\xed\x1f\x18\
\x1d\xd869w\xafA\x92\x84d\xc0\xbeC\xb1\x80\x94\
\x02JHw81!2\x0e\xa0\xc6\xa0\x8dv9b\
\xd0ul\x03{\xbe\x84\xaf\x14<_\xc1\xf7\xecu\
F\xa15\xd76\xff\xc4\xda\x9e\xeb\x1d\xab\xb6\xf7\xbf\xb4\
\xf9\xf5\x13\xb5N\xd8\xf7E_\xefTi\xea\xf8\xc6\xfa\
\x8d=mA[\x03\x99$\xdel\xd8\xcd\xb48\x1bw\
M\xd0\xdaX\x00\x8d\xd8\x98%\x8a@D\xee^c\xc8\
\xddk(\xb9\xdfp\xb2W\xef564\xaa\xd6\x9e\xf9\
h\xea\xf8G\xc3o\xb8\x8e\xe9\x1eF\x1d\x9fw\xfc\xb0\
\xabm\xd7\x81\xbd\xeb\xf7\x02\xc2`.\x9aAu\x9f\x85\
\x15\x5c\xdc\x935\x0c\x84\x00r\xb9,\xee\x9a?!\x93\
$p{\x1b\xbcGQ\xa9\x84P\xd2\x83'\xfd\x9a\x94\
\xf2\x90\xb1{\x81\xca\xc2g\xcf\xda\x0d\xc6\xc2!\xdc\x89\
n\x9e\xf9\xec\xc9\xef\x0f\xb8\x10\x8c\xcf\x8dw#\x06z\
\xda\xbb0T\xb8\x00-4 Q\x93\x84\x80\x14\x02J\
J7\xcb(Y\x0b'\xc7\x87\xb1\xe8\x06X\x00L\x9c\
x\x81\xaa\x22\x042\x8b\xbcX\x8d\x91\x89Q\x9c\x1a:\
\x8d\xf7\xf7\xbd\x85\xdb4\x8cyLv\xd5r\x80\x88\x9a\
\xc6\xa6\xc602u\x1dEQN\x0eVp\xb3\x10H\
\x0e\x96\x02\xcc\xb2\x06\xc2\xe2\x01Dm0\x83\x00x\xc2\
\xc3\xba\xecz\xcc\x95\x8b\xb81}\x0bgG\xbfF\x1c\
\x11\x00\xd8\xf50\x8a\xc1md|\xd9T\x03\x00[\x91\
k\xb5\xa8\xa88\x01 \x07\x91$\x1fK+\x07\x90\x1c\
\x0e\x86\xaa\x02A8\xefdT\x1d\x9a\x83V\x17\x8e0\
\x0a1\xb10\x81o\xfe\xf8\x0a\x85J\x11\xa5(\xb6\xa0\
\x12\x90\xe42V\xb3v\xf9@\x9e\xc0R\x80Y\x10\x9a\
f\x0a\xb3\xb8^\xbc\x09?\xf0\x9c\x82L\x80l\x10\xc0\
>\xe7\x91\xf7\xb2\x8eI2\x01,]G\x0cD\x00\x22\
\x8d\xf9h\x1a\xc3\x93\xbf!\xd4\x15h\x22D\xc6\x00\xd2\
\xc0S\xca\xca@\x19W.N\x19\xcf\xc3\x02\xbb\xf0\xcc\
.-\xc3A\x10\x0e\x0e\x0e\xff\x82\x1d\xebw .\xc5\
@9\x09A$\x04H\x85(\x8b\x18\x9e'\xdc\x87\xd6\
5H\xdc\xc1-\x04\x9erkea\x1e\xf1;Q\xd1\
\x06\xbc\x987\xe43L\x96\xa09)[cU\xefe\
1e\x93\x97du\x8f\x7f~\x00\xa0q\x04\xc0\xfe\x85\
\xf2B\xdd\xf9k\xe7\x81x1\x04\x9cP\x0b+)\x92\
^ \x94\xc0\xb3{z\xe1\xafV\xc8T3\xbc\x0a!\
%\xc2B\x8c\x9f.\xffj!%\xaa=@YyK\
\xe4\xfb\x0a\x85\x8cB&c\xd7\x19\xaf\xacY\x1f\xa9\xf5\
\x01\xfe\x90\xaf\x81\xd0\x03\xe0,\x08\x0b0@M\x1a`\
;SU\x94\x5ck\x13\xbbC}%\x1d@\xe0{\xae\
D\x99\x01\x02\xbb\xba\xa7\x7f\x89\xacx\xce\xdaN\xda\xb9\
\xfbD\xdf\xc8\xd0\xb2N\xc8\x1f\xf3\x15\x00\xcf\xfdg\xdb\
|M2\x0b\xd7\xf5\x9cG\xbc*\x80\xb2\x00\x9e\x87J\
r\x00$\x0b\x8c\xbd;#R\xf9MH&\xa9\xf3\x98\
b\xf0b|\x94L\xbc\x00h\x07@)\xfe\x1ep\x87\
3\x0bT\xe2\x18\xc2\x18\x18J\xbe\xb5X\xecF\xc9+\
E\x80\x04\x02(\xeb\x0a8\xd2P2\xb2J\x1aR1\
\xa2\xa4\x9d\xa4\x0e \x18\xa5(D\x14F\xae\xe6+\xb1\
F!\xac ,\xf9 k\xb3<)\x02\x88D\x0bq\
\x08*VP\xac\xc4\xae\x17\xb8\xf2,f\xc0\x92\xacd\
z\x00^F\x15\x1b\xb2A\xbeE\x05\xb8Q\x9aK\xda\
\xb4U\xf5\xb5)\xee@=1B.\x15R\xfbg\x94\
\xad\xf7\x7f\xdf\xd9\xde\x82\x83\xcdk\xb03n\x86\x9cR\
\xc0\xb4D\xb7\xea\xc5V\xde\x82<\xea!H^H\x0d\
\xdf\x1c\xbc\xb9ym\xa0W7*\xb4\xe6\xf3 c\
\xc0Dx\xa2\xf9q\xc8@Add$`\xdeN\x0d\
\xe0N\xff\xec\x05\xd9\x1c=?\xee\xf3\xe5\xfb\x22Z\xa0\
,\x83\xeb\x18*\xab\xc2 \x9b\xb9\x12\xa0n\xffD\x7f\
\xf1\x12V0\x1e\xfa\xdf\xf3\x7f\x00j\xf0\xda\xe8\xbc\xba\
\xd0\x0a\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x04<\ \x00\x00\x04<\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -2649,6 +2813,10 @@ qt_resource_name = b"\
\x0f\xba\x1e'\ \x0f\xba\x1e'\
\x00d\ \x00d\
\x00r\x00a\x00w\x00-\x00p\x00a\x00t\x00h\x00.\x00p\x00n\x00g\ \x00r\x00a\x00w\x00-\x00p\x00a\x00t\x00h\x00.\x00p\x00n\x00g\
\x00\x0f\
\x020\x8b\xe7\
\x00l\
\x00i\x00s\x00t\x00-\x00r\x00e\x00m\x00o\x00v\x00e\x00.\x00p\x00n\x00g\
\x00\x0d\ \x00\x0d\
\x0b\xe6\x1f\xa7\ \x0b\xe6\x1f\xa7\
\x00d\ \x00d\
@@ -2723,6 +2891,10 @@ qt_resource_name = b"\
\x00o\ \x00o\
\x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\ \x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\
\x00.\x00p\x00n\x00g\ \x00.\x00p\x00n\x00g\
\x00\x0c\
\x09\xc6\x19'\
\x00l\
\x00i\x00s\x00t\x00-\x00a\x00d\x00d\x00.\x00p\x00n\x00g\
\x00\x10\ \x00\x10\
\x03\xe6\xd3g\ \x03\xe6\xd3g\
\x00d\ \x00d\
@@ -2749,67 +2921,71 @@ qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1e\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x02\x00\x00\x00 \x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00N\xe8\ \x00\x00\x02\x22\x00\x00\x00\x00\x00\x01\x00\x00S\x15\
\x00\x00\x01\x9f{C\xf1'\ \x00\x00\x01\x9f{C\xf1'\
\x00\x00\x02>\x00\x00\x00\x00\x00\x01\x00\x00Z\x1a\ \x00\x00\x02b\x00\x00\x00\x00\x00\x01\x00\x00^G\
\x00\x00\x01\x9f\x7f\xa8\xa8)\ \x00\x00\x01\x9f\x7f\xa8\xa8)\
\x00\x00\x02\xec\x00\x00\x00\x00\x00\x01\x00\x00tV\ \x00\x00\x03\x10\x00\x00\x00\x00\x00\x01\x00\x00x\x83\
\x00\x00\x01\x9f{0\xc99\ \x00\x00\x01\x9f{0\xc99\
\x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x8d\xaa\ \x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x004B\
\x00\x00\x01\x9f\x84\xbd\x03\xb8\
\x00\x00\x03\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x91\xd7\
\x00\x00\x01\x9f\x84\x8f\x00\xb9\ \x00\x00\x01\x9f\x84\x8f\x00\xb9\
\x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\ \x00\x00\x01\xe8\x00\x00\x00\x00\x00\x01\x00\x00H_\
\x00\x00\x01\x9f\x7f&\x83\xcd\ \x00\x00\x01\x9f\x7f&\x83\xcd\
\x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<J\ \x00\x00\x01\xc8\x00\x00\x00\x00\x00\x01\x00\x00@w\
\x00\x00\x01\x9f{C\xf1\x18\ \x00\x00\x01\x9f{C\xf1\x18\
\x00\x00\x03\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x91\x1b\ \x00\x00\x04\x16\x00\x00\x00\x00\x00\x01\x00\x00\x9b\x1b\
\x00\x00\x01\x9f\x7fY\xceg\ \x00\x00\x01\x9f\x7fY\xceg\
\x00\x00\x02\xa2\x00\x00\x00\x00\x00\x01\x00\x00f\xc8\ \x00\x00\x02\xc6\x00\x00\x00\x00\x00\x01\x00\x00j\xf5\
\x00\x00\x01\x9f\x7fV\xd5\xc0\ \x00\x00\x01\x9f\x7fV\xd5\xc0\
\x00\x00\x03Z\x00\x00\x00\x00\x00\x01\x00\x00\x84\xc5\ \x00\x00\x03~\x00\x00\x00\x00\x00\x01\x00\x00\x88\xf2\
\x00\x00\x01\x9f\x7f&\x83r\ \x00\x00\x01\x9f\x7f&\x83r\
\x00\x00\x02\xce\x00\x00\x00\x00\x00\x01\x00\x00m[\ \x00\x00\x02\xf2\x00\x00\x00\x00\x00\x01\x00\x00q\x88\
\x00\x00\x01\x9f\x7f&\x83\xb1\ \x00\x00\x01\x9f\x7f&\x83\xb1\
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00,\x08\ \x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00,\x08\
\x00\x00\x01\x9f\x7fY\xce\x82\ \x00\x00\x01\x9f\x7fY\xce\x82\
\x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00Kh\ \x00\x00\x02\x04\x00\x00\x00\x00\x00\x01\x00\x00O\x95\
\x00\x00\x01\x9f{C\xf1.\ \x00\x00\x01\x9f{C\xf1.\
\x00\x00\x03\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x95[\ \x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00\x9f[\
\x00\x00\x01\x9f\x84\x8f\xa6\x10\ \x00\x00\x01\x9f\x84\x8f\xa6\x10\
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\ \x00\x00\x01\xa8\x00\x00\x00\x00\x00\x01\x00\x00:\xc9\
\x00\x00\x01\x9f{{\xa5\xd5\ \x00\x00\x01\x9f{{\xa5\xd5\
\x00\x00\x04&\x00\x00\x00\x00\x00\x01\x00\x00\x97\x0c\ \x00\x00\x04h\x00\x00\x00\x00\x00\x01\x00\x00\xa1\x0c\
\x00\x00\x01\x9f\x7f&\x83\x10\ \x00\x00\x01\x9f\x7f&\x83\x10\
\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x95H\
\x00\x00\x01\x9f\x84\xbd\x03\xde\
\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x01\x00\x00(e\ \x00\x00\x00\xfe\x00\x00\x00\x00\x00\x01\x00\x00(e\
\x00\x00\x01\x9f\x7f&\x83~\ \x00\x00\x01\x9f\x7f&\x83~\
\x00\x00\x036\x00\x00\x00\x00\x00\x01\x00\x00\x80\xe2\ \x00\x00\x03Z\x00\x00\x00\x00\x00\x01\x00\x00\x85\x0f\
\x00\x00\x01\x9f\x7f&\x83T\ \x00\x00\x01\x9f\x7f&\x83T\
\x00\x00\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x14M\ \x00\x00\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x14M\
\x00\x00\x01\x9f\x7fY\xce^\ \x00\x00\x01\x9f\x7fY\xce^\
\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x19Y\ \x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x19Y\
\x00\x00\x01\x9f{0\xc9B\ \x00\x00\x01\x9f{0\xc9B\
\x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x004B\ \x00\x00\x01\x88\x00\x00\x00\x00\x00\x01\x00\x008o\
\x00\x00\x01\x9f\x7f&\x83)\ \x00\x00\x01\x9f\x7f&\x83)\
\x00\x00\x03\x14\x00\x00\x00\x00\x00\x01\x00\x00{`\ \x00\x00\x038\x00\x00\x00\x00\x00\x01\x00\x00\x7f\x8d\
\x00\x00\x01\x9f{C\xf1N\ \x00\x00\x01\x9f{C\xf1N\
\x00\x00\x02p\x00\x00\x00\x00\x00\x01\x00\x00b\x99\ \x00\x00\x02\x94\x00\x00\x00\x00\x00\x01\x00\x00f\xc6\
\x00\x00\x01\x9f\x7f&\x82\xf2\ \x00\x00\x01\x9f\x7f&\x82\xf2\
\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00!\xc5\ \x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00!\xc5\
\x00\x00\x01\x9f{\x8d\xf34\ \x00\x00\x01\x9f{\x8d\xf34\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9f{0\xc9+\ \x00\x00\x01\x9f{0\xc9+\
\x00\x00\x02\x1e\x00\x00\x00\x00\x00\x01\x00\x00RH\ \x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x00Vu\
\x00\x00\x01\x9f{C\xf1=\ \x00\x00\x01\x9f{C\xf1=\
\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\ \x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
\x00\x00\x01\x9f\x7f&\x83\x99\ \x00\x00\x01\x9f\x7f&\x83\x99\
\x00\x00\x04P\x00\x00\x00\x00\x00\x01\x00\x00\x99\xa2\ \x00\x00\x04\x92\x00\x00\x00\x00\x00\x01\x00\x00\xa3\xa2\
\x00\x00\x01\x9f{{\xa5\xe3\ \x00\x00\x01\x9f{{\xa5\xe3\
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\ \x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
\x00\x00\x01\x9f\x7f\xac\xf2\xc6\ \x00\x00\x01\x9f\x7f\xac\xf2\xc6\
\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00/{\ \x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00/{\
\x00\x00\x01\x9f\x7fV\xd5\xe5\ \x00\x00\x01\x9f\x7fV\xd5\xe5\
\x00\x00\x03|\x00\x00\x00\x00\x00\x01\x00\x00\x88\xb7\ \x00\x00\x03\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xe4\
\x00\x00\x01\x9f{0\xc9R\ \x00\x00\x01\x9f{0\xc9R\
" "

View File

@@ -56,50 +56,53 @@ class Ui_MainWindow(object):
icon4 = QIcon() icon4 = QIcon()
icon4.addFile(u":/icons/icons/office-chart-line.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon4.addFile(u":/icons/icons/office-chart-line.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSimulationWindow.setIcon(icon4) self.actionSimulationWindow.setIcon(icon4)
self.actionExportModel = QAction(MainWindow)
self.actionExportModel.setObjectName(u"actionExportModel")
icon5 = QIcon()
icon5.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionExportModel.setIcon(icon5)
self.actionNew = QAction(MainWindow) self.actionNew = QAction(MainWindow)
self.actionNew.setObjectName(u"actionNew") self.actionNew.setObjectName(u"actionNew")
icon5 = QIcon() icon6 = QIcon()
icon5.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon6.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionNew.setIcon(icon5) self.actionNew.setIcon(icon6)
self.actionRotateClockwise = QAction(MainWindow) self.actionRotateClockwise = QAction(MainWindow)
self.actionRotateClockwise.setObjectName(u"actionRotateClockwise") self.actionRotateClockwise.setObjectName(u"actionRotateClockwise")
icon6 = QIcon() icon7 = QIcon()
icon6.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon7.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRotateClockwise.setIcon(icon6) self.actionRotateClockwise.setIcon(icon7)
self.actionZoomIn = QAction(MainWindow) self.actionZoomIn = QAction(MainWindow)
self.actionZoomIn.setObjectName(u"actionZoomIn") self.actionZoomIn.setObjectName(u"actionZoomIn")
icon7 = QIcon() icon8 = QIcon()
icon7.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon8.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionZoomIn.setIcon(icon7) self.actionZoomIn.setIcon(icon8)
self.actionZoomOut = QAction(MainWindow) self.actionZoomOut = QAction(MainWindow)
self.actionZoomOut.setObjectName(u"actionZoomOut") self.actionZoomOut.setObjectName(u"actionZoomOut")
icon8 = QIcon() icon9 = QIcon()
icon8.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon9.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionZoomOut.setIcon(icon8) self.actionZoomOut.setIcon(icon9)
self.actionCenterView = QAction(MainWindow) self.actionCenterView = QAction(MainWindow)
self.actionCenterView.setObjectName(u"actionCenterView") self.actionCenterView.setObjectName(u"actionCenterView")
icon9 = QIcon() icon10 = QIcon()
icon9.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon10.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCenterView.setIcon(icon9) self.actionCenterView.setIcon(icon10)
self.actionOpen = QAction(MainWindow) self.actionOpen = QAction(MainWindow)
self.actionOpen.setObjectName(u"actionOpen") self.actionOpen.setObjectName(u"actionOpen")
icon10 = QIcon() icon11 = QIcon()
icon10.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon11.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionOpen.setIcon(icon10) self.actionOpen.setIcon(icon11)
self.actionReloadLibraries = QAction(MainWindow) self.actionReloadLibraries = QAction(MainWindow)
self.actionReloadLibraries.setObjectName(u"actionReloadLibraries") self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
self.actionReloadSimulation = QAction(MainWindow) self.actionReloadSimulation = QAction(MainWindow)
self.actionReloadSimulation.setObjectName(u"actionReloadSimulation") self.actionReloadSimulation.setObjectName(u"actionReloadSimulation")
self.actionSave = QAction(MainWindow) self.actionSave = QAction(MainWindow)
self.actionSave.setObjectName(u"actionSave") self.actionSave.setObjectName(u"actionSave")
icon11 = QIcon() icon12 = QIcon()
icon11.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon12.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSave.setIcon(icon11) self.actionSave.setIcon(icon12)
self.actionSaveAs = QAction(MainWindow) self.actionSaveAs = QAction(MainWindow)
self.actionSaveAs.setObjectName(u"actionSaveAs") self.actionSaveAs.setObjectName(u"actionSaveAs")
icon12 = QIcon() self.actionSaveAs.setIcon(icon5)
icon12.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSaveAs.setIcon(icon12)
self.actionExit = QAction(MainWindow) self.actionExit = QAction(MainWindow)
self.actionExit.setObjectName(u"actionExit") self.actionExit.setObjectName(u"actionExit")
self.actionClose = QAction(MainWindow) self.actionClose = QAction(MainWindow)
@@ -295,7 +298,7 @@ class Ui_MainWindow(object):
self.rotateToolButton = QToolButton(self.workspaceHeader) self.rotateToolButton = QToolButton(self.workspaceHeader)
self.rotateToolButton.setObjectName(u"rotateToolButton") self.rotateToolButton.setObjectName(u"rotateToolButton")
self.rotateToolButton.setIcon(icon6) self.rotateToolButton.setIcon(icon7)
self.workspaceHeaderLayout.addWidget(self.rotateToolButton) self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
@@ -441,6 +444,7 @@ class Ui_MainWindow(object):
self.menuSimulation.addAction(self.actionSimulationSettings) self.menuSimulation.addAction(self.actionSimulationSettings)
self.menuSimulation.addAction(self.actionGraphParameters) self.menuSimulation.addAction(self.actionGraphParameters)
self.menuSimulation.addAction(self.actionCompose) self.menuSimulation.addAction(self.actionCompose)
self.menuSimulation.addAction(self.actionExportModel)
self.menuSimulation.addAction(self.actionSimulationWindow) self.menuSimulation.addAction(self.actionSimulationWindow)
self.menuSimulation.addAction(self.actionRunSimulation) self.menuSimulation.addAction(self.actionRunSimulation)
self.fileToolbar.addAction(self.actionNew) self.fileToolbar.addAction(self.actionNew)
@@ -496,6 +500,10 @@ class Ui_MainWindow(object):
self.actionSimulationWindow.setText(QCoreApplication.translate("MainWindow", u"Simulation Window", None)) self.actionSimulationWindow.setText(QCoreApplication.translate("MainWindow", u"Simulation Window", None))
#if QT_CONFIG(statustip) #if QT_CONFIG(statustip)
self.actionSimulationWindow.setStatusTip(QCoreApplication.translate("MainWindow", u"Show the simulation results window", None)) self.actionSimulationWindow.setStatusTip(QCoreApplication.translate("MainWindow", u"Show the simulation results window", None))
#endif // QT_CONFIG(statustip)
self.actionExportModel.setText(QCoreApplication.translate("MainWindow", u"Export Model\u2026", None))
#if QT_CONFIG(statustip)
self.actionExportModel.setStatusTip(QCoreApplication.translate("MainWindow", u"Save the composed OpenModelica model to a file", None))
#endif // QT_CONFIG(statustip) #endif // QT_CONFIG(statustip)
self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None)) self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None))
#if QT_CONFIG(statustip) #if QT_CONFIG(statustip)

View File

@@ -16,10 +16,11 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QIcon, QImage, QKeySequence, QLinearGradient, QIcon, QImage, QKeySequence, QLinearGradient,
QPainter, QPalette, QPixmap, QRadialGradient, QPainter, QPalette, QPixmap, QRadialGradient,
QTransform) QTransform)
from PySide6.QtWidgets import (QApplication, QDockWidget, QHBoxLayout, QLabel, from PySide6.QtWidgets import (QAbstractItemView, QApplication, QDockWidget, QHBoxLayout,
QListWidget, QListWidgetItem, QMainWindow, QMenu, QHeaderView, QLabel, QListWidget, QListWidgetItem,
QMenuBar, QProgressBar, QSizePolicy, QToolBar, QMainWindow, QMenu, QMenuBar, QProgressBar,
QVBoxLayout, QWidget) QSizePolicy, QTabWidget, QToolBar, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget)
from . import resources_rc from . import resources_rc
class Ui_SimulationWindow(object): class Ui_SimulationWindow(object):
@@ -60,15 +61,26 @@ class Ui_SimulationWindow(object):
self.actionToggleResults.setObjectName(u"actionToggleResults") self.actionToggleResults.setObjectName(u"actionToggleResults")
self.actionToggleResults.setCheckable(True) self.actionToggleResults.setCheckable(True)
self.actionToggleResults.setChecked(True) self.actionToggleResults.setChecked(True)
self.actionAddGraph = QAction(SimulationWindow)
self.actionAddGraph.setObjectName(u"actionAddGraph")
icon5 = QIcon()
icon5.addFile(u":/icons/icons/list-add.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionAddGraph.setIcon(icon5)
self.actionRemoveGraph = QAction(SimulationWindow)
self.actionRemoveGraph.setObjectName(u"actionRemoveGraph")
icon6 = QIcon()
icon6.addFile(u":/icons/icons/list-remove.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRemoveGraph.setIcon(icon6)
self.centralWidget = QWidget(SimulationWindow) self.centralWidget = QWidget(SimulationWindow)
self.centralWidget.setObjectName(u"centralWidget") self.centralWidget.setObjectName(u"centralWidget")
self.resultsLayout = QVBoxLayout(self.centralWidget) self.resultsLayout = QVBoxLayout(self.centralWidget)
self.resultsLayout.setObjectName(u"resultsLayout") self.resultsLayout.setObjectName(u"resultsLayout")
self.resultsPlaceholder = QLabel(self.centralWidget) self.graphTabs = QTabWidget(self.centralWidget)
self.resultsPlaceholder.setObjectName(u"resultsPlaceholder") self.graphTabs.setObjectName(u"graphTabs")
self.resultsPlaceholder.setAlignment(Qt.AlignmentFlag.AlignCenter) self.graphTabs.setTabsClosable(False)
self.graphTabs.setMovable(True)
self.resultsLayout.addWidget(self.resultsPlaceholder) self.resultsLayout.addWidget(self.graphTabs)
SimulationWindow.setCentralWidget(self.centralWidget) SimulationWindow.setCentralWidget(self.centralWidget)
self.menuBar = QMenuBar(SimulationWindow) self.menuBar = QMenuBar(SimulationWindow)
@@ -84,7 +96,13 @@ class Ui_SimulationWindow(object):
self.menuToolbars.setObjectName(u"menuToolbars") self.menuToolbars.setObjectName(u"menuToolbars")
self.menuHelp = QMenu(self.menuBar) self.menuHelp = QMenu(self.menuBar)
self.menuHelp.setObjectName(u"menuHelp") self.menuHelp.setObjectName(u"menuHelp")
self.menuGraph = QMenu(self.menuBar)
self.menuGraph.setObjectName(u"menuGraph")
SimulationWindow.setMenuBar(self.menuBar) SimulationWindow.setMenuBar(self.menuBar)
self.workspaceToolbar = QToolBar(SimulationWindow)
self.workspaceToolbar.setObjectName(u"workspaceToolbar")
self.workspaceToolbar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
SimulationWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.workspaceToolbar)
self.fileToolbar = QToolBar(SimulationWindow) self.fileToolbar = QToolBar(SimulationWindow)
self.fileToolbar.setObjectName(u"fileToolbar") self.fileToolbar.setObjectName(u"fileToolbar")
self.fileToolbar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.fileToolbar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
@@ -130,9 +148,26 @@ class Ui_SimulationWindow(object):
self.logDock.setWidget(self.logDockContents) self.logDock.setWidget(self.logDockContents)
SimulationWindow.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.logDock) SimulationWindow.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.logDock)
self.signalsDock = QDockWidget(SimulationWindow)
self.signalsDock.setObjectName(u"signalsDock")
self.signalsDockContents = QWidget()
self.signalsDockContents.setObjectName(u"signalsDockContents")
self.signalsLayout = QVBoxLayout(self.signalsDockContents)
self.signalsLayout.setObjectName(u"signalsLayout")
self.signalsTree = QTreeWidget(self.signalsDockContents)
self.signalsTree.setObjectName(u"signalsTree")
self.signalsTree.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.signalsTree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.signalsTree.setHeaderHidden(True)
self.signalsLayout.addWidget(self.signalsTree)
self.signalsDock.setWidget(self.signalsDockContents)
SimulationWindow.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.signalsDock)
self.menuBar.addAction(self.menuFile.menuAction()) self.menuBar.addAction(self.menuFile.menuAction())
self.menuBar.addAction(self.menuView.menuAction()) self.menuBar.addAction(self.menuView.menuAction())
self.menuBar.addAction(self.menuGraph.menuAction())
self.menuBar.addAction(self.menuHelp.menuAction()) self.menuBar.addAction(self.menuHelp.menuAction())
self.menuFile.addAction(self.actionOpen) self.menuFile.addAction(self.actionOpen)
self.menuFile.addAction(self.actionSave) self.menuFile.addAction(self.actionSave)
@@ -145,10 +180,14 @@ class Ui_SimulationWindow(object):
self.menuView.addAction(self.menuToolbars.menuAction()) self.menuView.addAction(self.menuToolbars.menuAction())
self.menuHelp.addAction(self.actionAbout) self.menuHelp.addAction(self.actionAbout)
self.menuHelp.addAction(self.actionAboutQt) self.menuHelp.addAction(self.actionAboutQt)
self.menuGraph.addAction(self.actionAddGraph)
self.menuGraph.addAction(self.actionRemoveGraph)
self.workspaceToolbar.addAction(self.actionAddGraph)
self.workspaceToolbar.addAction(self.actionRemoveGraph)
self.fileToolbar.addAction(self.actionClear)
self.fileToolbar.addAction(self.actionOpen) self.fileToolbar.addAction(self.actionOpen)
self.fileToolbar.addAction(self.actionSave) self.fileToolbar.addAction(self.actionSave)
self.fileToolbar.addAction(self.actionSaveAs) self.fileToolbar.addAction(self.actionSaveAs)
self.fileToolbar.addAction(self.actionClear)
self.retranslateUi(SimulationWindow) self.retranslateUi(SimulationWindow)
@@ -177,17 +216,29 @@ class Ui_SimulationWindow(object):
self.actionAbout.setText(QCoreApplication.translate("SimulationWindow", u"&About Simulation Window", None)) self.actionAbout.setText(QCoreApplication.translate("SimulationWindow", u"&About Simulation Window", None))
self.actionAboutQt.setText(QCoreApplication.translate("SimulationWindow", u"About &Qt", None)) self.actionAboutQt.setText(QCoreApplication.translate("SimulationWindow", u"About &Qt", None))
self.actionToggleResults.setText(QCoreApplication.translate("SimulationWindow", u"Results", None)) self.actionToggleResults.setText(QCoreApplication.translate("SimulationWindow", u"Results", None))
self.resultsPlaceholder.setText(QCoreApplication.translate("SimulationWindow", u"Simulation graphs and result controls can be added here.", None)) self.actionAddGraph.setText(QCoreApplication.translate("SimulationWindow", u"Add Graph", None))
#if QT_CONFIG(statustip)
self.actionAddGraph.setStatusTip(QCoreApplication.translate("SimulationWindow", u"Add a graph workspace tab", None))
#endif // QT_CONFIG(statustip)
self.actionRemoveGraph.setText(QCoreApplication.translate("SimulationWindow", u"Remove Current Graph", None))
#if QT_CONFIG(statustip)
self.actionRemoveGraph.setStatusTip(QCoreApplication.translate("SimulationWindow", u"Remove the current graph workspace tab", None))
#endif // QT_CONFIG(statustip)
self.menuFile.setTitle(QCoreApplication.translate("SimulationWindow", u"&File", None)) self.menuFile.setTitle(QCoreApplication.translate("SimulationWindow", u"&File", None))
self.menuView.setTitle(QCoreApplication.translate("SimulationWindow", u"&View", None)) self.menuView.setTitle(QCoreApplication.translate("SimulationWindow", u"&View", None))
self.menuPanels.setTitle(QCoreApplication.translate("SimulationWindow", u"&Panels", None)) self.menuPanels.setTitle(QCoreApplication.translate("SimulationWindow", u"&Panels", None))
self.menuToolbars.setTitle(QCoreApplication.translate("SimulationWindow", u"&Toolbars", None)) self.menuToolbars.setTitle(QCoreApplication.translate("SimulationWindow", u"&Toolbars", None))
self.menuHelp.setTitle(QCoreApplication.translate("SimulationWindow", u"&Help", None)) self.menuHelp.setTitle(QCoreApplication.translate("SimulationWindow", u"&Help", None))
self.menuGraph.setTitle(QCoreApplication.translate("SimulationWindow", u"&Graph", None))
self.workspaceToolbar.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Workspace", None))
self.fileToolbar.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"File", None)) self.fileToolbar.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"File", None))
self.statusDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Status", None)) self.statusDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Status", None))
self.timeLabel.setText(QCoreApplication.translate("SimulationWindow", u"Time: 0 s", None)) self.timeLabel.setText(QCoreApplication.translate("SimulationWindow", u"Time: 0 s", None))
self.progressBar.setFormat(QCoreApplication.translate("SimulationWindow", u"%p%", None)) self.progressBar.setFormat(QCoreApplication.translate("SimulationWindow", u"%p%", None))
self.statusLabel.setText(QCoreApplication.translate("SimulationWindow", u"No simulation has been run yet.", None)) self.statusLabel.setText(QCoreApplication.translate("SimulationWindow", u"No simulation has been run yet.", None))
self.logDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Log", None)) self.logDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Log", None))
self.signalsDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Signals", None))
___qtreewidgetitem = self.signalsTree.headerItem()
___qtreewidgetitem.setText(0, QCoreApplication.translate("SimulationWindow", u"Signal", None))
# retranslateUi # retranslateUi

View File

@@ -34,7 +34,31 @@ class Ui_TextDefinitionEditor(object):
self.columnSplitter.setObjectName(u"columnSplitter") self.columnSplitter.setObjectName(u"columnSplitter")
self.columnSplitter.setOrientation(Qt.Orientation.Horizontal) self.columnSplitter.setOrientation(Qt.Orientation.Horizontal)
self.columnSplitter.setChildrenCollapsible(False) self.columnSplitter.setChildrenCollapsible(False)
self.equationsGroup = QGroupBox(self.columnSplitter) self.sourceSplitter = QSplitter(self.columnSplitter)
self.sourceSplitter.setObjectName(u"sourceSplitter")
self.sourceSplitter.setOrientation(Qt.Orientation.Vertical)
self.sourceSplitter.setChildrenCollapsible(False)
self.declarationsGroup = QGroupBox(self.sourceSplitter)
self.declarationsGroup.setObjectName(u"declarationsGroup")
self.declarationsLayout = QVBoxLayout(self.declarationsGroup)
self.declarationsLayout.setObjectName(u"declarationsLayout")
self.declarationsEdit = OpenModelicaEditor(self.declarationsGroup)
self.declarationsEdit.setObjectName(u"declarationsEdit")
self.declarationsLayout.addWidget(self.declarationsEdit)
self.sourceSplitter.addWidget(self.declarationsGroup)
self.initialEquationsGroup = QGroupBox(self.sourceSplitter)
self.initialEquationsGroup.setObjectName(u"initialEquationsGroup")
self.initialEquationsLayout = QVBoxLayout(self.initialEquationsGroup)
self.initialEquationsLayout.setObjectName(u"initialEquationsLayout")
self.initialEquationsEdit = OpenModelicaEditor(self.initialEquationsGroup)
self.initialEquationsEdit.setObjectName(u"initialEquationsEdit")
self.initialEquationsLayout.addWidget(self.initialEquationsEdit)
self.sourceSplitter.addWidget(self.initialEquationsGroup)
self.equationsGroup = QGroupBox(self.sourceSplitter)
self.equationsGroup.setObjectName(u"equationsGroup") self.equationsGroup.setObjectName(u"equationsGroup")
self.equationsLayout = QVBoxLayout(self.equationsGroup) self.equationsLayout = QVBoxLayout(self.equationsGroup)
self.equationsLayout.setObjectName(u"equationsLayout") self.equationsLayout.setObjectName(u"equationsLayout")
@@ -43,7 +67,8 @@ class Ui_TextDefinitionEditor(object):
self.equationsLayout.addWidget(self.equationsEdit) self.equationsLayout.addWidget(self.equationsEdit)
self.columnSplitter.addWidget(self.equationsGroup) self.sourceSplitter.addWidget(self.equationsGroup)
self.columnSplitter.addWidget(self.sourceSplitter)
self.definitionSplitter = QSplitter(self.columnSplitter) self.definitionSplitter = QSplitter(self.columnSplitter)
self.definitionSplitter.setObjectName(u"definitionSplitter") self.definitionSplitter.setObjectName(u"definitionSplitter")
self.definitionSplitter.setOrientation(Qt.Orientation.Vertical) self.definitionSplitter.setOrientation(Qt.Orientation.Vertical)
@@ -141,6 +166,8 @@ class Ui_TextDefinitionEditor(object):
# setupUi # setupUi
def retranslateUi(self, TextDefinitionEditor): def retranslateUi(self, TextDefinitionEditor):
self.declarationsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Declarations", None))
self.initialEquationsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Initial Equations", None))
self.equationsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Equations", None)) self.equationsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Equations", None))
self.portsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Ports", None)) self.portsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Ports", None))
___qtablewidgetitem = self.portsTable.horizontalHeaderItem(0) ___qtablewidgetitem = self.portsTable.horizontalHeaderItem(0)

View File

@@ -181,6 +181,7 @@ class MainWindow(QMainWindow):
) )
self.ui.actionGraphParameters.triggered.connect(self.show_graph_parameters) self.ui.actionGraphParameters.triggered.connect(self.show_graph_parameters)
self.ui.actionCompose.triggered.connect(self.compose_active_graph) self.ui.actionCompose.triggered.connect(self.compose_active_graph)
self.ui.actionExportModel.triggered.connect(self.export_model)
self.ui.actionSimulationWindow.triggered.connect(self.show_simulation_window) self.ui.actionSimulationWindow.triggered.connect(self.show_simulation_window)
self.ui.actionRunSimulation.triggered.connect(self.run_simulation) self.ui.actionRunSimulation.triggered.connect(self.run_simulation)
self.document_controller.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled) self.document_controller.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled)
@@ -261,6 +262,35 @@ class MainWindow(QMainWindow):
self.log.error("Composition failed: %s", error) self.log.error("Composition failed: %s", error)
QMessageBox.warning(self, "Cannot compose", str(error)) QMessageBox.warning(self, "Cannot compose", str(error))
@Slot()
def export_model(self) -> None:
try:
model_name, source = self.document_controller.compose_active_graph_source()
except ValueError as error:
self.log.error("Model export composition failed: %s", error)
QMessageBox.warning(self, "Cannot Export Model", str(error))
return
file_name, _selected_filter = QFileDialog.getSaveFileName(
self,
"Export OpenModelica Model",
f"{model_name}.mo",
"Modelica Models (*.mo);;All Files (*)",
)
if not file_name:
return
path = Path(file_name)
if not path.suffix:
path = path.with_suffix(".mo")
temporary_path = path.with_suffix(path.suffix + ".tmp")
try:
temporary_path.write_text(source, encoding="utf-8")
temporary_path.replace(path)
except OSError as error:
self.log.error("Could not export model %s: %s", path, error)
QMessageBox.critical(self, "Cannot Export Model", str(error))
return
self.log.info("Exported OpenModelica model to %s", path)
@Slot() @Slot()
def show_simulation_window(self) -> None: def show_simulation_window(self) -> None:
self._simulation_window.show() self._simulation_window.show()
@@ -274,7 +304,7 @@ class MainWindow(QMainWindow):
self.show_simulation_window() self.show_simulation_window()
try: try:
self.document_controller.run_simulation(*callbacks) self.document_controller.run_simulation(*callbacks)
window.set_model_name(self.simulation.model_name) window.prepare_run_model(self.simulation.model_name)
except Exception as error: except Exception as error:
self.log.exception("Simulation run failed") self.log.exception("Simulation run failed")
window.report_start_error(error) window.report_start_error(error)
@@ -307,6 +337,7 @@ class MainWindow(QMainWindow):
self.ui.actionSimulationSettings.setEnabled(False) self.ui.actionSimulationSettings.setEnabled(False)
self.ui.actionGraphParameters.setEnabled(False) self.ui.actionGraphParameters.setEnabled(False)
self.ui.actionCompose.setEnabled(False) self.ui.actionCompose.setEnabled(False)
self.ui.actionExportModel.setEnabled(False)
self.ui.actionRunSimulation.setEnabled(False) self.ui.actionRunSimulation.setEnabled(False)
self._update_edit_actions() self._update_edit_actions()
return return
@@ -318,6 +349,7 @@ class MainWindow(QMainWindow):
self.ui.actionSimulationSettings.setEnabled(is_graph) self.ui.actionSimulationSettings.setEnabled(is_graph)
self.ui.actionGraphParameters.setEnabled(is_graph) self.ui.actionGraphParameters.setEnabled(is_graph)
self.ui.actionCompose.setEnabled(is_graph) self.ui.actionCompose.setEnabled(is_graph)
self.ui.actionExportModel.setEnabled(is_graph)
self.ui.actionRunSimulation.setEnabled(is_graph) self.ui.actionRunSimulation.setEnabled(is_graph)
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text") self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
self.ui.workspaceStack.setCurrentWidget( self.ui.workspaceStack.setCurrentWidget(
@@ -505,6 +537,8 @@ class MainWindow(QMainWindow):
if component is None: if component is None:
return return
self.ui.textDefinitionEditor.set_definition( self.ui.textDefinitionEditor.set_definition(
component.source.get("declarations", ""),
component.source.get("initialEquations", ""),
component.source.get("equations", ""), component.source.get("equations", ""),
component.inputs, component.inputs,
component.outputs, component.outputs,
@@ -522,7 +556,8 @@ class MainWindow(QMainWindow):
answer = QMessageBox.question( answer = QMessageBox.question(
self, self,
"Apply text component changes?", "Apply text component changes?",
"The text component has unapplied equation, port, or parameter changes.", "The text component has unapplied declaration, initial-equation, "
"equation, port, or parameter changes.",
QMessageBox.StandardButton.Apply QMessageBox.StandardButton.Apply
| QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.Discard
| QMessageBox.StandardButton.Cancel, | QMessageBox.StandardButton.Cancel,
@@ -540,6 +575,8 @@ class MainWindow(QMainWindow):
self.document_controller.replace_active_text_definition( self.document_controller.replace_active_text_definition(
inputs, inputs,
outputs, outputs,
self.ui.textDefinitionEditor.declarations,
self.ui.textDefinitionEditor.initial_equations,
self.ui.textDefinitionEditor.equations, self.ui.textDefinitionEditor.equations,
self.ui.textDefinitionEditor.parameters, self.ui.textDefinitionEditor.parameters,
) )
@@ -672,6 +709,8 @@ class MainWindow(QMainWindow):
if scene is not None: if scene is not None:
scene.update() scene.update()
self.ui.graphView.viewport().update() self.ui.graphView.viewport().update()
self.ui.textDefinitionEditor.ui.declarationsEdit.reload_highlighting()
self.ui.textDefinitionEditor.ui.initialEquationsEdit.reload_highlighting()
self.ui.textDefinitionEditor.ui.equationsEdit.reload_highlighting() self.ui.textDefinitionEditor.ui.equationsEdit.reload_highlighting()
def _document_opened_changed(self, opened: bool) -> None: def _document_opened_changed(self, opened: bool) -> None:

View File

@@ -1,17 +1,33 @@
import re import re
from pathlib import Path from pathlib import Path
from matplotlib.backend_bases import MouseButton
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg, NavigationToolbar2QT
from matplotlib.figure import Figure
from PySide6.QtCore import Qt, Signal from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QFileDialog, QMainWindow, QMessageBox from PySide6.QtGui import QCloseEvent
from PySide6.QtWidgets import (
QFileDialog,
QInputDialog,
QMainWindow,
QMenu,
QMessageBox,
QTreeWidgetItem,
QVBoxLayout,
QWidget,
)
from bedit.core.simulation.openmodelica import SimulationMessage, SimulationProgress from bedit.core.simulation.openmodelica import SimulationMessage, SimulationProgress
from bedit.core.simulation.results import ( from bedit.core.simulation.results import (
SimulationExecutionResult, SimulationExecutionResult,
SimulationGraph,
SimulationResults, SimulationResults,
SimulationTrace,
load_simulation_results, load_simulation_results,
save_simulation_results, save_simulation_results,
) )
from bedit.gui.generated.ui_simulation_window import Ui_SimulationWindow from bedit.gui.generated.ui_simulation_window import Ui_SimulationWindow
from bedit.gui.preferences import application_settings
class SimulationWindow(QMainWindow): class SimulationWindow(QMainWindow):
@@ -26,9 +42,12 @@ class SimulationWindow(QMainWindow):
super().__init__(parent) super().__init__(parent)
self.ui = Ui_SimulationWindow() self.ui = Ui_SimulationWindow()
self.ui.setupUi(self) self.ui.setupUi(self)
self.settings = application_settings()
self._running = False self._running = False
self._run_generation = 0 self._run_generation = 0
self._file_path: Path | None = None self._file_path: Path | None = None
self._rebuilding_graph_tabs = False
self._updating_signal_checks = False
self.results = SimulationResults() self.results = SimulationResults()
self._connect_actions() self._connect_actions()
self._populate_view_menu() self._populate_view_menu()
@@ -36,6 +55,7 @@ class SimulationWindow(QMainWindow):
self.ui.statusDock, self.ui.logDock, Qt.Orientation.Vertical self.ui.statusDock, self.ui.logDock, Qt.Orientation.Vertical
) )
self.ui.statusDock.setFixedHeight(self.ui.statusDock.sizeHint().height()) self.ui.statusDock.setFixedHeight(self.ui.statusDock.sizeHint().height())
self._restore_window_layout()
self.progressReceived.connect(self._show_progress) self.progressReceived.connect(self._show_progress)
self.messageReceived.connect(self._show_message) self.messageReceived.connect(self._show_message)
self.simulationFinished.connect(self._show_finished) self.simulationFinished.connect(self._show_finished)
@@ -52,19 +72,57 @@ class SimulationWindow(QMainWindow):
lambda: QMessageBox.aboutQt(self, "About Qt Framework") lambda: QMessageBox.aboutQt(self, "About Qt Framework")
) )
self.ui.actionToggleResults.toggled.connect(self.ui.centralWidget.setVisible) self.ui.actionToggleResults.toggled.connect(self.ui.centralWidget.setVisible)
self.ui.actionAddGraph.triggered.connect(self.add_graph_tab)
self.ui.actionRemoveGraph.triggered.connect(self.remove_current_graph_tab)
self.ui.graphTabs.tabBarDoubleClicked.connect(self.rename_graph_tab)
self.ui.graphTabs.tabBar().tabMoved.connect(self._move_graph_tab)
self.ui.graphTabs.currentChanged.connect(self._current_graph_changed)
self.ui.signalsTree.itemChanged.connect(self._signal_check_changed)
self.ui.signalsTree.customContextMenuRequested.connect(
self.show_signal_context_menu
)
def _populate_view_menu(self) -> None: def _populate_view_menu(self) -> None:
self.ui.menuPanels.addAction(self.ui.actionToggleResults) self.ui.menuPanels.addAction(self.ui.actionToggleResults)
for panel in (self.ui.statusDock, self.ui.logDock): for panel in (self.ui.statusDock, self.ui.logDock, self.ui.signalsDock):
self.ui.menuPanels.addAction(panel.toggleViewAction()) self.ui.menuPanels.addAction(panel.toggleViewAction())
self.ui.menuToolbars.addAction(self.ui.fileToolbar.toggleViewAction()) self.ui.menuToolbars.addAction(self.ui.fileToolbar.toggleViewAction())
self.ui.menuToolbars.addAction(self.ui.workspaceToolbar.toggleViewAction())
def begin_run(self, model_name: str = "") -> tuple: def _restore_window_layout(self) -> None:
"""Reset the viewer and return callbacks bound to this run.""" geometry = self.settings.value("simulationWindow/geometry")
if geometry is not None:
self.restoreGeometry(geometry)
state = self.settings.value("simulationWindow/state")
if state is not None:
self.restoreState(state)
results_visible = self.settings.value(
"simulationWindow/resultsVisible", True, type=bool
)
self.ui.actionToggleResults.setChecked(results_visible)
self.clear(model_name=model_name) def _save_window_layout(self) -> None:
self.settings.setValue("simulationWindow/geometry", self.saveGeometry())
self.settings.setValue("simulationWindow/state", self.saveState())
self.settings.setValue(
"simulationWindow/resultsVisible",
self.ui.actionToggleResults.isChecked(),
)
self.settings.sync()
def begin_run(self) -> tuple:
"""Reset transient run state while retaining the current workspace."""
self._run_generation += 1
generation = self._run_generation generation = self._run_generation
self._running = True self._running = True
self._file_path = None
self.results.status = {}
self.results.messages.clear()
self.results.metadata.clear()
self.ui.progressBar.setValue(0)
self.ui.timeLabel.setText("Time: 0 s")
self.ui.messageList.clear()
self.ui.statusLabel.setText("Preparing simulation…") self.ui.statusLabel.setText("Preparing simulation…")
return ( return (
lambda progress: self._report_progress(generation, progress), lambda progress: self._report_progress(generation, progress),
@@ -73,9 +131,18 @@ class SimulationWindow(QMainWindow):
lambda error: self._report_error(generation, error), lambda error: self._report_error(generation, error),
) )
def set_model_name(self, model_name: str | None) -> None: def prepare_run_model(self, model_name: str | None) -> None:
if model_name: """Retain graph configuration only when rerunning the same model."""
if not model_name:
return
if self.results.model_name != model_name:
self.results = SimulationResults(model_name=model_name)
self.clear_result_views()
self.load_result_views()
else:
self.results.model_name = model_name self.results.model_name = model_name
self.ui.statusLabel.setText("Preparing simulation…")
self._update_title() self._update_title()
def clear(self, checked: bool = False, *, model_name: str = "") -> None: def clear(self, checked: bool = False, *, model_name: str = "") -> None:
@@ -91,6 +158,7 @@ class SimulationWindow(QMainWindow):
self.ui.timeLabel.setText("Time: 0 s") self.ui.timeLabel.setText("Time: 0 s")
self.ui.messageList.clear() self.ui.messageList.clear()
self.clear_result_views() self.clear_result_views()
self.load_result_views()
self._update_title() self._update_title()
def clear_result_views(self) -> None: def clear_result_views(self) -> None:
@@ -100,9 +168,10 @@ class SimulationWindow(QMainWindow):
``resultsLayout`` and reset here. ``resultsLayout`` and reset here.
""" """
self.ui.resultsPlaceholder.setText( self._rebuilding_graph_tabs = True
"Simulation graphs and result controls can be added here." self._clear_graph_tab_widgets()
) self._rebuilding_graph_tabs = False
self.ui.signalsTree.clear()
def load_result_views(self) -> None: def load_result_views(self) -> None:
"""Populate custom plots from ``self.results.data`` and traces. """Populate custom plots from ``self.results.data`` and traces.
@@ -110,14 +179,206 @@ class SimulationWindow(QMainWindow):
This is the intended integration point for a future plotting widget. This is the intended integration point for a future plotting widget.
""" """
data_count = len(self.results.data) self._rebuild_graph_tabs()
trace_count = len(self.results.traces) self._rebuild_signal_tree()
if data_count or trace_count:
sample_count = len(next(iter(self.results.data.values()), [])) def _rebuild_graph_tabs(self) -> None:
self.ui.resultsPlaceholder.setText( current_page = self.ui.graphTabs.currentWidget()
f"{data_count} data column(s), {sample_count} sample(s), and " current_graph_id = (
f"{trace_count} configured trace(s) loaded; add graph rendering here." current_page.graph_id
if isinstance(current_page, GraphWorkspacePage)
else None
) )
self._rebuilding_graph_tabs = True
self._clear_graph_tab_widgets()
for graph in self.results.graphs:
self.ui.graphTabs.addTab(
GraphWorkspacePage(graph, self.results), graph.title
)
if current_graph_id is not None:
for index, graph in enumerate(self.results.graphs):
if graph.id == current_graph_id:
self.ui.graphTabs.setCurrentIndex(index)
break
self._rebuilding_graph_tabs = False
self._update_graph_actions()
self._sync_signal_checks()
def _clear_graph_tab_widgets(self) -> None:
while self.ui.graphTabs.count():
page = self.ui.graphTabs.widget(0)
self.ui.graphTabs.removeTab(0)
page.deleteLater()
def add_graph_tab(self) -> None:
used_titles = {graph.title for graph in self.results.graphs}
number = 1
while f"Graph {number}" in used_titles:
number += 1
graph = SimulationGraph(title=f"Graph {number}")
self.results.graphs.append(graph)
index = self.ui.graphTabs.addTab(
GraphWorkspacePage(graph, self.results), graph.title
)
self.ui.graphTabs.setCurrentIndex(index)
self._update_graph_actions()
def remove_current_graph_tab(self) -> None:
index = self.ui.graphTabs.currentIndex()
if index < 0 or index >= len(self.results.graphs):
return
self.results.graphs.pop(index)
page = self.ui.graphTabs.widget(index)
self.ui.graphTabs.removeTab(index)
page.deleteLater()
self._update_graph_actions()
self._sync_signal_checks()
def rename_graph_tab(self, index: int) -> None:
if index < 0 or index >= len(self.results.graphs):
return
graph = self.results.graphs[index]
title, accepted = QInputDialog.getText(
self, "Rename Graph", "Title:", text=graph.title
)
title = title.strip()
if accepted and title:
graph.title = title
self.ui.graphTabs.setTabText(index, title)
page = self.ui.graphTabs.widget(index)
if isinstance(page, GraphWorkspacePage):
page.refresh_chart()
def _move_graph_tab(self, old_index: int, new_index: int) -> None:
if self._rebuilding_graph_tabs or old_index == new_index:
return
graph = self.results.graphs.pop(old_index)
self.results.graphs.insert(new_index, graph)
self._sync_signal_checks()
def _update_graph_actions(self) -> None:
self.ui.actionRemoveGraph.setEnabled(bool(self.results.graphs))
def _rebuild_signal_tree(self) -> None:
"""Build a hierarchy while retaining each leaf's complete signal name."""
tree = self.ui.signalsTree
self._updating_signal_checks = True
try:
tree.clear()
items: dict[tuple[str, ...], QTreeWidgetItem] = {}
for signal_name in sorted(self.results.data, key=str.casefold):
parts = _signal_tree_parts(signal_name)
if not parts:
continue
parent = tree.invisibleRootItem()
for depth, part in enumerate(parts, start=1):
path = parts[:depth]
item = items.get(path)
if item is None:
item = QTreeWidgetItem(parent, [part])
items[path] = item
parent = item
parent.setData(0, Qt.ItemDataRole.UserRole, signal_name)
parent.setToolTip(0, signal_name)
parent.setFlags(parent.flags() | Qt.ItemFlag.ItemIsUserCheckable)
parent.setCheckState(0, Qt.CheckState.Unchecked)
tree.expandToDepth(0)
finally:
self._updating_signal_checks = False
self._sync_signal_checks()
def _current_graph_changed(self, _index: int) -> None:
if not self._rebuilding_graph_tabs:
self._sync_signal_checks()
def _current_graph(self) -> SimulationGraph | None:
index = self.ui.graphTabs.currentIndex()
if 0 <= index < len(self.results.graphs):
return self.results.graphs[index]
return None
def _sync_signal_checks(self) -> None:
graph = self._current_graph()
enabled = {trace.name for trace in graph.traces} if graph else set()
self._updating_signal_checks = True
try:
root = self.ui.signalsTree.invisibleRootItem()
pending = [root.child(index) for index in range(root.childCount())]
while pending:
item = pending.pop()
pending.extend(
item.child(index) for index in range(item.childCount())
)
signal_name = item.data(0, Qt.ItemDataRole.UserRole)
if isinstance(signal_name, str):
item.setCheckState(
0,
Qt.CheckState.Checked
if signal_name in enabled
else Qt.CheckState.Unchecked,
)
finally:
self._updating_signal_checks = False
self.ui.signalsTree.setEnabled(graph is not None)
def _signal_check_changed(self, item: QTreeWidgetItem, _column: int) -> None:
if self._updating_signal_checks:
return
signal_name = item.data(0, Qt.ItemDataRole.UserRole)
graph = self._current_graph()
if not isinstance(signal_name, str) or graph is None:
return
enabled = item.checkState(0) == Qt.CheckState.Checked
existing = next(
(trace for trace in graph.traces if trace.name == signal_name), None
)
if enabled and existing is None:
graph.traces.append(SimulationTrace(name=signal_name))
elif not enabled and existing is not None:
graph.traces.remove(existing)
page = self.ui.graphTabs.currentWidget()
if isinstance(page, GraphWorkspacePage):
page.refresh_chart()
def selected_signal_names(self) -> list[str]:
"""Return full column names selected for future plotting actions."""
names = []
for item in self.ui.signalsTree.selectedItems():
name = item.data(0, Qt.ItemDataRole.UserRole)
if isinstance(name, str):
names.append(name)
return names
def show_signal_context_menu(self, position) -> None:
item = self.ui.signalsTree.itemAt(position)
signal_name = (
item.data(0, Qt.ItemDataRole.UserRole) if item is not None else None
)
graph = self._current_graph()
if not isinstance(signal_name, str) or graph is None:
return
menu = QMenu(self)
use_as_x_action = menu.addAction("Use as X Axis")
use_as_x_action.setCheckable(True)
use_as_x_action.setChecked(graph.x_axis == signal_name)
selected = menu.exec(
self.ui.signalsTree.viewport().mapToGlobal(position)
)
if selected is use_as_x_action:
self.set_x_axis_signal(signal_name)
def set_x_axis_signal(self, signal_name: str) -> None:
"""Set the current graph's persisted horizontal data column."""
graph = self._current_graph()
if graph is None or signal_name not in self.results.data:
return
graph.x_axis = signal_name
page = self.ui.graphTabs.currentWidget()
if isinstance(page, GraphWorkspacePage):
page.refresh_chart()
def open_results(self) -> None: def open_results(self) -> None:
file_name, _selected_filter = QFileDialog.getOpenFileName( file_name, _selected_filter = QFileDialog.getOpenFileName(
@@ -231,6 +492,19 @@ class SimulationWindow(QMainWindow):
self.results.status.update(phase="Simulation finished", progress=10000) self.results.status.update(phase="Simulation finished", progress=10000)
if isinstance(result, SimulationExecutionResult): if isinstance(result, SimulationExecutionResult):
self.results.data = result.data self.results.data = result.data
available_signals = set(result.data)
for graph in self.results.graphs:
graph.traces = [
trace
for trace in graph.traces
if trace.name in available_signals
]
if graph.x_axis not in available_signals:
graph.x_axis = (
"time"
if "time" in available_signals
else next(iter(result.data), "")
)
self.results.metadata["processReturnCode"] = result.return_code self.results.metadata["processReturnCode"] = result.return_code
self.results.metadata["sourceResultFile"] = Path(result.result_file).name self.results.metadata["sourceResultFile"] = Path(result.result_file).name
else: else:
@@ -256,11 +530,197 @@ class SimulationWindow(QMainWindow):
"<p>View live progress and open or save simulation results.</p>", "<p>View live progress and open or save simulation results.</p>",
) )
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802 (Qt API name)
self._save_window_layout()
super().closeEvent(event)
@property @property
def is_running(self) -> bool: def is_running(self) -> bool:
return self._running return self._running
class GraphWorkspacePage(QWidget):
"""Matplotlib view of one persisted simulation graph definition."""
def __init__(
self, graph: SimulationGraph, results: SimulationResults, parent=None
) -> None:
super().__init__(parent)
self.graph_id = graph.id
self.graph = graph
self.results = results
self.plot_layout = QVBoxLayout(self)
self.figure = Figure(layout="constrained")
self.canvas = InteractiveFigureCanvas(self.figure)
self.axes = self.figure.add_subplot(111)
self.canvas.axes = self.axes
self.navigation_toolbar = GraphNavigationToolbar(self.canvas, self)
self.canvas.navigation_toolbar = self.navigation_toolbar
self.plot_layout.addWidget(self.navigation_toolbar)
self.plot_layout.addWidget(self.canvas)
self.refresh_chart()
def refresh_chart(self) -> None:
self.axes.clear()
x_values = self.results.data.get(self.graph.x_axis)
for trace in self.graph.traces:
y_values = self.results.data.get(trace.name)
if y_values is None:
continue
horizontal = x_values if x_values is not None else range(len(y_values))
sample_count = min(len(horizontal), len(y_values))
color = trace.properties.get("color")
self.axes.plot(
list(horizontal)[:sample_count],
y_values[:sample_count],
label=trace.name,
color=color if isinstance(color, str) and color else None,
)
self.axes.set_title(self.graph.title)
self.axes.set_xlabel(self.graph.x_axis if x_values is not None else "sample")
self.axes.grid(True, alpha=0.25)
if self.axes.lines:
self.axes.legend()
self.axes.relim()
self.axes.autoscale_view()
self.canvas.set_home_view()
self.canvas.draw_idle()
class GraphNavigationToolbar(NavigationToolbar2QT):
"""Navigation toolbar whose Home action includes direct canvas navigation."""
def home(self, *args) -> None:
del args
self.canvas.reset_home_view()
class InteractiveFigureCanvas(FigureCanvasQTAgg):
"""Matplotlib canvas with always-available wheel zoom and drag pan."""
def __init__(self, figure: Figure) -> None:
super().__init__(figure)
self.axes = None
self.navigation_toolbar = None
self._pan_start = None
self._home_view = None
self.mpl_connect("scroll_event", self._zoom_at_cursor)
self.mpl_connect("button_press_event", self._start_pan)
self.mpl_connect("motion_notify_event", self._pan)
self.mpl_connect("button_release_event", self._finish_pan)
def _toolbar_is_active(self) -> bool:
return bool(
self.navigation_toolbar is not None
and self.navigation_toolbar.mode
)
def set_home_view(self) -> None:
if self.axes is not None:
self._home_view = (self.axes.get_xlim(), self.axes.get_ylim())
def reset_home_view(self) -> None:
if self.axes is None or self._home_view is None:
return
x_limits, y_limits = self._home_view
self.axes.set_xlim(x_limits)
self.axes.set_ylim(y_limits)
self.draw_idle()
def _zoom_at_cursor(self, event) -> None:
if (
self.axes is None
or event.inaxes is not self.axes
or event.xdata is None
or event.ydata is None
or self._toolbar_is_active()
):
return
scale = 0.8 if event.button == "up" else 1.25
left, right = self.axes.get_xlim()
bottom, top = self.axes.get_ylim()
self.axes.set_xlim(
event.xdata - (event.xdata - left) * scale,
event.xdata + (right - event.xdata) * scale,
)
self.axes.set_ylim(
event.ydata - (event.ydata - bottom) * scale,
event.ydata + (top - event.ydata) * scale,
)
self.draw_idle()
def _start_pan(self, event) -> None:
if (
self.axes is None
or event.inaxes is not self.axes
or event.button != MouseButton.LEFT
or self._toolbar_is_active()
):
return
self._pan_start = (
event.x,
event.y,
self.axes.get_xlim(),
self.axes.get_ylim(),
)
def _pan(self, event) -> None:
if (
self.axes is None
or self._pan_start is None
or event.x is None
or event.y is None
):
return
start_x, start_y, x_limits, y_limits = self._pan_start
width = max(self.axes.bbox.width, 1.0)
height = max(self.axes.bbox.height, 1.0)
delta_x = (event.x - start_x) * (x_limits[1] - x_limits[0]) / width
delta_y = (event.y - start_y) * (y_limits[1] - y_limits[0]) / height
self.axes.set_xlim(x_limits[0] - delta_x, x_limits[1] - delta_x)
self.axes.set_ylim(y_limits[0] - delta_y, y_limits[1] - delta_y)
self.draw_idle()
def _finish_pan(self, _event) -> None:
self._pan_start = None
def _safe_file_stem(model_name: str) -> str: def _safe_file_stem(model_name: str) -> str:
stem = re.sub(r"[^A-Za-z0-9_.-]+", "_", model_name).strip("._") stem = re.sub(r"[^A-Za-z0-9_.-]+", "_", model_name).strip("._")
return stem or "simulation" return stem or "simulation"
def _signal_tree_parts(signal_name: str) -> tuple[str, ...]:
"""Split a result name into readable component, variable, and index levels.
OpenModelica emits state derivatives as names such as ``der(block.x)``.
Keep those traces available, but display them below a ``Derivatives`` group
on their owning component instead of creating a misleading top-level
``der(block`` branch.
"""
derivative = re.fullmatch(r"der\((.+)\)", signal_name)
if derivative is not None:
inner_name = derivative.group(1)
inner_parts = _signal_tree_parts(inner_name)
if inner_parts:
index_count = len(re.findall(r"\[[^\]]+\]", inner_name.rsplit(".", 1)[-1]))
variable_index = max(0, len(inner_parts) - index_count - 1)
return (
*inner_parts[:variable_index],
"Derivatives",
*inner_parts[variable_index:],
)
return ("Derivatives", signal_name)
parts: list[str] = []
for segment in signal_name.split("."):
if not segment:
continue
match = re.fullmatch(r"([^\[]+)((?:\[[^\]]+\])+)", segment)
if match is None:
parts.append(segment)
continue
parts.append(match.group(1))
parts.extend(re.findall(r"\[([^\]]+)\]", match.group(2)))
return tuple(parts)

View File

@@ -552,6 +552,7 @@
<addaction name="actionSimulationSettings"/> <addaction name="actionSimulationSettings"/>
<addaction name="actionGraphParameters"/> <addaction name="actionGraphParameters"/>
<addaction name="actionCompose"/> <addaction name="actionCompose"/>
<addaction name="actionExportModel"/>
<addaction name="actionSimulationWindow"/> <addaction name="actionSimulationWindow"/>
<addaction name="actionRunSimulation"/> <addaction name="actionRunSimulation"/>
</widget> </widget>
@@ -715,6 +716,18 @@
<string>Show the simulation results window</string> <string>Show the simulation results window</string>
</property> </property>
</action> </action>
<action name="actionExportModel">
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/document-save-as.png</normaloff>:/icons/icons/document-save-as.png</iconset>
</property>
<property name="text">
<string>Export Model…</string>
</property>
<property name="statusTip">
<string>Save the composed OpenModelica model to a file</string>
</property>
</action>
<action name="actionNew"> <action name="actionNew">
<property name="icon"> <property name="icon">
<iconset resource="../resources/resources.qrc"> <iconset resource="../resources/resources.qrc">

View File

@@ -20,12 +20,12 @@
<widget class="QWidget" name="centralWidget"> <widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="resultsLayout"> <layout class="QVBoxLayout" name="resultsLayout">
<item> <item>
<widget class="QLabel" name="resultsPlaceholder"> <widget class="QTabWidget" name="graphTabs">
<property name="text"> <property name="tabsClosable">
<string>Simulation graphs and result controls can be added here.</string> <bool>false</bool>
</property> </property>
<property name="alignment"> <property name="movable">
<set>Qt::AlignmentFlag::AlignCenter</set> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@@ -76,10 +76,34 @@
<addaction name="actionAbout"/> <addaction name="actionAbout"/>
<addaction name="actionAboutQt"/> <addaction name="actionAboutQt"/>
</widget> </widget>
<widget class="QMenu" name="menuGraph">
<property name="title">
<string>&amp;Graph</string>
</property>
<addaction name="actionAddGraph"/>
<addaction name="actionRemoveGraph"/>
</widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menuView"/> <addaction name="menuView"/>
<addaction name="menuGraph"/>
<addaction name="menuHelp"/> <addaction name="menuHelp"/>
</widget> </widget>
<widget class="QToolBar" name="workspaceToolbar">
<property name="windowTitle">
<string>Workspace</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonIconOnly</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionAddGraph"/>
<addaction name="actionRemoveGraph"/>
</widget>
<widget class="QToolBar" name="fileToolbar"> <widget class="QToolBar" name="fileToolbar">
<property name="windowTitle"> <property name="windowTitle">
<string>File</string> <string>File</string>
@@ -93,10 +117,10 @@
<attribute name="toolBarBreak"> <attribute name="toolBarBreak">
<bool>false</bool> <bool>false</bool>
</attribute> </attribute>
<addaction name="actionClear"/>
<addaction name="actionOpen"/> <addaction name="actionOpen"/>
<addaction name="actionSave"/> <addaction name="actionSave"/>
<addaction name="actionSaveAs"/> <addaction name="actionSaveAs"/>
<addaction name="actionClear"/>
</widget> </widget>
<widget class="QDockWidget" name="statusDock"> <widget class="QDockWidget" name="statusDock">
<property name="windowTitle"> <property name="windowTitle">
@@ -162,6 +186,36 @@
</layout> </layout>
</widget> </widget>
</widget> </widget>
<widget class="QDockWidget" name="signalsDock">
<property name="windowTitle">
<string>Signals</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="signalsDockContents">
<layout class="QVBoxLayout" name="signalsLayout">
<item>
<widget class="QTreeWidget" name="signalsTree">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::ExtendedSelection</enum>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Signal</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<action name="actionOpen"> <action name="actionOpen">
<property name="icon"> <property name="icon">
<iconset resource="../resources/resources.qrc"> <iconset resource="../resources/resources.qrc">
@@ -236,6 +290,30 @@
<string>Results</string> <string>Results</string>
</property> </property>
</action> </action>
<action name="actionAddGraph">
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/list-add.png</normaloff>:/icons/icons/list-add.png</iconset>
</property>
<property name="text">
<string>Add Graph</string>
</property>
<property name="statusTip">
<string>Add a graph workspace tab</string>
</property>
</action>
<action name="actionRemoveGraph">
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/list-remove.png</normaloff>:/icons/icons/list-remove.png</iconset>
</property>
<property name="text">
<string>Remove Current Graph</string>
</property>
<property name="statusTip">
<string>Remove the current graph workspace tab</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="../resources/resources.qrc"/> <include location="../resources/resources.qrc"/>

View File

@@ -12,12 +12,28 @@
<widget class="QSplitter" name="columnSplitter"> <widget class="QSplitter" name="columnSplitter">
<property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property> <property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property>
<property name="childrenCollapsible"><bool>false</bool></property> <property name="childrenCollapsible"><bool>false</bool></property>
<widget class="QSplitter" name="sourceSplitter">
<property name="orientation"><enum>Qt::Orientation::Vertical</enum></property>
<property name="childrenCollapsible"><bool>false</bool></property>
<widget class="QGroupBox" name="declarationsGroup">
<property name="title"><string>Declarations</string></property>
<layout class="QVBoxLayout" name="declarationsLayout">
<item><widget class="OpenModelicaEditor" name="declarationsEdit"/></item>
</layout>
</widget>
<widget class="QGroupBox" name="initialEquationsGroup">
<property name="title"><string>Initial Equations</string></property>
<layout class="QVBoxLayout" name="initialEquationsLayout">
<item><widget class="OpenModelicaEditor" name="initialEquationsEdit"/></item>
</layout>
</widget>
<widget class="QGroupBox" name="equationsGroup"> <widget class="QGroupBox" name="equationsGroup">
<property name="title"><string>Equations</string></property> <property name="title"><string>Equations</string></property>
<layout class="QVBoxLayout" name="equationsLayout"> <layout class="QVBoxLayout" name="equationsLayout">
<item><widget class="OpenModelicaEditor" name="equationsEdit"/></item> <item><widget class="OpenModelicaEditor" name="equationsEdit"/></item>
</layout> </layout>
</widget> </widget>
</widget>
<widget class="QSplitter" name="definitionSplitter"> <widget class="QSplitter" name="definitionSplitter">
<property name="orientation"><enum>Qt::Orientation::Vertical</enum></property> <property name="orientation"><enum>Qt::Orientation::Vertical</enum></property>
<property name="childrenCollapsible"><bool>false</bool></property> <property name="childrenCollapsible"><bool>false</bool></property>

View File

@@ -165,7 +165,9 @@
"implementation": { "implementation": {
"kind": "text", "kind": "text",
"source": { "source": {
"equations": "y = v;" "equations": "y = v;",
"declarations": "",
"initialEquations": ""
} }
} }
}, },
@@ -270,7 +272,9 @@
"implementation": { "implementation": {
"kind": "text", "kind": "text",
"source": { "source": {
"equations": "y = k*u;" "equations": "y = k*u;",
"declarations": "",
"initialEquations": ""
} }
} }
}, },
@@ -377,7 +381,9 @@
"implementation": { "implementation": {
"kind": "text", "kind": "text",
"source": { "source": {
"equations": "y = v+sin(time);" "equations": "y = v+sin(10*time);",
"declarations": "",
"initialEquations": ""
} }
} }
}, },
@@ -482,7 +488,9 @@
"implementation": { "implementation": {
"kind": "text", "kind": "text",
"source": { "source": {
"equations": "y = k*u;" "equations": "y = k*u;",
"declarations": "",
"initialEquations": ""
} }
} }
}, },
@@ -579,7 +587,255 @@
"implementation": { "implementation": {
"kind": "text", "kind": "text",
"source": { "source": {
"equations": "y = sum(u[i] for i in 1:$u_N$ );" "equations": "y = sum(u[i] for i in 1:$u_N$ );",
"declarations": "",
"initialEquations": ""
}
}
},
{
"id": "78c8fcb6-bd46-4b85-aa91-3276a8d1e8cb",
"name": "Integrate0",
"position": {
"x": 224.0,
"y": -96.0
},
"rotation": 0.0,
"interface": {
"inputs": [
{
"id": "port-df34ce84",
"name": "u",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 64.0,
"y": 64.0
}
},
"type": "signal",
"multipleConnections": false
}
],
"outputs": [
{
"id": "port-fe9e6486",
"name": "y",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 88.0,
"y": 40.0
}
},
"type": "signal",
"multipleConnections": false
}
]
},
"parameters": [
{
"id": "parameter-61a43a86",
"name": "y_start",
"type": "real",
"value": "0"
}
],
"icon": {
"shape": "rectangle",
"fill": "#f4f4f4",
"border": "#303030",
"text": "Text",
"size": {
"width": 128.0,
"height": 128.0
},
"elements": [
{
"cornerRadius": 5.0,
"fill": "#f4f4f4",
"height": 64.0,
"lineStyle": "solid",
"lineWidth": 1.5,
"stroke": "#303030",
"type": "rectangle",
"width": 64.0,
"x": 32.0,
"y": 32.0
},
{
"color": "#00007f",
"fill": "#ffffff",
"fontSize": 24.0,
"height": 48.0,
"lineStyle": "solid",
"lineWidth": 1.5,
"stroke": "#00007f",
"text": "dt",
"type": "text",
"width": 48.0,
"x": 48.0,
"y": 40.0
},
{
"type": "text",
"x": 32.0,
"y": 32.0,
"width": 32.0,
"height": 56.0,
"fill": "none",
"stroke": "#00007f",
"lineWidth": 1.5,
"lineStyle": "none",
"text": "\u222b",
"fontSize": 35.0,
"color": "#00007f"
}
]
},
"properties": {
"showName": true
},
"library": {
"showSubtree": true
},
"implementation": {
"kind": "text",
"source": {
"equations": "der(y) = u;",
"declarations": "",
"initialEquations": "y = y_start;"
}
}
},
{
"id": "f2caf59f-5688-46b4-b17d-bdc911580d7b",
"name": "Differentiate0",
"position": {
"x": 224.0,
"y": 32.0
},
"rotation": 0.0,
"interface": {
"inputs": [
{
"id": "port-df34ce84",
"name": "u",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 64.0,
"y": 64.0
}
},
"type": "signal",
"multipleConnections": false
}
],
"outputs": [
{
"id": "port-fe9e6486",
"name": "y",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 88.0,
"y": 40.0
}
},
"type": "signal",
"multipleConnections": false
}
]
},
"parameters": [
{
"id": "parameter-61a43a86",
"name": "x_start",
"type": "real",
"value": "0"
},
{
"id": "parameter-d110667b",
"name": "y_start",
"type": "real",
"value": "0"
},
{
"id": "parameter-873b503d",
"name": "k",
"type": "real",
"value": "1"
},
{
"id": "parameter-a55752c7",
"name": "T",
"type": "real",
"value": "0.01"
}
],
"icon": {
"shape": "rectangle",
"fill": "#f4f4f4",
"border": "#303030",
"text": "Text",
"size": {
"width": 128.0,
"height": 128.0
},
"elements": [
{
"cornerRadius": 5.0,
"fill": "#f4f4f4",
"height": 64.0,
"lineStyle": "solid",
"lineWidth": 1.5,
"stroke": "#303030",
"type": "rectangle",
"width": 64.0,
"x": 32.0,
"y": 32.0
},
{
"color": "#00007f",
"fill": "#ffffff",
"fontSize": 22.0,
"height": 48.0,
"lineStyle": "solid",
"lineWidth": 1.5,
"stroke": "#00007f",
"text": "d/dt",
"type": "text",
"width": 64.0,
"x": 32.0,
"y": 40.0
}
]
},
"properties": {
"showName": true
},
"library": {
"showSubtree": true
},
"implementation": {
"kind": "text",
"source": {
"equations": "assert(T > 0, \"Differentiate time constant T must be positive\");\n\nder(x) = (u - x)/T;\ny = (k/T)*(u - x);",
"declarations": "Real x(start=x_start);",
"initialEquations": "y = y_start;"
} }
} }
} }
@@ -644,6 +900,41 @@
"properties": { "properties": {
"waypoints": [] "waypoints": []
} }
},
{
"id": "33e15cc2-7f33-469e-91a8-921c04f6b823",
"source": {
"block": "59bb51be-8185-4b0b-a894-e38db61aff18",
"port": "port-a2f0dea6"
},
"target": {
"block": "78c8fcb6-bd46-4b85-aa91-3276a8d1e8cb",
"port": "port-df34ce84"
},
"name": "",
"properties": {
"waypoints": []
}
},
{
"id": "26e10b34-f168-4492-b4f2-fb46d8a4aafa",
"source": {
"block": "59bb51be-8185-4b0b-a894-e38db61aff18",
"port": "port-a2f0dea6"
},
"target": {
"block": "f2caf59f-5688-46b4-b17d-bdc911580d7b",
"port": "port-df34ce84"
},
"name": "",
"properties": {
"waypoints": [
{
"x": 160.0,
"y": 96.0
}
]
}
} }
], ],
"annotations": [], "annotations": [],