Added signal graphing

This commit is contained in:
2026-07-21 15:14:12 +02:00
parent edd7bb98f2
commit 35d933ccbb
13 changed files with 873 additions and 78 deletions

View File

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

View File

@@ -139,6 +139,16 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
- After a successful OpenModelica run, `<model>_res.csv` is parsed on the worker - After a successful OpenModelica run, `<model>_res.csv` is parsed on the worker
before temporary-directory cleanup. `SimulationResults.data` stores every CSV before temporary-directory cleanup. `SimulationResults.data` stores every CSV
column as a numeric array, including `time`, for later plotting and persistence. column as a numeric array, including `time`, for later plotting and persistence.
- The simulation window's dockable Signals tree derives hierarchy from dot-separated
result-column names and bracketed array indices (`a[1]` becomes `a → 1`). Leaf
items retain the exact full column name in `UserRole`; plotting code should
consume `SimulationWindow.selected_signal_names()`.
- Simulation graph tabs persist as `SimulationResults.graphs`; every graph has a
stable ID, editable title, and its own `traces` list. Runtime graph widgets belong
in `GraphWorkspacePage.plot_layout`, not in the serialized core model. The
Signals tree checkboxes edit the active graph's traces, and each page embeds a
Matplotlib QtAgg canvas. Each graph persists its own `x_axis` signal (default
`time`), selectable from the Signals tree context menu.
- The optional OpenModelica executable is persisted as - The optional OpenModelica executable is persisted as
`simulation/openModelicaPath`. The GUI passes it into `Simulation`; core must `simulation/openModelicaPath`. The GUI passes it into `Simulation`; core must
not read `QSettings`. An empty path uses OMPython/PATH discovery, while an not read `QSettings`. An empty path uses OMPython/PATH discovery, while an

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,26 @@
import re import re
from pathlib import Path from pathlib import Path
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg, NavigationToolbar2QT
from matplotlib.figure import Figure
from PySide6.QtCore import Qt, Signal from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QFileDialog, QMainWindow, QMessageBox from PySide6.QtWidgets import (
QFileDialog,
QInputDialog,
QMainWindow,
QMenu,
QMessageBox,
QTreeWidgetItem,
QVBoxLayout,
QWidget,
)
from bedit.core.simulation.openmodelica import SimulationMessage, SimulationProgress from bedit.core.simulation.openmodelica import SimulationMessage, SimulationProgress
from bedit.core.simulation.results import ( from bedit.core.simulation.results import (
SimulationExecutionResult, SimulationExecutionResult,
SimulationGraph,
SimulationResults, SimulationResults,
SimulationTrace,
load_simulation_results, load_simulation_results,
save_simulation_results, save_simulation_results,
) )
@@ -29,6 +42,8 @@ class SimulationWindow(QMainWindow):
self._running = False self._running = False
self._run_generation = 0 self._run_generation = 0
self._file_path: Path | None = None self._file_path: Path | None = None
self._rebuilding_graph_tabs = False
self._updating_signal_checks = False
self.results = SimulationResults() self.results = SimulationResults()
self._connect_actions() self._connect_actions()
self._populate_view_menu() self._populate_view_menu()
@@ -52,12 +67,22 @@ class SimulationWindow(QMainWindow):
lambda: QMessageBox.aboutQt(self, "About Qt Framework") lambda: QMessageBox.aboutQt(self, "About Qt Framework")
) )
self.ui.actionToggleResults.toggled.connect(self.ui.centralWidget.setVisible) self.ui.actionToggleResults.toggled.connect(self.ui.centralWidget.setVisible)
self.ui.actionAddGraph.triggered.connect(self.add_graph_tab)
self.ui.actionRemoveGraph.triggered.connect(self.remove_current_graph_tab)
self.ui.graphTabs.tabBarDoubleClicked.connect(self.rename_graph_tab)
self.ui.graphTabs.tabBar().tabMoved.connect(self._move_graph_tab)
self.ui.graphTabs.currentChanged.connect(self._current_graph_changed)
self.ui.signalsTree.itemChanged.connect(self._signal_check_changed)
self.ui.signalsTree.customContextMenuRequested.connect(
self.show_signal_context_menu
)
def _populate_view_menu(self) -> None: def _populate_view_menu(self) -> None:
self.ui.menuPanels.addAction(self.ui.actionToggleResults) self.ui.menuPanels.addAction(self.ui.actionToggleResults)
for panel in (self.ui.statusDock, self.ui.logDock): for panel in (self.ui.statusDock, self.ui.logDock, self.ui.signalsDock):
self.ui.menuPanels.addAction(panel.toggleViewAction()) self.ui.menuPanels.addAction(panel.toggleViewAction())
self.ui.menuToolbars.addAction(self.ui.fileToolbar.toggleViewAction()) self.ui.menuToolbars.addAction(self.ui.fileToolbar.toggleViewAction())
self.ui.menuToolbars.addAction(self.ui.workspaceToolbar.toggleViewAction())
def begin_run(self, model_name: str = "") -> tuple: def begin_run(self, model_name: str = "") -> tuple:
"""Reset the viewer and return callbacks bound to this run.""" """Reset the viewer and return callbacks bound to this run."""
@@ -91,6 +116,7 @@ class SimulationWindow(QMainWindow):
self.ui.timeLabel.setText("Time: 0 s") self.ui.timeLabel.setText("Time: 0 s")
self.ui.messageList.clear() self.ui.messageList.clear()
self.clear_result_views() self.clear_result_views()
self.load_result_views()
self._update_title() self._update_title()
def clear_result_views(self) -> None: def clear_result_views(self) -> None:
@@ -100,9 +126,10 @@ class SimulationWindow(QMainWindow):
``resultsLayout`` and reset here. ``resultsLayout`` and reset here.
""" """
self.ui.resultsPlaceholder.setText( self._rebuilding_graph_tabs = True
"Simulation graphs and result controls can be added here." self._clear_graph_tab_widgets()
) self._rebuilding_graph_tabs = False
self.ui.signalsTree.clear()
def load_result_views(self) -> None: def load_result_views(self) -> None:
"""Populate custom plots from ``self.results.data`` and traces. """Populate custom plots from ``self.results.data`` and traces.
@@ -110,14 +137,195 @@ class SimulationWindow(QMainWindow):
This is the intended integration point for a future plotting widget. This is the intended integration point for a future plotting widget.
""" """
data_count = len(self.results.data) self._rebuild_graph_tabs()
trace_count = len(self.results.traces) self._rebuild_signal_tree()
if data_count or trace_count:
sample_count = len(next(iter(self.results.data.values()), [])) def _rebuild_graph_tabs(self) -> None:
self.ui.resultsPlaceholder.setText( self._rebuilding_graph_tabs = True
f"{data_count} data column(s), {sample_count} sample(s), and " self._clear_graph_tab_widgets()
f"{trace_count} configured trace(s) loaded; add graph rendering here." for graph in self.results.graphs:
self.ui.graphTabs.addTab(
GraphWorkspacePage(graph, self.results), graph.title
) )
self._rebuilding_graph_tabs = False
self._update_graph_actions()
self._sync_signal_checks()
def _clear_graph_tab_widgets(self) -> None:
while self.ui.graphTabs.count():
page = self.ui.graphTabs.widget(0)
self.ui.graphTabs.removeTab(0)
page.deleteLater()
def add_graph_tab(self) -> None:
used_titles = {graph.title for graph in self.results.graphs}
number = 1
while f"Graph {number}" in used_titles:
number += 1
graph = SimulationGraph(title=f"Graph {number}")
self.results.graphs.append(graph)
index = self.ui.graphTabs.addTab(
GraphWorkspacePage(graph, self.results), graph.title
)
self.ui.graphTabs.setCurrentIndex(index)
self._update_graph_actions()
def remove_current_graph_tab(self) -> None:
index = self.ui.graphTabs.currentIndex()
if index < 0 or index >= len(self.results.graphs):
return
self.results.graphs.pop(index)
page = self.ui.graphTabs.widget(index)
self.ui.graphTabs.removeTab(index)
page.deleteLater()
self._update_graph_actions()
self._sync_signal_checks()
def rename_graph_tab(self, index: int) -> None:
if index < 0 or index >= len(self.results.graphs):
return
graph = self.results.graphs[index]
title, accepted = QInputDialog.getText(
self, "Rename Graph", "Title:", text=graph.title
)
title = title.strip()
if accepted and title:
graph.title = title
self.ui.graphTabs.setTabText(index, title)
page = self.ui.graphTabs.widget(index)
if isinstance(page, GraphWorkspacePage):
page.refresh_chart()
def _move_graph_tab(self, old_index: int, new_index: int) -> None:
if self._rebuilding_graph_tabs or old_index == new_index:
return
graph = self.results.graphs.pop(old_index)
self.results.graphs.insert(new_index, graph)
self._sync_signal_checks()
def _update_graph_actions(self) -> None:
self.ui.actionRemoveGraph.setEnabled(bool(self.results.graphs))
def _rebuild_signal_tree(self) -> None:
"""Build a hierarchy while retaining each leaf's complete signal name."""
tree = self.ui.signalsTree
self._updating_signal_checks = True
try:
tree.clear()
items: dict[tuple[str, ...], QTreeWidgetItem] = {}
for signal_name in sorted(self.results.data, key=str.casefold):
parts = _signal_tree_parts(signal_name)
if not parts:
continue
parent = tree.invisibleRootItem()
for depth, part in enumerate(parts, start=1):
path = parts[:depth]
item = items.get(path)
if item is None:
item = QTreeWidgetItem(parent, [part])
items[path] = item
parent = item
parent.setData(0, Qt.ItemDataRole.UserRole, signal_name)
parent.setToolTip(0, signal_name)
parent.setFlags(parent.flags() | Qt.ItemFlag.ItemIsUserCheckable)
parent.setCheckState(0, Qt.CheckState.Unchecked)
tree.expandToDepth(0)
finally:
self._updating_signal_checks = False
self._sync_signal_checks()
def _current_graph_changed(self, _index: int) -> None:
if not self._rebuilding_graph_tabs:
self._sync_signal_checks()
def _current_graph(self) -> SimulationGraph | None:
index = self.ui.graphTabs.currentIndex()
if 0 <= index < len(self.results.graphs):
return self.results.graphs[index]
return None
def _sync_signal_checks(self) -> None:
graph = self._current_graph()
enabled = {trace.name for trace in graph.traces} if graph else set()
self._updating_signal_checks = True
try:
root = self.ui.signalsTree.invisibleRootItem()
pending = [root.child(index) for index in range(root.childCount())]
while pending:
item = pending.pop()
pending.extend(
item.child(index) for index in range(item.childCount())
)
signal_name = item.data(0, Qt.ItemDataRole.UserRole)
if isinstance(signal_name, str):
item.setCheckState(
0,
Qt.CheckState.Checked
if signal_name in enabled
else Qt.CheckState.Unchecked,
)
finally:
self._updating_signal_checks = False
self.ui.signalsTree.setEnabled(graph is not None)
def _signal_check_changed(self, item: QTreeWidgetItem, _column: int) -> None:
if self._updating_signal_checks:
return
signal_name = item.data(0, Qt.ItemDataRole.UserRole)
graph = self._current_graph()
if not isinstance(signal_name, str) or graph is None:
return
enabled = item.checkState(0) == Qt.CheckState.Checked
existing = next(
(trace for trace in graph.traces if trace.name == signal_name), None
)
if enabled and existing is None:
graph.traces.append(SimulationTrace(name=signal_name))
elif not enabled and existing is not None:
graph.traces.remove(existing)
page = self.ui.graphTabs.currentWidget()
if isinstance(page, GraphWorkspacePage):
page.refresh_chart()
def selected_signal_names(self) -> list[str]:
"""Return full column names selected for future plotting actions."""
names = []
for item in self.ui.signalsTree.selectedItems():
name = item.data(0, Qt.ItemDataRole.UserRole)
if isinstance(name, str):
names.append(name)
return names
def show_signal_context_menu(self, position) -> None:
item = self.ui.signalsTree.itemAt(position)
signal_name = (
item.data(0, Qt.ItemDataRole.UserRole) if item is not None else None
)
graph = self._current_graph()
if not isinstance(signal_name, str) or graph is None:
return
menu = QMenu(self)
use_as_x_action = menu.addAction("Use as X Axis")
use_as_x_action.setCheckable(True)
use_as_x_action.setChecked(graph.x_axis == signal_name)
selected = menu.exec(
self.ui.signalsTree.viewport().mapToGlobal(position)
)
if selected is use_as_x_action:
self.set_x_axis_signal(signal_name)
def set_x_axis_signal(self, signal_name: str) -> None:
"""Set the current graph's persisted horizontal data column."""
graph = self._current_graph()
if graph is None or signal_name not in self.results.data:
return
graph.x_axis = signal_name
page = self.ui.graphTabs.currentWidget()
if isinstance(page, GraphWorkspacePage):
page.refresh_chart()
def open_results(self) -> None: def open_results(self) -> None:
file_name, _selected_filter = QFileDialog.getOpenFileName( file_name, _selected_filter = QFileDialog.getOpenFileName(
@@ -261,6 +469,65 @@ class SimulationWindow(QMainWindow):
return self._running return self._running
class GraphWorkspacePage(QWidget):
"""Matplotlib view of one persisted simulation graph definition."""
def __init__(
self, graph: SimulationGraph, results: SimulationResults, parent=None
) -> None:
super().__init__(parent)
self.graph_id = graph.id
self.graph = graph
self.results = results
self.plot_layout = QVBoxLayout(self)
self.figure = Figure(layout="constrained")
self.canvas = FigureCanvasQTAgg(self.figure)
self.axes = self.figure.add_subplot(111)
self.navigation_toolbar = NavigationToolbar2QT(self.canvas, self)
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.canvas.draw_idle()
def _safe_file_stem(model_name: str) -> str: def _safe_file_stem(model_name: str) -> str:
stem = re.sub(r"[^A-Za-z0-9_.-]+", "_", model_name).strip("._") stem = re.sub(r"[^A-Za-z0-9_.-]+", "_", model_name).strip("._")
return stem or "simulation" return stem or "simulation"
def _signal_tree_parts(signal_name: str) -> tuple[str, ...]:
"""Split dots and array indices into display hierarchy segments."""
parts: list[str] = []
for segment in signal_name.split("."):
if not segment:
continue
match = re.fullmatch(r"([^\[]+)((?:\[[^\]]+\])+)", segment)
if match is None:
parts.append(segment)
continue
parts.append(match.group(1))
parts.extend(re.findall(r"\[([^\]]+)\]", match.group(2)))
return tuple(parts)

View File

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