Compare commits
4 Commits
edd7bb98f2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 48ac9e2f59 | |||
| 2f6487e510 | |||
| eb8f77ce64 | |||
| 35d933ccbb |
11
BEdit/.vscode/tasks.json
vendored
11
BEdit/.vscode/tasks.json
vendored
@@ -115,6 +115,14 @@
|
||||
"options": {"cwd": "${workspaceFolder}"},
|
||||
"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",
|
||||
"dependsOrder": "sequence",
|
||||
@@ -125,7 +133,8 @@
|
||||
"Qt: Compile Component Options UI to Python",
|
||||
"Qt: Compile Port 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": [],
|
||||
"group": {
|
||||
|
||||
@@ -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$`
|
||||
expressions are highlighted as BEvalues. Highlight colors and bold/italic
|
||||
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
|
||||
window installs the Qt log-panel handler; core code must only use standard
|
||||
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
|
||||
`$portname_N$`; array connection endpoints receive stable one-based indices in
|
||||
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
|
||||
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
|
||||
@@ -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
|
||||
Designer-owned `resultsLayout` and the
|
||||
`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`.
|
||||
Its versioned schema retains model status, messages, metadata, and plottable
|
||||
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
|
||||
before temporary-directory cleanup. `SimulationResults.data` stores every CSV
|
||||
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
|
||||
`simulation/openModelicaPath`. The GUI passes it into `Simulation`; core must
|
||||
not read `QSettings`. An empty path uses OMPython/PATH discovery, while an
|
||||
|
||||
@@ -9,6 +9,7 @@ description = "A starter Qt desktop application"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"matplotlib>=3.8,<4",
|
||||
"msgpack>=1.0,<2",
|
||||
"PySide6>=6.7,<7",
|
||||
"OMPython>4.0",
|
||||
|
||||
BIN
BEdit/resources/icons/list-add.png
Normal file
BIN
BEdit/resources/icons/list-add.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
BEdit/resources/icons/list-remove.png
Normal file
BIN
BEdit/resources/icons/list-remove.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1,5 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons">
|
||||
<file>icons/list-remove.png</file>
|
||||
<file>icons/list-add.png</file>
|
||||
<file>icons/view-form-table.png</file>
|
||||
<file>icons/office-chart-line.png</file>
|
||||
<file>icons/run-build.png</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\
|
||||
\xd6\x06q(\x0e\x10\x00\x00\x00\x00IEND\xaeB\
|
||||
`\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\
|
||||
\x89\
|
||||
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\
|
||||
b/B\x06\x12o\xbf\x00\xa3\x17WYZq\xd9W\
|
||||
\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<\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
@@ -2649,6 +2813,10 @@ qt_resource_name = b"\
|
||||
\x0f\xba\x1e'\
|
||||
\x00d\
|
||||
\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\
|
||||
\x0b\xe6\x1f\xa7\
|
||||
\x00d\
|
||||
@@ -2723,6 +2891,10 @@ qt_resource_name = b"\
|
||||
\x00o\
|
||||
\x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\
|
||||
\x00.\x00p\x00n\x00g\
|
||||
\x00\x0c\
|
||||
\x09\xc6\x19'\
|
||||
\x00l\
|
||||
\x00i\x00s\x00t\x00-\x00a\x00d\x00d\x00.\x00p\x00n\x00g\
|
||||
\x00\x10\
|
||||
\x03\xe6\xd3g\
|
||||
\x00d\
|
||||
@@ -2749,67 +2921,71 @@ qt_resource_struct = b"\
|
||||
\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\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\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\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\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\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\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\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\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\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\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\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\x1c\x00\x00\x00\x00\x00\x01\x00\x00,\x08\
|
||||
\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\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\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\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\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\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\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x14M\
|
||||
\x00\x00\x01\x9f\x7fY\xce^\
|
||||
\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x19Y\
|
||||
\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\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\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\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00!\xc5\
|
||||
\x00\x00\x01\x9f{\x8d\xf34\
|
||||
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\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\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
|
||||
\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\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
|
||||
\x00\x00\x01\x9f\x7f\xac\xf2\xc6\
|
||||
\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00/{\
|
||||
\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\
|
||||
"
|
||||
|
||||
|
||||
@@ -344,11 +344,19 @@ class Component:
|
||||
if kind == "text":
|
||||
raw_source = implementation.get("source", {})
|
||||
equations = raw_source.get("equations", "")
|
||||
declarations = raw_source.get("declarations", "")
|
||||
initial_equations = raw_source.get("initialEquations", "")
|
||||
parameters = data.get("parameters", raw_source.get("parameters", []))
|
||||
if not isinstance(equations, str):
|
||||
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 = {
|
||||
"equations": equations,
|
||||
"declarations": declarations,
|
||||
"initialEquations": initial_equations,
|
||||
}
|
||||
if not isinstance(parameters, list):
|
||||
raise ValueError("Component parameters must be a list")
|
||||
|
||||
@@ -4,6 +4,7 @@ from bedit.core.simulation.results import (
|
||||
BerSimulationResultsSerializer,
|
||||
JsonSimulationResultsSerializer,
|
||||
SimulationExecutionResult,
|
||||
SimulationGraph,
|
||||
SimulationResults,
|
||||
SimulationResultsSerializer,
|
||||
SimulationTrace,
|
||||
@@ -14,6 +15,7 @@ __all__ = [
|
||||
"Simulation",
|
||||
"SimulationResults",
|
||||
"SimulationExecutionResult",
|
||||
"SimulationGraph",
|
||||
"SimulationResultsSerializer",
|
||||
"SimulationTrace",
|
||||
"BerSimulationResultsSerializer",
|
||||
|
||||
@@ -123,6 +123,14 @@ def emit_model(
|
||||
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":
|
||||
for block in nested_graph.get("blocks", []):
|
||||
block_type = model_name_for(block)
|
||||
@@ -134,8 +142,9 @@ def emit_model(
|
||||
f"{body_indent}{junction_type} {_junction_name(junction['id'])};"
|
||||
)
|
||||
|
||||
lines.append(f"{indentation}equation")
|
||||
if implementation_kind == "graph":
|
||||
lines.append(f"{indentation}equation")
|
||||
|
||||
blocks = {block["id"]: block for block in nested_graph.get("blocks", [])}
|
||||
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.
|
||||
lines.append(f"{body_indent}{target} = {source};")
|
||||
else:
|
||||
initial_equations = str(
|
||||
implementation.get("source", {}).get("initialEquations", "")
|
||||
)
|
||||
initial_equations = expand_bevalues(initial_equations, macros)
|
||||
equations = str(implementation.get("source", {}).get("equations", ""))
|
||||
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(
|
||||
f"{body_indent}{line}" if line.strip() else ""
|
||||
for line in equations.splitlines()
|
||||
@@ -161,7 +183,6 @@ def emit_model(
|
||||
lines.append(f"{indentation}end {model_name};")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def cleanup_graph(graph: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Remove annotations and UI-only data from a serialized component tree."""
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import zlib
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
import msgpack
|
||||
|
||||
@@ -25,6 +26,16 @@ class SimulationTrace:
|
||||
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
|
||||
class SimulationResults:
|
||||
"""Serializable state displayed by the standalone simulation window."""
|
||||
@@ -33,7 +44,9 @@ class SimulationResults:
|
||||
status: dict[str, Any] = field(default_factory=dict)
|
||||
messages: list[dict[str, str]] = field(default_factory=list)
|
||||
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)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
@@ -44,7 +57,7 @@ class SimulationResults:
|
||||
"status": dict(self.status),
|
||||
"messages": [dict(message) for message in self.messages],
|
||||
"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),
|
||||
}
|
||||
|
||||
@@ -55,7 +68,17 @@ class SimulationResults:
|
||||
if data.get("version") != RESULTS_VERSION:
|
||||
raise ValueError(f"Unsupported simulation-results version: {data.get('version')!r}")
|
||||
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(
|
||||
model_name=str(data.get("modelName", "")),
|
||||
status=dict(data.get("status", {})),
|
||||
@@ -64,10 +87,10 @@ class SimulationResults:
|
||||
str(name): [float(value) for value in values]
|
||||
for name, values in dict(data.get("data", {})).items()
|
||||
},
|
||||
traces=traces,
|
||||
graphs=graphs,
|
||||
metadata=dict(data.get("metadata", {})),
|
||||
)
|
||||
except (TypeError, ValueError) as error:
|
||||
except (KeyError, TypeError, ValueError) as error:
|
||||
raise ValueError("Malformed simulation-results data") from error
|
||||
|
||||
|
||||
|
||||
@@ -47,13 +47,7 @@ class Simulation:
|
||||
"""Compose and retain the active graph's Modelica representation."""
|
||||
|
||||
self.model_path = None
|
||||
|
||||
# Create openmodelica model
|
||||
result = compose_graph(graph)
|
||||
self.last_composition_input = result.graph
|
||||
self.id_list = result.objects_by_id
|
||||
self.last_composition_output = result.modelica
|
||||
self.model_name = result.model_name
|
||||
self.compose_source(graph)
|
||||
|
||||
def _model_compiled(result):
|
||||
log.info("Compiling OK: %s", result)
|
||||
@@ -79,6 +73,17 @@ class Simulation:
|
||||
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(
|
||||
self,
|
||||
graph: dict[str, Any],
|
||||
|
||||
@@ -168,7 +168,13 @@ class DocumentController(QObject):
|
||||
name=self._available_component_name(base_name, self.document.roots.values(), number),
|
||||
implementation_kind=kind,
|
||||
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.activate_component(component.id)
|
||||
@@ -187,7 +193,13 @@ class DocumentController(QObject):
|
||||
name=self._available_component_name(base_name, owner.graph.blocks.values(), number),
|
||||
implementation_kind=kind,
|
||||
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))
|
||||
return component.id
|
||||
@@ -427,6 +439,12 @@ class DocumentController(QObject):
|
||||
raise ValueError("Open a graph component before composing")
|
||||
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(
|
||||
self,
|
||||
progress_callback=None,
|
||||
@@ -636,6 +654,8 @@ class DocumentController(QObject):
|
||||
self,
|
||||
inputs: list[Port],
|
||||
outputs: list[Port],
|
||||
declarations: str,
|
||||
initial_equations: str,
|
||||
equations: str,
|
||||
parameters: list[Parameter],
|
||||
) -> None:
|
||||
@@ -682,6 +702,8 @@ class DocumentController(QObject):
|
||||
"outputs": [port.to_dict() for port in outputs],
|
||||
"source": {
|
||||
"equations": equations,
|
||||
"declarations": declarations,
|
||||
"initialEquations": initial_equations,
|
||||
},
|
||||
"parameters": [parameter.to_dict() for parameter in parameters],
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ PROPERTIES_ROLE = Qt.ItemDataRole.UserRole + 1
|
||||
|
||||
|
||||
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)
|
||||
definitionEdited = Signal()
|
||||
@@ -32,7 +32,10 @@ class TextDefinitionEditor(QWidget):
|
||||
QHeaderView.ResizeMode.Stretch
|
||||
)
|
||||
self.ui.columnSplitter.setSizes([560, 340])
|
||||
self.ui.sourceSplitter.setSizes([180, 180, 240])
|
||||
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.portsTable.cellChanged.connect(self._symbols_modified)
|
||||
self.ui.parametersTable.cellChanged.connect(self._symbols_modified)
|
||||
@@ -52,6 +55,14 @@ class TextDefinitionEditor(QWidget):
|
||||
def equations(self) -> str:
|
||||
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
|
||||
def ports(self) -> tuple[list[Port], list[Port]]:
|
||||
inputs: list[Port] = []
|
||||
@@ -87,12 +98,16 @@ class TextDefinitionEditor(QWidget):
|
||||
|
||||
def set_definition(
|
||||
self,
|
||||
declarations: str,
|
||||
initial_equations: str,
|
||||
equations: str,
|
||||
inputs: list[Port],
|
||||
outputs: list[Port],
|
||||
parameters: list[Parameter],
|
||||
) -> None:
|
||||
self._loading = True
|
||||
self.ui.declarationsEdit.setPlainText(declarations)
|
||||
self.ui.initialEquationsEdit.setPlainText(initial_equations)
|
||||
self.ui.equationsEdit.setPlainText(equations)
|
||||
self.ui.portsTable.setRowCount(0)
|
||||
for port in inputs:
|
||||
@@ -102,11 +117,7 @@ class TextDefinitionEditor(QWidget):
|
||||
self.ui.parametersTable.setRowCount(0)
|
||||
for parameter in parameters:
|
||||
self._append_parameter(parameter)
|
||||
self.ui.equationsEdit.set_symbols(
|
||||
[port.name for port in inputs],
|
||||
[port.name for port in outputs],
|
||||
[parameter.name for parameter in parameters],
|
||||
)
|
||||
self._set_editor_symbols(inputs, outputs, parameters)
|
||||
self._loading = False
|
||||
self.set_modified(False)
|
||||
self._update_buttons()
|
||||
@@ -128,11 +139,22 @@ class TextDefinitionEditor(QWidget):
|
||||
|
||||
def _refresh_editor_symbols(self) -> None:
|
||||
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 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:
|
||||
combo = QComboBox(self)
|
||||
|
||||
@@ -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\
|
||||
\xd6\x06q(\x0e\x10\x00\x00\x00\x00IEND\xaeB\
|
||||
`\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\
|
||||
\x89\
|
||||
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\
|
||||
b/B\x06\x12o\xbf\x00\xa3\x17WYZq\xd9W\
|
||||
\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<\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
@@ -2649,6 +2813,10 @@ qt_resource_name = b"\
|
||||
\x0f\xba\x1e'\
|
||||
\x00d\
|
||||
\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\
|
||||
\x0b\xe6\x1f\xa7\
|
||||
\x00d\
|
||||
@@ -2723,6 +2891,10 @@ qt_resource_name = b"\
|
||||
\x00o\
|
||||
\x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\
|
||||
\x00.\x00p\x00n\x00g\
|
||||
\x00\x0c\
|
||||
\x09\xc6\x19'\
|
||||
\x00l\
|
||||
\x00i\x00s\x00t\x00-\x00a\x00d\x00d\x00.\x00p\x00n\x00g\
|
||||
\x00\x10\
|
||||
\x03\xe6\xd3g\
|
||||
\x00d\
|
||||
@@ -2749,67 +2921,71 @@ qt_resource_struct = b"\
|
||||
\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\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\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\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\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\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\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\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\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\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\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\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\x1c\x00\x00\x00\x00\x00\x01\x00\x00,\x08\
|
||||
\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\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\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\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\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\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\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x14M\
|
||||
\x00\x00\x01\x9f\x7fY\xce^\
|
||||
\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x19Y\
|
||||
\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\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\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\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00!\xc5\
|
||||
\x00\x00\x01\x9f{\x8d\xf34\
|
||||
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\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\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
|
||||
\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\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
|
||||
\x00\x00\x01\x9f\x7f\xac\xf2\xc6\
|
||||
\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00/{\
|
||||
\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\
|
||||
"
|
||||
|
||||
|
||||
@@ -56,50 +56,53 @@ class Ui_MainWindow(object):
|
||||
icon4 = QIcon()
|
||||
icon4.addFile(u":/icons/icons/office-chart-line.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
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.setObjectName(u"actionNew")
|
||||
icon5 = QIcon()
|
||||
icon5.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionNew.setIcon(icon5)
|
||||
icon6 = QIcon()
|
||||
icon6.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionNew.setIcon(icon6)
|
||||
self.actionRotateClockwise = QAction(MainWindow)
|
||||
self.actionRotateClockwise.setObjectName(u"actionRotateClockwise")
|
||||
icon6 = QIcon()
|
||||
icon6.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionRotateClockwise.setIcon(icon6)
|
||||
icon7 = QIcon()
|
||||
icon7.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionRotateClockwise.setIcon(icon7)
|
||||
self.actionZoomIn = QAction(MainWindow)
|
||||
self.actionZoomIn.setObjectName(u"actionZoomIn")
|
||||
icon7 = QIcon()
|
||||
icon7.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionZoomIn.setIcon(icon7)
|
||||
icon8 = QIcon()
|
||||
icon8.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionZoomIn.setIcon(icon8)
|
||||
self.actionZoomOut = QAction(MainWindow)
|
||||
self.actionZoomOut.setObjectName(u"actionZoomOut")
|
||||
icon8 = QIcon()
|
||||
icon8.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionZoomOut.setIcon(icon8)
|
||||
icon9 = QIcon()
|
||||
icon9.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionZoomOut.setIcon(icon9)
|
||||
self.actionCenterView = QAction(MainWindow)
|
||||
self.actionCenterView.setObjectName(u"actionCenterView")
|
||||
icon9 = QIcon()
|
||||
icon9.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCenterView.setIcon(icon9)
|
||||
icon10 = QIcon()
|
||||
icon10.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCenterView.setIcon(icon10)
|
||||
self.actionOpen = QAction(MainWindow)
|
||||
self.actionOpen.setObjectName(u"actionOpen")
|
||||
icon10 = QIcon()
|
||||
icon10.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionOpen.setIcon(icon10)
|
||||
icon11 = QIcon()
|
||||
icon11.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionOpen.setIcon(icon11)
|
||||
self.actionReloadLibraries = QAction(MainWindow)
|
||||
self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
|
||||
self.actionReloadSimulation = QAction(MainWindow)
|
||||
self.actionReloadSimulation.setObjectName(u"actionReloadSimulation")
|
||||
self.actionSave = QAction(MainWindow)
|
||||
self.actionSave.setObjectName(u"actionSave")
|
||||
icon11 = QIcon()
|
||||
icon11.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSave.setIcon(icon11)
|
||||
icon12 = QIcon()
|
||||
icon12.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSave.setIcon(icon12)
|
||||
self.actionSaveAs = QAction(MainWindow)
|
||||
self.actionSaveAs.setObjectName(u"actionSaveAs")
|
||||
icon12 = QIcon()
|
||||
icon12.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSaveAs.setIcon(icon12)
|
||||
self.actionSaveAs.setIcon(icon5)
|
||||
self.actionExit = QAction(MainWindow)
|
||||
self.actionExit.setObjectName(u"actionExit")
|
||||
self.actionClose = QAction(MainWindow)
|
||||
@@ -295,7 +298,7 @@ class Ui_MainWindow(object):
|
||||
|
||||
self.rotateToolButton = QToolButton(self.workspaceHeader)
|
||||
self.rotateToolButton.setObjectName(u"rotateToolButton")
|
||||
self.rotateToolButton.setIcon(icon6)
|
||||
self.rotateToolButton.setIcon(icon7)
|
||||
|
||||
self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
|
||||
|
||||
@@ -441,6 +444,7 @@ class Ui_MainWindow(object):
|
||||
self.menuSimulation.addAction(self.actionSimulationSettings)
|
||||
self.menuSimulation.addAction(self.actionGraphParameters)
|
||||
self.menuSimulation.addAction(self.actionCompose)
|
||||
self.menuSimulation.addAction(self.actionExportModel)
|
||||
self.menuSimulation.addAction(self.actionSimulationWindow)
|
||||
self.menuSimulation.addAction(self.actionRunSimulation)
|
||||
self.fileToolbar.addAction(self.actionNew)
|
||||
@@ -496,6 +500,10 @@ class Ui_MainWindow(object):
|
||||
self.actionSimulationWindow.setText(QCoreApplication.translate("MainWindow", u"Simulation Window", None))
|
||||
#if QT_CONFIG(statustip)
|
||||
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)
|
||||
self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None))
|
||||
#if QT_CONFIG(statustip)
|
||||
|
||||
@@ -16,10 +16,11 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
|
||||
QIcon, QImage, QKeySequence, QLinearGradient,
|
||||
QPainter, QPalette, QPixmap, QRadialGradient,
|
||||
QTransform)
|
||||
from PySide6.QtWidgets import (QApplication, QDockWidget, QHBoxLayout, QLabel,
|
||||
QListWidget, QListWidgetItem, QMainWindow, QMenu,
|
||||
QMenuBar, QProgressBar, QSizePolicy, QToolBar,
|
||||
QVBoxLayout, QWidget)
|
||||
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QDockWidget, QHBoxLayout,
|
||||
QHeaderView, QLabel, QListWidget, QListWidgetItem,
|
||||
QMainWindow, QMenu, QMenuBar, QProgressBar,
|
||||
QSizePolicy, QTabWidget, QToolBar, QTreeWidget,
|
||||
QTreeWidgetItem, QVBoxLayout, QWidget)
|
||||
from . import resources_rc
|
||||
|
||||
class Ui_SimulationWindow(object):
|
||||
@@ -60,15 +61,26 @@ class Ui_SimulationWindow(object):
|
||||
self.actionToggleResults.setObjectName(u"actionToggleResults")
|
||||
self.actionToggleResults.setCheckable(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.setObjectName(u"centralWidget")
|
||||
self.resultsLayout = QVBoxLayout(self.centralWidget)
|
||||
self.resultsLayout.setObjectName(u"resultsLayout")
|
||||
self.resultsPlaceholder = QLabel(self.centralWidget)
|
||||
self.resultsPlaceholder.setObjectName(u"resultsPlaceholder")
|
||||
self.resultsPlaceholder.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.graphTabs = QTabWidget(self.centralWidget)
|
||||
self.graphTabs.setObjectName(u"graphTabs")
|
||||
self.graphTabs.setTabsClosable(False)
|
||||
self.graphTabs.setMovable(True)
|
||||
|
||||
self.resultsLayout.addWidget(self.resultsPlaceholder)
|
||||
self.resultsLayout.addWidget(self.graphTabs)
|
||||
|
||||
SimulationWindow.setCentralWidget(self.centralWidget)
|
||||
self.menuBar = QMenuBar(SimulationWindow)
|
||||
@@ -84,7 +96,13 @@ class Ui_SimulationWindow(object):
|
||||
self.menuToolbars.setObjectName(u"menuToolbars")
|
||||
self.menuHelp = QMenu(self.menuBar)
|
||||
self.menuHelp.setObjectName(u"menuHelp")
|
||||
self.menuGraph = QMenu(self.menuBar)
|
||||
self.menuGraph.setObjectName(u"menuGraph")
|
||||
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.setObjectName(u"fileToolbar")
|
||||
self.fileToolbar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||
@@ -130,9 +148,26 @@ class Ui_SimulationWindow(object):
|
||||
|
||||
self.logDock.setWidget(self.logDockContents)
|
||||
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.menuView.menuAction())
|
||||
self.menuBar.addAction(self.menuGraph.menuAction())
|
||||
self.menuBar.addAction(self.menuHelp.menuAction())
|
||||
self.menuFile.addAction(self.actionOpen)
|
||||
self.menuFile.addAction(self.actionSave)
|
||||
@@ -145,10 +180,14 @@ class Ui_SimulationWindow(object):
|
||||
self.menuView.addAction(self.menuToolbars.menuAction())
|
||||
self.menuHelp.addAction(self.actionAbout)
|
||||
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.actionSave)
|
||||
self.fileToolbar.addAction(self.actionSaveAs)
|
||||
self.fileToolbar.addAction(self.actionClear)
|
||||
|
||||
self.retranslateUi(SimulationWindow)
|
||||
|
||||
@@ -177,17 +216,29 @@ class Ui_SimulationWindow(object):
|
||||
self.actionAbout.setText(QCoreApplication.translate("SimulationWindow", u"&About Simulation Window", None))
|
||||
self.actionAboutQt.setText(QCoreApplication.translate("SimulationWindow", u"About &Qt", 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.menuView.setTitle(QCoreApplication.translate("SimulationWindow", u"&View", None))
|
||||
self.menuPanels.setTitle(QCoreApplication.translate("SimulationWindow", u"&Panels", None))
|
||||
self.menuToolbars.setTitle(QCoreApplication.translate("SimulationWindow", u"&Toolbars", 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.statusDock.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Status", None))
|
||||
self.timeLabel.setText(QCoreApplication.translate("SimulationWindow", u"Time: 0 s", 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.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
|
||||
|
||||
|
||||
@@ -34,7 +34,31 @@ class Ui_TextDefinitionEditor(object):
|
||||
self.columnSplitter.setObjectName(u"columnSplitter")
|
||||
self.columnSplitter.setOrientation(Qt.Orientation.Horizontal)
|
||||
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.equationsLayout = QVBoxLayout(self.equationsGroup)
|
||||
self.equationsLayout.setObjectName(u"equationsLayout")
|
||||
@@ -43,7 +67,8 @@ class Ui_TextDefinitionEditor(object):
|
||||
|
||||
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.setObjectName(u"definitionSplitter")
|
||||
self.definitionSplitter.setOrientation(Qt.Orientation.Vertical)
|
||||
@@ -141,6 +166,8 @@ class Ui_TextDefinitionEditor(object):
|
||||
# setupUi
|
||||
|
||||
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.portsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Ports", None))
|
||||
___qtablewidgetitem = self.portsTable.horizontalHeaderItem(0)
|
||||
|
||||
@@ -181,6 +181,7 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
self.ui.actionGraphParameters.triggered.connect(self.show_graph_parameters)
|
||||
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.actionRunSimulation.triggered.connect(self.run_simulation)
|
||||
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)
|
||||
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()
|
||||
def show_simulation_window(self) -> None:
|
||||
self._simulation_window.show()
|
||||
@@ -274,7 +304,7 @@ class MainWindow(QMainWindow):
|
||||
self.show_simulation_window()
|
||||
try:
|
||||
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:
|
||||
self.log.exception("Simulation run failed")
|
||||
window.report_start_error(error)
|
||||
@@ -307,6 +337,7 @@ class MainWindow(QMainWindow):
|
||||
self.ui.actionSimulationSettings.setEnabled(False)
|
||||
self.ui.actionGraphParameters.setEnabled(False)
|
||||
self.ui.actionCompose.setEnabled(False)
|
||||
self.ui.actionExportModel.setEnabled(False)
|
||||
self.ui.actionRunSimulation.setEnabled(False)
|
||||
self._update_edit_actions()
|
||||
return
|
||||
@@ -318,6 +349,7 @@ class MainWindow(QMainWindow):
|
||||
self.ui.actionSimulationSettings.setEnabled(is_graph)
|
||||
self.ui.actionGraphParameters.setEnabled(is_graph)
|
||||
self.ui.actionCompose.setEnabled(is_graph)
|
||||
self.ui.actionExportModel.setEnabled(is_graph)
|
||||
self.ui.actionRunSimulation.setEnabled(is_graph)
|
||||
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
|
||||
self.ui.workspaceStack.setCurrentWidget(
|
||||
@@ -505,6 +537,8 @@ class MainWindow(QMainWindow):
|
||||
if component is None:
|
||||
return
|
||||
self.ui.textDefinitionEditor.set_definition(
|
||||
component.source.get("declarations", ""),
|
||||
component.source.get("initialEquations", ""),
|
||||
component.source.get("equations", ""),
|
||||
component.inputs,
|
||||
component.outputs,
|
||||
@@ -522,7 +556,8 @@ class MainWindow(QMainWindow):
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
"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.Discard
|
||||
| QMessageBox.StandardButton.Cancel,
|
||||
@@ -540,6 +575,8 @@ class MainWindow(QMainWindow):
|
||||
self.document_controller.replace_active_text_definition(
|
||||
inputs,
|
||||
outputs,
|
||||
self.ui.textDefinitionEditor.declarations,
|
||||
self.ui.textDefinitionEditor.initial_equations,
|
||||
self.ui.textDefinitionEditor.equations,
|
||||
self.ui.textDefinitionEditor.parameters,
|
||||
)
|
||||
@@ -672,6 +709,8 @@ class MainWindow(QMainWindow):
|
||||
if scene is not None:
|
||||
scene.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()
|
||||
|
||||
def _document_opened_changed(self, opened: bool) -> None:
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
import re
|
||||
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.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.results import (
|
||||
SimulationExecutionResult,
|
||||
SimulationGraph,
|
||||
SimulationResults,
|
||||
SimulationTrace,
|
||||
load_simulation_results,
|
||||
save_simulation_results,
|
||||
)
|
||||
from bedit.gui.generated.ui_simulation_window import Ui_SimulationWindow
|
||||
from bedit.gui.preferences import application_settings
|
||||
|
||||
|
||||
class SimulationWindow(QMainWindow):
|
||||
@@ -26,9 +42,12 @@ class SimulationWindow(QMainWindow):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_SimulationWindow()
|
||||
self.ui.setupUi(self)
|
||||
self.settings = application_settings()
|
||||
self._running = False
|
||||
self._run_generation = 0
|
||||
self._file_path: Path | None = None
|
||||
self._rebuilding_graph_tabs = False
|
||||
self._updating_signal_checks = False
|
||||
self.results = SimulationResults()
|
||||
self._connect_actions()
|
||||
self._populate_view_menu()
|
||||
@@ -36,6 +55,7 @@ class SimulationWindow(QMainWindow):
|
||||
self.ui.statusDock, self.ui.logDock, Qt.Orientation.Vertical
|
||||
)
|
||||
self.ui.statusDock.setFixedHeight(self.ui.statusDock.sizeHint().height())
|
||||
self._restore_window_layout()
|
||||
self.progressReceived.connect(self._show_progress)
|
||||
self.messageReceived.connect(self._show_message)
|
||||
self.simulationFinished.connect(self._show_finished)
|
||||
@@ -52,19 +72,57 @@ class SimulationWindow(QMainWindow):
|
||||
lambda: QMessageBox.aboutQt(self, "About Qt Framework")
|
||||
)
|
||||
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:
|
||||
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.menuToolbars.addAction(self.ui.fileToolbar.toggleViewAction())
|
||||
self.ui.menuToolbars.addAction(self.ui.workspaceToolbar.toggleViewAction())
|
||||
|
||||
def begin_run(self, model_name: str = "") -> tuple:
|
||||
"""Reset the viewer and return callbacks bound to this run."""
|
||||
def _restore_window_layout(self) -> None:
|
||||
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
|
||||
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…")
|
||||
return (
|
||||
lambda progress: self._report_progress(generation, progress),
|
||||
@@ -73,9 +131,18 @@ class SimulationWindow(QMainWindow):
|
||||
lambda error: self._report_error(generation, error),
|
||||
)
|
||||
|
||||
def set_model_name(self, model_name: str | None) -> None:
|
||||
if model_name:
|
||||
def prepare_run_model(self, model_name: str | None) -> None:
|
||||
"""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.ui.statusLabel.setText("Preparing simulation…")
|
||||
self._update_title()
|
||||
|
||||
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.messageList.clear()
|
||||
self.clear_result_views()
|
||||
self.load_result_views()
|
||||
self._update_title()
|
||||
|
||||
def clear_result_views(self) -> None:
|
||||
@@ -100,9 +168,10 @@ class SimulationWindow(QMainWindow):
|
||||
``resultsLayout`` and reset here.
|
||||
"""
|
||||
|
||||
self.ui.resultsPlaceholder.setText(
|
||||
"Simulation graphs and result controls can be added here."
|
||||
)
|
||||
self._rebuilding_graph_tabs = True
|
||||
self._clear_graph_tab_widgets()
|
||||
self._rebuilding_graph_tabs = False
|
||||
self.ui.signalsTree.clear()
|
||||
|
||||
def load_result_views(self) -> None:
|
||||
"""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.
|
||||
"""
|
||||
|
||||
data_count = len(self.results.data)
|
||||
trace_count = len(self.results.traces)
|
||||
if data_count or trace_count:
|
||||
sample_count = len(next(iter(self.results.data.values()), []))
|
||||
self.ui.resultsPlaceholder.setText(
|
||||
f"{data_count} data column(s), {sample_count} sample(s), and "
|
||||
f"{trace_count} configured trace(s) loaded; add graph rendering here."
|
||||
self._rebuild_graph_tabs()
|
||||
self._rebuild_signal_tree()
|
||||
|
||||
def _rebuild_graph_tabs(self) -> None:
|
||||
current_page = self.ui.graphTabs.currentWidget()
|
||||
current_graph_id = (
|
||||
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:
|
||||
file_name, _selected_filter = QFileDialog.getOpenFileName(
|
||||
@@ -231,6 +492,19 @@ class SimulationWindow(QMainWindow):
|
||||
self.results.status.update(phase="Simulation finished", progress=10000)
|
||||
if isinstance(result, SimulationExecutionResult):
|
||||
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["sourceResultFile"] = Path(result.result_file).name
|
||||
else:
|
||||
@@ -256,11 +530,197 @@ class SimulationWindow(QMainWindow):
|
||||
"<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
|
||||
def is_running(self) -> bool:
|
||||
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:
|
||||
stem = re.sub(r"[^A-Za-z0-9_.-]+", "_", model_name).strip("._")
|
||||
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)
|
||||
|
||||
@@ -552,6 +552,7 @@
|
||||
<addaction name="actionSimulationSettings"/>
|
||||
<addaction name="actionGraphParameters"/>
|
||||
<addaction name="actionCompose"/>
|
||||
<addaction name="actionExportModel"/>
|
||||
<addaction name="actionSimulationWindow"/>
|
||||
<addaction name="actionRunSimulation"/>
|
||||
</widget>
|
||||
@@ -715,6 +716,18 @@
|
||||
<string>Show the simulation results window</string>
|
||||
</property>
|
||||
</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">
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<layout class="QVBoxLayout" name="resultsLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="resultsPlaceholder">
|
||||
<property name="text">
|
||||
<string>Simulation graphs and result controls can be added here.</string>
|
||||
<widget class="QTabWidget" name="graphTabs">
|
||||
<property name="tabsClosable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
<property name="movable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -76,10 +76,34 @@
|
||||
<addaction name="actionAbout"/>
|
||||
<addaction name="actionAboutQt"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuGraph">
|
||||
<property name="title">
|
||||
<string>&Graph</string>
|
||||
</property>
|
||||
<addaction name="actionAddGraph"/>
|
||||
<addaction name="actionRemoveGraph"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuView"/>
|
||||
<addaction name="menuGraph"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</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">
|
||||
<property name="windowTitle">
|
||||
<string>File</string>
|
||||
@@ -93,10 +117,10 @@
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionClear"/>
|
||||
<addaction name="actionOpen"/>
|
||||
<addaction name="actionSave"/>
|
||||
<addaction name="actionSaveAs"/>
|
||||
<addaction name="actionClear"/>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="statusDock">
|
||||
<property name="windowTitle">
|
||||
@@ -162,6 +186,36 @@
|
||||
</layout>
|
||||
</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">
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
@@ -236,6 +290,30 @@
|
||||
<string>Results</string>
|
||||
</property>
|
||||
</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>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
|
||||
@@ -12,12 +12,28 @@
|
||||
<widget class="QSplitter" name="columnSplitter">
|
||||
<property name="orientation"><enum>Qt::Orientation::Horizontal</enum></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">
|
||||
<property name="title"><string>Equations</string></property>
|
||||
<layout class="QVBoxLayout" name="equationsLayout">
|
||||
<item><widget class="OpenModelicaEditor" name="equationsEdit"/></item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QSplitter" name="definitionSplitter">
|
||||
<property name="orientation"><enum>Qt::Orientation::Vertical</enum></property>
|
||||
<property name="childrenCollapsible"><bool>false</bool></property>
|
||||
|
||||
@@ -165,7 +165,9 @@
|
||||
"implementation": {
|
||||
"kind": "text",
|
||||
"source": {
|
||||
"equations": "y = v;"
|
||||
"equations": "y = v;",
|
||||
"declarations": "",
|
||||
"initialEquations": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -270,7 +272,9 @@
|
||||
"implementation": {
|
||||
"kind": "text",
|
||||
"source": {
|
||||
"equations": "y = k*u;"
|
||||
"equations": "y = k*u;",
|
||||
"declarations": "",
|
||||
"initialEquations": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -377,7 +381,9 @@
|
||||
"implementation": {
|
||||
"kind": "text",
|
||||
"source": {
|
||||
"equations": "y = v+sin(time);"
|
||||
"equations": "y = v+sin(10*time);",
|
||||
"declarations": "",
|
||||
"initialEquations": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -482,7 +488,9 @@
|
||||
"implementation": {
|
||||
"kind": "text",
|
||||
"source": {
|
||||
"equations": "y = k*u;"
|
||||
"equations": "y = k*u;",
|
||||
"declarations": "",
|
||||
"initialEquations": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -579,7 +587,255 @@
|
||||
"implementation": {
|
||||
"kind": "text",
|
||||
"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": {
|
||||
"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": [],
|
||||
|
||||
Reference in New Issue
Block a user