Compare commits
4 Commits
dc770a0886
...
edd7bb98f2
| Author | SHA1 | Date | |
|---|---|---|---|
| edd7bb98f2 | |||
| ddc004dee5 | |||
| cdfc891980 | |||
| 6fb2478589 |
1
BEdit/.vscode/tasks.json
vendored
1
BEdit/.vscode/tasks.json
vendored
@@ -6,7 +6,6 @@
|
|||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pyside6-designer",
|
"command": "pyside6-designer",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceFolder}/ui/*.ui"
|
|
||||||
],
|
],
|
||||||
"options": {
|
"options": {
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ src/bedit/
|
|||||||
│ ├── port_types.py # Port type definitions and compatibility
|
│ ├── port_types.py # Port type definitions and compatibility
|
||||||
│ ├── serializer.py # JSON persistence
|
│ ├── serializer.py # JSON persistence
|
||||||
│ ├── libraries.py # Library file discovery and parsing
|
│ ├── libraries.py # Library file discovery and parsing
|
||||||
│ └── simulation/ # Qt-free simulation service and compiler/runtime code
|
│ └── simulation/ # Qt-free composition and OpenModelica interface code
|
||||||
└── gui/ # All Qt-dependent code
|
└── gui/ # All Qt-dependent code
|
||||||
├── app.py # QApplication startup and palette
|
├── app.py # QApplication startup and palette
|
||||||
├── main_window.py # Top-level UI orchestration
|
├── main_window.py # Top-level UI orchestration
|
||||||
@@ -94,6 +94,11 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
|
|||||||
- Avoid the QSettings group name `general`; Qt treats `General` specially in INI
|
- Avoid the QSettings group name `general`; Qt treats `General` specially in INI
|
||||||
files. Autosave keys live under `autosave/`.
|
files. Autosave keys live under `autosave/`.
|
||||||
- User-visible document edits should participate in undo/redo.
|
- User-visible document edits should participate in undo/redo.
|
||||||
|
- Component clipboard data is shared by the graph, document tree, and library
|
||||||
|
tree. Pasting into the document node creates roots; pasting into a graph node
|
||||||
|
creates children at an origin-normalized position. Always clone pasted trees
|
||||||
|
with fresh IDs, preserve connections between jointly copied graph blocks, and
|
||||||
|
assign unique sibling names.
|
||||||
- The text-definition editor uses OpenModelica highlighting and completion from
|
- The text-definition editor uses OpenModelica highlighting and completion from
|
||||||
`src/bedit/data/syntax/openmodelica.json`. Keep keywords, types, built-ins, and
|
`src/bedit/data/syntax/openmodelica.json`. Keep keywords, types, built-ins, and
|
||||||
named BEdit `$name$` macro completions editable there; arbitrary `$name$`
|
named BEdit `$name$` macro completions editable there; arbitrary `$name$`
|
||||||
@@ -105,16 +110,35 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
|
|||||||
- File → Reload Simulation Code (`Ctrl+F5`) reloads modules under
|
- File → Reload Simulation Code (`Ctrl+F5`) reloads modules under
|
||||||
`bedit.core.simulation`, replaces the shared application/controller service,
|
`bedit.core.simulation`, replaces the shared application/controller service,
|
||||||
and preserves the previous instance attributes where possible.
|
and preserves the previous instance attributes where possible.
|
||||||
- Simulation compilation lives in `core/simulation/compiler.py`; the simulation
|
- Modelica composition lives in `core/simulation/composer.py`; the simulation
|
||||||
service only owns application state and delegates compilation. Ports with
|
service only owns application state and delegates composition. Ports with
|
||||||
`multipleConnections` are emitted as Modelica arrays. Their size is inferred
|
`multipleConnections` are emitted as Modelica arrays. Their size is inferred
|
||||||
per component instance from graph connections and exposed while compiling as
|
per component instance from graph connections and exposed while compiling as
|
||||||
`$portname_N$`; array connection endpoints receive stable one-based indices in
|
`$portname_N$`; array connection endpoints receive stable one-based indices in
|
||||||
graph connection order.
|
graph connection order.
|
||||||
- Blocking simulator integration belongs in `core/simulation/runner.py` and runs
|
- OpenModelica integration belongs in `core/simulation/openmodelica.py`. Its
|
||||||
on its daemon worker thread. Never perform OMPython startup or simulation work
|
persistent worker and OMC session start lazily on the first queued request.
|
||||||
directly on the Qt GUI thread; background failures must go to the application
|
Never perform OMPython work directly on the Qt GUI thread. Result and error
|
||||||
log.
|
callbacks run on background threads and must use a Qt signal before touching UI.
|
||||||
|
One lazy temporary working directory is shared by all requests in the session.
|
||||||
|
Explicit application shutdown closes OMC and removes that directory plus the
|
||||||
|
current session's OMPython log and port files; `__del__` is only a fallback.
|
||||||
|
- Simulation runs start an ephemeral localhost TCP listener before launching the
|
||||||
|
generated model through OMC's `system()` function. OpenModelica's newline-delimited
|
||||||
|
`xmltcp` status and message records are parsed in the core and forwarded through
|
||||||
|
callbacks; the simulation service retains the latest progress for polling.
|
||||||
|
- The application owns one reusable `SimulationWindow`. Starting a run clears its
|
||||||
|
progress, log, and future result views. Extend graph presentation through its
|
||||||
|
Designer-owned `resultsLayout` and the
|
||||||
|
`clear_result_views()`/`load_result_views()` hooks.
|
||||||
|
- 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.
|
||||||
|
Human-readable `.json` uses JSON, while the default `.ber` format uses the same
|
||||||
|
compressed MessagePack approach as `.beb` documents.
|
||||||
|
- 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 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
|
||||||
@@ -159,6 +183,9 @@ pyside6-uic --from-imports ui/text_definition_editor.ui \
|
|||||||
pyside6-uic --from-imports ui/simulation_settings_dialog.ui \
|
pyside6-uic --from-imports ui/simulation_settings_dialog.ui \
|
||||||
-o src/bedit/gui/generated/ui_simulation_settings_dialog.py
|
-o src/bedit/gui/generated/ui_simulation_settings_dialog.py
|
||||||
|
|
||||||
|
pyside6-uic --from-imports ui/simulation_window.ui \
|
||||||
|
-o src/bedit/gui/generated/ui_simulation_window.py
|
||||||
|
|
||||||
pyside6-uic --from-imports ui/graph_parameters_dialog.ui \
|
pyside6-uic --from-imports ui/graph_parameters_dialog.ui \
|
||||||
-o src/bedit/gui/generated/ui_graph_parameters_dialog.py
|
-o src/bedit/gui/generated/ui_graph_parameters_dialog.py
|
||||||
|
|
||||||
|
|||||||
BIN
BEdit/resources/icons/office-chart-line.png
Normal file
BIN
BEdit/resources/icons/office-chart-line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 877 B |
BIN
BEdit/resources/icons/view-form-table.png
Normal file
BIN
BEdit/resources/icons/view-form-table.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 429 B |
@@ -1,5 +1,7 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="icons">
|
<qresource prefix="icons">
|
||||||
|
<file>icons/view-form-table.png</file>
|
||||||
|
<file>icons/office-chart-line.png</file>
|
||||||
<file>icons/run-build.png</file>
|
<file>icons/run-build.png</file>
|
||||||
<file>icons/preferences-system.png</file>
|
<file>icons/preferences-system.png</file>
|
||||||
<file>icons/draw-triangle.png</file>
|
<file>icons/draw-triangle.png</file>
|
||||||
|
|||||||
@@ -2326,6 +2326,63 @@ K\x80@\x89\x15\x8d\xc04\xd5\xb5^\xaf\x1bx\xfa\x19\
|
|||||||
\x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\
|
\x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\
|
||||||
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
|
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
|
||||||
\xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\
|
\xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\
|
||||||
|
\x00\x00\x03m\
|
||||||
|
\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\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||||
|
\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
|
||||||
|
\x00\x00\x09pHYs\x00\x00\x1b\xaf\x00\x00\x1b\xaf\x01\
|
||||||
|
^\x1a\x91\x1c\x00\x00\x00\x07tIME\x07\xd9\x02\x10\
|
||||||
|
\x17\x22\x16\x993\xa5<\x00\x00\x02\xedIDATx\
|
||||||
|
\xda\xed\x97_H\xd3Q\x14\xc7\xcf\xd9\xef7\xcb\xca\x89\
|
||||||
|
B \x91\x0f\xc9\xcczha\xf6\xa8\xb4\x06M\x90f\
|
||||||
|
aiV$\x96V\x12\xf9P-S\x90d%Lh\
|
||||||
|
V/B.\x8d\xc2\xb2\xc0\x1erS\xc1\x84\x1cdo\
|
||||||
|
a\xa0\x0f>h\x0f\xfdy\xf0-\x9b\xa6\x0f\xb1{\xfb\
|
||||||
|
n\xf4\x8b\x91\xe2\x5cn\xae\x07\x0f\xdc\x9dq/\xfc>\
|
||||||
|
\xe7{\xce\xe1\xdc\xdf\x8f\xd6M\xb3\x86\x86\x86\xe2Dp\
|
||||||
|
u\xbf\xe19pG\x82>!\x01\x04\xe1\x9aOd\x09\
|
||||||
|
:\x13V\x02\xcd\xd6\x03H\x84\xa9\x04\xab\xaf\xaf\xef\x84\
|
||||||
|
\xcb\x8f\x07\xe0z\xcf\x82AU\xc8\xa6\xeax\xfc\xf6\xd1\
|
||||||
|
\x8dcKf\xa0\xa5\xa5\xa5\x0an$V\xd0\xea\xc7\xf3\
|
||||||
|
\x86\x9a\xae\xf9\xd3\xb5\xdd\xf3\xaf\x84\xa4\xcfRR\xa9\x5c\
|
||||||
|
&\x031\xb3\xf2\xf6\x1f\xb9P\xdb\x14\x90\xd2\xacH\xf6\
|
||||||
|
\x01\xda\x13\x10T\xd1Z\x96\xec\xa7x\x07P\xd26\xc7\
|
||||||
|
P\xdb!\x04\xbd\x10L\x15\xedg7-\x82\xc65\x00\
|
||||||
|
\xc0m\x02?\x82\xd9\xf5\xa4j\xb3\x5c\xd3&,\xba\x1f\
|
||||||
|
R\x8f\xd4\x93\xe3\xe5\xc5\x08\xf0X7\xa1\xa6> B\
|
||||||
|
\xde\xbb&s\xc0\xb3\xf3x\x81'\xbbt\x1f\xc1,w\
|
||||||
|
f\x19M\xd7$\xa0\xbe\xf7\xf2\x16\x19\xf5\x1c\x88\x0e|\
|
||||||
|
L!\xc9\x8d$u\x97X\xa8\x0b\xde\xac\x0aS\xab$\
|
||||||
|
3\x07\xd5\xb3\xd4\xd4\xc7'\x00\xa8\xce\x00\xfc)\xe0\xcc\
|
||||||
|
\xa4\xdbK2\xe9\x96\x94\xaa\x0bu\xcfc\xa8\x1f\xbc\x92\
|
||||||
|
\x22\xffq\x14GnB\xa4\xdbBRy\xcfRy\xcb\
|
||||||
|
\xa4XmS\xcf\xa7I\xe8\xed,\xf4\x85\xbb&\xc7\xb4\
|
||||||
|
\xda\xc7\xfe:\x06X\xf1d\x975y\x8c\xe5_\xbd\xc6\
|
||||||
|
\x93\x96\xf0\xb3\x5c\x87\x9f++\xdf\x8d\xf6m\xbf\xfa\xa9\
|
||||||
|
o[\x9d!\xe6\x97\x11\xc0\x198\x1e\x84\xea\x02\xa4|\
|
||||||
|
?T\xbf\x09?\x97\x92\x8a\xc7\xb3\xf6H)\x92^#\
|
||||||
|
\x1b\xae\xe5 \x03\x06\xb7e \xa5\xc3\xb4\xd2\x00\x00?\
|
||||||
|
\x91\x87Z\x87R\x0e_h\x9b\xea\x9e\x0e?\xdf\xdd\xf8\
|
||||||
|
\x9d\x03B\xde\x0cv>\x9a\xf1\x1aK\xbd\xb5\x7fk\xb3\
|
||||||
|
u1\xb8\xdd0`x\xe8F\xf9\x1e\xe19\xea\x8a\x03\
|
||||||
|
`\xa9\xfb\x09\xf8)\x80\x1d\xb6\x8f\xcf\x02\xda~\xa6}\
|
||||||
|
\x86v\xdc\x981\x06\x045\x02NB\x90\xf7\xf0t\xb3\
|
||||||
|
\x1f\x19\xb8\x80\xe5\xeeOo5\x84\xa9>\x040\x1aD\
|
||||||
|
!\xc0MEs\xe7F\x97diM\xc8\xcc\xf9N\xa7\
|
||||||
|
3'\xfc0\xb9\xe6\x1b\xe9\x98\x8c\xaaB\x07\x92\x142\
|
||||||
|
\xebU6\xeb\xf1\xbc$\x85}\xd8\xbb;\xd1\x9c\xfa\xe1\
|
||||||
|
\x0f0\xfd\x9e;\x04\x13\x1b\xecD\xec\x02\xdcJ\x92\xcf\
|
||||||
|
\x03<\x14\xf5$\x04\xd8\x84\xd5\x85\xbf_P\xeba,\
|
||||||
|
3\x96\x0f\x8a\x0fB}\xe6\xa43\xf5L\x08\x1enB\
|
||||||
|
\xb5\x13J\x01\xb5\x13a\xaa\x87V3\x07|X\x0eI\
|
||||||
|
4\xe5oK\x8b|\x1f\xcc\xd4\xfa\xd1h%\x00\xa7\x02\
|
||||||
|
<\xbc\xaaA\xb4\xf0 m\x0c\x0e+:+\x9a\xad\x1e\
|
||||||
|
\xfd\xaf\xdf\x091k\xea\xfe\xfe\xf8\xe1H\xdf\x05R\xca\
|
||||||
|
|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\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\
|
||||||
@@ -2396,6 +2453,35 @@ f\xa2\xd1\xa5\x5c\x22\xb4\x91SZ\xd5u\xd7\x0a\xd2]\
|
|||||||
|\x01\x85\x09\x800\x7fss\xd3{\xf6\x7fABG\
|
|\x01\x85\x09\x800\x7fss\xd3{\xf6\x7fABG\
|
||||||
Y\x01\x05l*\xfc\x00!\x00\x12\xf1%U\xb6\x0e\x00\
|
Y\x01\x05l*\xfc\x00!\x00\x12\xf1%U\xb6\x0e\x00\
|
||||||
\x00\x00\x00IEND\xaeB`\x82\
|
\x00\x00\x00IEND\xaeB`\x82\
|
||||||
|
\x00\x00\x01\xad\
|
||||||
|
\x89\
|
||||||
|
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||||
|
\x00\x00 \x00\x00\x00 \x08\x03\x00\x00\x00D\xa4\x8a\xc6\
|
||||||
|
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||||
|
\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01B(\
|
||||||
|
\x9bx\x00\x00\x00\x07tIME\x07\xd9\x0c\x1c\x03\x1c\
|
||||||
|
\x0e%S,b\x00\x00\x00uPLTE\x00\x00\x00\
|
||||||
|
\x13\x13\x13\x0a\x0a\x0a\x0b\x0b\x0b\x00\x00\x00\x00\x00\x00\x00\
|
||||||
|
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6\xc6\xc8\xc7\xc7\
|
||||||
|
\xc9\xc9\xc9\xcb\xcc\xcc\xce\xce\xce\xd0\xd0\xd0\xd2\xd4\xd4\xd6\
|
||||||
|
\xd7\xd7\xd8\xd7\xd7\xd9\xd8\xd8\xda\xd9\xd9\xdb\xda\xda\xdb\xda\
|
||||||
|
\xda\xdc\xdb\xdb\xdc\xdb\xdb\xdd\xdd\xdd\xdf\xde\xde\xdf\xe2\xe2\
|
||||||
|
\xe3\xe3\xe3\xe5\xe3\xe3\xe6\xe4\xe4\xe6\xe6\xe6\xe7\xe7\xe7\xe9\
|
||||||
|
\xe9\xe9\xea\xeb\xeb\xec\xed\xed\xee\xf0\xf0\xf1\xf3\xf3\xf4\xff\
|
||||||
|
\xff\xff\xd3\x9b\xcc\x0e\x00\x00\x00\x0atRNS\x00\x09\
|
||||||
|
\x15\x15\x1825678\xb5\xcc\xc0\x1e\x00\x00\x00\x01\
|
||||||
|
bKGD&Z\x08\x98\xb5\x00\x00\x00\x9bIDA\
|
||||||
|
Tx\xda\xd5\x93\xcb\x0e\x820\x10\x00\x8b\x0aEP|\
|
||||||
|
u\xc1G)\x94\x02\xff\xff\x89.]\x0e\x18\xccr1\
|
||||||
|
F\xe72\xd9d\xd26\x9bT\xfc\x02\xc1\x86%\x10a\
|
||||||
|
\xcf\x12\x0a\xd9)\x86N\x0a\xd9^\x18Z\x0c\xdc\x91\xc1\
|
||||||
|
a\xd0\x1c\x18\x1a\x0c\xec\x9e\xc1bP'\x9et\x97\xce\
|
||||||
|
\x95\xd4\x18\x94=q#\xddIWR\x89\x81\x1e\x03E\
|
||||||
|
\x82\x97IO\x02\x98\x8b\x82G\x01\x9e\xec\x8d\x8aj\xf1\
|
||||||
|
\x84\xcf\xbc!W\x03\x90\x8d\x02oR^-^\xf1?\
|
||||||
|
{\xe0\x83\xc8\x1a=`\xce\xc6\xebd\xa6\x93\x8b\xc4*\
|
||||||
|
\xde2\xc4\xeb/|\xcd'\xec\xbfO\xbf\x90M\x1a\x0a\
|
||||||
|
\x00\x00\x00\x00IEND\xaeB`\x82\
|
||||||
\x00\x00\x02\x92\
|
\x00\x00\x02\x92\
|
||||||
\x89\
|
\x89\
|
||||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||||
@@ -2632,10 +2718,20 @@ qt_resource_name = b"\
|
|||||||
\x00d\
|
\x00d\
|
||||||
\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\
|
\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\
|
||||||
\
|
\
|
||||||
|
\x00\x15\
|
||||||
|
\x02\xb4\x1f\x07\
|
||||||
|
\x00o\
|
||||||
|
\x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\
|
||||||
|
\x00.\x00p\x00n\x00g\
|
||||||
\x00\x10\
|
\x00\x10\
|
||||||
\x03\xe6\xd3g\
|
\x03\xe6\xd3g\
|
||||||
\x00d\
|
\x00d\
|
||||||
\x00r\x00a\x00w\x00-\x00e\x00l\x00l\x00i\x00p\x00s\x00e\x00.\x00p\x00n\x00g\
|
\x00r\x00a\x00w\x00-\x00e\x00l\x00l\x00i\x00p\x00s\x00e\x00.\x00p\x00n\x00g\
|
||||||
|
\x00\x13\
|
||||||
|
\x07\xd6O\x07\
|
||||||
|
\x00v\
|
||||||
|
\x00i\x00e\x00w\x00-\x00f\x00o\x00r\x00m\x00-\x00t\x00a\x00b\x00l\x00e\x00.\x00p\
|
||||||
|
\x00n\x00g\
|
||||||
\x00\x12\
|
\x00\x12\
|
||||||
\x09\xb3>\xc7\
|
\x09\xb3>\xc7\
|
||||||
\x00d\
|
\x00d\
|
||||||
@@ -2653,7 +2749,7 @@ 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\x1c\x00\x00\x00\x03\
|
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1e\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\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00N\xe8\
|
||||||
\x00\x00\x01\x9f{C\xf1'\
|
\x00\x00\x01\x9f{C\xf1'\
|
||||||
@@ -2661,11 +2757,13 @@ qt_resource_struct = b"\
|
|||||||
\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\x02\xec\x00\x00\x00\x00\x00\x01\x00\x00tV\
|
||||||
\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\x01\x9f\x84\x8f\x00\xb9\
|
||||||
\x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\
|
\x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\
|
||||||
\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\xa4\x00\x00\x00\x00\x00\x01\x00\x00<J\
|
||||||
\x00\x00\x01\x9f{C\xf1\x18\
|
\x00\x00\x01\x9f{C\xf1\x18\
|
||||||
\x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x8d\xaa\
|
\x00\x00\x03\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x91\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\xa2\x00\x00\x00\x00\x00\x01\x00\x00f\xc8\
|
||||||
\x00\x00\x01\x9f\x7fV\xd5\xc0\
|
\x00\x00\x01\x9f\x7fV\xd5\xc0\
|
||||||
@@ -2677,9 +2775,11 @@ qt_resource_struct = b"\
|
|||||||
\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\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00Kh\
|
||||||
\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\x01\x9f\x84\x8f\xa6\x10\
|
||||||
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\
|
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\
|
||||||
\x00\x00\x01\x9f{{\xa5\xd5\
|
\x00\x00\x01\x9f{{\xa5\xd5\
|
||||||
\x00\x00\x03\xca\x00\x00\x00\x00\x00\x01\x00\x00\x91\xea\
|
\x00\x00\x04&\x00\x00\x00\x00\x00\x01\x00\x00\x97\x0c\
|
||||||
\x00\x00\x01\x9f\x7f&\x83\x10\
|
\x00\x00\x01\x9f\x7f&\x83\x10\
|
||||||
\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~\
|
||||||
@@ -2703,7 +2803,7 @@ qt_resource_struct = b"\
|
|||||||
\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\x03\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x94\x80\
|
\x00\x00\x04P\x00\x00\x00\x00\x00\x01\x00\x00\x99\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\
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
from bedit.core.simulation.service import Simulation
|
from bedit.core.simulation.service import Simulation
|
||||||
|
from bedit.core.simulation.openmodelica import OpenModelicaInterface
|
||||||
|
from bedit.core.simulation.results import (
|
||||||
|
BerSimulationResultsSerializer,
|
||||||
|
JsonSimulationResultsSerializer,
|
||||||
|
SimulationExecutionResult,
|
||||||
|
SimulationResults,
|
||||||
|
SimulationResultsSerializer,
|
||||||
|
SimulationTrace,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = ["Simulation"]
|
__all__ = [
|
||||||
|
"OpenModelicaInterface",
|
||||||
|
"Simulation",
|
||||||
|
"SimulationResults",
|
||||||
|
"SimulationExecutionResult",
|
||||||
|
"SimulationResultsSerializer",
|
||||||
|
"SimulationTrace",
|
||||||
|
"BerSimulationResultsSerializer",
|
||||||
|
"JsonSimulationResultsSerializer",
|
||||||
|
]
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ _BEVALUE_PATTERN = re.compile(r"\$([A-Za-z_][A-Za-z0-9_]*)\$")
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CompilationResult:
|
class CompositionResult:
|
||||||
"""The intermediate data and generated source produced by compilation."""
|
"""The intermediate data and generated source produced by composition."""
|
||||||
|
|
||||||
graph: dict[str, Any]
|
graph: dict[str, Any]
|
||||||
objects_by_id: dict[str, Any]
|
objects_by_id: dict[str, Any]
|
||||||
@@ -23,16 +23,16 @@ class CompilationResult:
|
|||||||
model_name: str
|
model_name: str
|
||||||
|
|
||||||
|
|
||||||
def compile_graph(graph: dict[str, Any]) -> CompilationResult:
|
def compose_graph(graph: dict[str, Any]) -> CompositionResult:
|
||||||
"""Clean, index, and emit a serialized component tree."""
|
"""Clean, index, and compose a serialized component tree."""
|
||||||
|
|
||||||
cleaned_graph = cleanup_graph(deepcopy(graph))
|
cleaned_graph = cleanup_graph(deepcopy(graph))
|
||||||
objects_by_id = build_id_list(cleaned_graph)
|
objects_by_id = build_id_list(cleaned_graph)
|
||||||
return CompilationResult(
|
return CompositionResult(
|
||||||
graph=cleaned_graph,
|
graph=cleaned_graph,
|
||||||
objects_by_id=objects_by_id,
|
objects_by_id=objects_by_id,
|
||||||
modelica=emit_model(cleaned_graph, objects_by_id),
|
modelica=emit_model(cleaned_graph, objects_by_id),
|
||||||
model_name=model_name_for(cleaned_graph)
|
model_name=model_name_for(cleaned_graph),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ def emit_model(
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""Emit a component and its nested definitions as Modelica source."""
|
"""Emit a component and its nested definitions as Modelica source."""
|
||||||
|
|
||||||
del id_list # Kept in the public API for compiler extensions and inspection.
|
del id_list # Kept in the public API for composer extensions and inspection.
|
||||||
indentation = "\t" * indent
|
indentation = "\t" * indent
|
||||||
body_indent = "\t" * (indent + 1)
|
body_indent = "\t" * (indent + 1)
|
||||||
model_name = model_name_for(graph)
|
model_name = model_name_for(graph)
|
||||||
@@ -283,7 +283,7 @@ def _port_count_macros(
|
|||||||
|
|
||||||
|
|
||||||
def expand_bevalues(text: str, values: dict[str, str]) -> str:
|
def expand_bevalues(text: str, values: dict[str, str]) -> str:
|
||||||
"""Replace BEdit ``$name$`` macros and reject unresolved compiler values."""
|
"""Replace BEdit ``$name$`` macros and reject unresolved composer values."""
|
||||||
|
|
||||||
def replace(match: re.Match[str]) -> str:
|
def replace(match: re.Match[str]) -> str:
|
||||||
name = match.group(1)
|
name = match.group(1)
|
||||||
427
BEdit/src/bedit/core/simulation/openmodelica.py
Normal file
427
BEdit/src/bedit/core/simulation/openmodelica.py
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from queue import Queue
|
||||||
|
from threading import Event, Lock, Thread, current_thread
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from bedit.core.application_log import get_logger
|
||||||
|
from bedit.core.simulation.results import (
|
||||||
|
SimulationExecutionResult,
|
||||||
|
load_openmodelica_csv,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
log = get_logger(__name__)
|
||||||
|
ResultCallback = Callable[[Any], None]
|
||||||
|
ErrorCallback = Callable[[Exception], None]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _Request:
|
||||||
|
description: str
|
||||||
|
operation: Callable[[Any, Path], Any]
|
||||||
|
callback: ResultCallback | None
|
||||||
|
error_callback: ErrorCallback | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SimulationProgress:
|
||||||
|
phase: str
|
||||||
|
current_step_size: float
|
||||||
|
time: float
|
||||||
|
progress: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SimulationMessage:
|
||||||
|
stream: str
|
||||||
|
type: str
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
class OpenModelicaInterface:
|
||||||
|
"""Asynchronous, persistent interface to one OpenModelica session.
|
||||||
|
|
||||||
|
The worker and OMC session are created lazily for the first request. Callbacks
|
||||||
|
execute on background threads and must not manipulate Qt widgets directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, executable_path: str = "") -> None:
|
||||||
|
self._executable_path = executable_path
|
||||||
|
self._lifecycle_lock = Lock()
|
||||||
|
self._queue: Queue[_Request | None] | None = None
|
||||||
|
self._worker: Thread | None = None
|
||||||
|
self._temp_dir: Path | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def executable_path(self) -> str:
|
||||||
|
return self._executable_path
|
||||||
|
|
||||||
|
def configure(self, executable_path: str) -> None:
|
||||||
|
"""Use a new executable path for subsequent requests."""
|
||||||
|
|
||||||
|
if executable_path == self._executable_path:
|
||||||
|
return
|
||||||
|
self.shutdown(wait=True)
|
||||||
|
self._executable_path = executable_path
|
||||||
|
|
||||||
|
def get_version(
|
||||||
|
self,
|
||||||
|
callback: ResultCallback | None = None,
|
||||||
|
error_callback: ErrorCallback | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Request the OpenModelica version without blocking the caller."""
|
||||||
|
self.send_expression("getVersion()", callback, error_callback)
|
||||||
|
|
||||||
|
def build_model(
|
||||||
|
self,
|
||||||
|
model: str,
|
||||||
|
model_name: str,
|
||||||
|
callback: ResultCallback | None = None,
|
||||||
|
error_callback: ErrorCallback | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Load and build one composed model as an ordered worker operation."""
|
||||||
|
|
||||||
|
def operation(omc, _temp_dir: Path):
|
||||||
|
loaded = omc.sendExpression(f"loadString({json.dumps(model)})")
|
||||||
|
if loaded is not True:
|
||||||
|
raise RuntimeError("OpenModelica could not load the composed model")
|
||||||
|
return omc.sendExpression(f"buildModel({model_name})")
|
||||||
|
|
||||||
|
self._submit("build model", operation, callback, error_callback)
|
||||||
|
|
||||||
|
def run_model(
|
||||||
|
self,
|
||||||
|
executable: str,
|
||||||
|
arguments: list[str],
|
||||||
|
progress_callback: Callable[[SimulationProgress], None] | None = None,
|
||||||
|
message_callback: Callable[[SimulationMessage], None] | None = None,
|
||||||
|
callback: ResultCallback | None = None,
|
||||||
|
error_callback: ErrorCallback | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Run a built model and consume its XML/TCP status stream."""
|
||||||
|
|
||||||
|
def operation(omc, temp_dir: Path):
|
||||||
|
return _run_model_with_tcp(
|
||||||
|
omc,
|
||||||
|
executable,
|
||||||
|
arguments,
|
||||||
|
temp_dir,
|
||||||
|
progress_callback,
|
||||||
|
message_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._submit("run simulation", operation, callback, error_callback)
|
||||||
|
|
||||||
|
def send_expression(
|
||||||
|
self,
|
||||||
|
expression: str,
|
||||||
|
callback: ResultCallback | None = None,
|
||||||
|
error_callback: ErrorCallback | None = None,
|
||||||
|
*,
|
||||||
|
parsed: bool = True,
|
||||||
|
) -> None:
|
||||||
|
"""Queue an OMC expression for ordered execution on the worker thread."""
|
||||||
|
|
||||||
|
def operation(omc, _temp_dir: Path):
|
||||||
|
return omc.sendExpression(expression, parsed=parsed)
|
||||||
|
|
||||||
|
self._submit(expression, operation, callback, error_callback)
|
||||||
|
|
||||||
|
def _submit(
|
||||||
|
self,
|
||||||
|
description: str,
|
||||||
|
operation: Callable[[Any, Path], Any],
|
||||||
|
callback: ResultCallback | None,
|
||||||
|
error_callback: ErrorCallback | None,
|
||||||
|
) -> None:
|
||||||
|
request = _Request(description, operation, callback, error_callback)
|
||||||
|
self._ensure_worker().put(request)
|
||||||
|
|
||||||
|
def shutdown(self, *, wait: bool = True) -> None:
|
||||||
|
"""Ask the worker to close its OMC session after queued requests."""
|
||||||
|
|
||||||
|
with self._lifecycle_lock:
|
||||||
|
worker = self._worker
|
||||||
|
queue = self._queue
|
||||||
|
self._worker = None
|
||||||
|
self._queue = None
|
||||||
|
if queue is not None:
|
||||||
|
queue.put(None)
|
||||||
|
if wait and worker is not None and worker is not current_thread():
|
||||||
|
worker.join()
|
||||||
|
if worker is None:
|
||||||
|
self._cleanup_temp_dir()
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
"""Best-effort fallback; normal application shutdown is explicit."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.shutdown(wait=False)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _ensure_worker(self) -> Queue[_Request | None]:
|
||||||
|
with self._lifecycle_lock:
|
||||||
|
if self._worker is not None and self._worker.is_alive():
|
||||||
|
return self._queue
|
||||||
|
temp_dir = self._ensure_temp_dir_locked()
|
||||||
|
queue: Queue[_Request | None] = Queue()
|
||||||
|
worker = Thread(
|
||||||
|
target=self._worker_main,
|
||||||
|
args=(queue, self._executable_path, temp_dir),
|
||||||
|
name="bedit-openmodelica",
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
self._queue = queue
|
||||||
|
self._worker = worker
|
||||||
|
worker.start()
|
||||||
|
return queue
|
||||||
|
|
||||||
|
def _worker_main(
|
||||||
|
self,
|
||||||
|
queue: Queue[_Request | None],
|
||||||
|
executable_path: str,
|
||||||
|
temp_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
omc = None
|
||||||
|
try:
|
||||||
|
while (request := queue.get()) is not None:
|
||||||
|
try:
|
||||||
|
if omc is None:
|
||||||
|
omc = _create_session(executable_path)
|
||||||
|
changed_directory = omc.sendExpression(
|
||||||
|
f"cd({json.dumps(str(temp_dir))})"
|
||||||
|
)
|
||||||
|
if not changed_directory:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"OpenModelica could not use {str(temp_dir)!r}"
|
||||||
|
)
|
||||||
|
result = request.operation(omc, temp_dir)
|
||||||
|
except Exception as error:
|
||||||
|
if request.error_callback is None:
|
||||||
|
log.exception(
|
||||||
|
"OpenModelica request failed: %s", request.description
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_deliver_callback(request.error_callback, error)
|
||||||
|
else:
|
||||||
|
if request.callback is not None:
|
||||||
|
_deliver_callback(request.callback, result)
|
||||||
|
finally:
|
||||||
|
transport_files = _ompython_transport_files(omc)
|
||||||
|
if omc is not None:
|
||||||
|
try:
|
||||||
|
omc.sendExpression("quit()")
|
||||||
|
except Exception:
|
||||||
|
log.debug("Could not close OpenModelica session", exc_info=True)
|
||||||
|
for path in transport_files:
|
||||||
|
try:
|
||||||
|
path.unlink(missing_ok=True)
|
||||||
|
except OSError:
|
||||||
|
log.debug("Could not remove OMPython file %s", path, exc_info=True)
|
||||||
|
self._cleanup_temp_dir(temp_dir)
|
||||||
|
|
||||||
|
def _ensure_temp_dir_locked(self) -> Path:
|
||||||
|
if self._temp_dir is None:
|
||||||
|
self._temp_dir = Path(tempfile.mkdtemp(prefix="bedit-openmodelica-"))
|
||||||
|
return self._temp_dir
|
||||||
|
|
||||||
|
def _cleanup_temp_dir(self, expected: Path | None = None) -> None:
|
||||||
|
with self._lifecycle_lock:
|
||||||
|
if expected is not None and self._temp_dir != expected:
|
||||||
|
temp_dir = expected
|
||||||
|
else:
|
||||||
|
temp_dir = self._temp_dir
|
||||||
|
self._temp_dir = None
|
||||||
|
if temp_dir is not None:
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _deliver_callback(callback: Callable[[Any], None], value: Any) -> None:
|
||||||
|
try:
|
||||||
|
callback(value)
|
||||||
|
except Exception:
|
||||||
|
log.exception("OpenModelica callback failed")
|
||||||
|
|
||||||
|
|
||||||
|
def _create_session(executable_path: str):
|
||||||
|
from OMPython import OMCSessionZMQ
|
||||||
|
|
||||||
|
return OMCSessionZMQ(omhome=_openmodelica_home(executable_path))
|
||||||
|
|
||||||
|
|
||||||
|
def _run_model_with_tcp(
|
||||||
|
omc,
|
||||||
|
executable: str,
|
||||||
|
arguments: list[str],
|
||||||
|
temp_dir: Path,
|
||||||
|
progress_callback: Callable[[SimulationProgress], None] | None,
|
||||||
|
message_callback: Callable[[SimulationMessage], None] | None,
|
||||||
|
) -> SimulationExecutionResult:
|
||||||
|
executable_path = Path(executable)
|
||||||
|
executable_command = executable
|
||||||
|
if not executable_path.is_absolute() and executable_path.parent == Path("."):
|
||||||
|
executable_command = f"./{executable}"
|
||||||
|
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
|
||||||
|
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
server.bind(("127.0.0.1", 0))
|
||||||
|
server.listen(1)
|
||||||
|
server.settimeout(0.25)
|
||||||
|
port = server.getsockname()[1]
|
||||||
|
command = shlex.join([
|
||||||
|
executable_command,
|
||||||
|
*arguments,
|
||||||
|
f"-port={port}",
|
||||||
|
"-logFormat=xmltcp",
|
||||||
|
])
|
||||||
|
output_path = temp_dir / "simulation-output.txt"
|
||||||
|
command_finished = Event()
|
||||||
|
reader_finished = Event()
|
||||||
|
reader_errors: list[Exception] = []
|
||||||
|
|
||||||
|
def read_progress() -> None:
|
||||||
|
try:
|
||||||
|
connection = _accept_simulation_connection(server, command_finished)
|
||||||
|
with connection, connection.makefile(
|
||||||
|
"r", encoding="utf-8"
|
||||||
|
) as stream:
|
||||||
|
for line in stream:
|
||||||
|
_handle_simulation_xml(
|
||||||
|
line, progress_callback, message_callback
|
||||||
|
)
|
||||||
|
except Exception as error:
|
||||||
|
reader_errors.append(error)
|
||||||
|
finally:
|
||||||
|
reader_finished.set()
|
||||||
|
|
||||||
|
reader = Thread(
|
||||||
|
target=read_progress,
|
||||||
|
name="bedit-simulation-progress",
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
reader.start()
|
||||||
|
log.info("Starting simulation through OpenModelica: %s", command)
|
||||||
|
try:
|
||||||
|
return_code = omc.sendExpression(
|
||||||
|
f"system({json.dumps(command)}, {json.dumps(str(output_path))})"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
command_finished.set()
|
||||||
|
if not reader_finished.wait(20.0):
|
||||||
|
raise TimeoutError("Simulation progress connection did not close")
|
||||||
|
if reader_errors:
|
||||||
|
raise reader_errors[0]
|
||||||
|
if return_code != 0:
|
||||||
|
output = output_path.read_text(errors="replace") if output_path.exists() else ""
|
||||||
|
detail = f": {output.strip()}" if output.strip() else ""
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Simulation process exited with status {return_code}{detail}"
|
||||||
|
)
|
||||||
|
result_path = temp_dir / f"{Path(executable).stem}_res.csv"
|
||||||
|
if not result_path.is_file():
|
||||||
|
raise RuntimeError(
|
||||||
|
f"OpenModelica did not create the expected result file {result_path.name!r}"
|
||||||
|
)
|
||||||
|
data = load_openmodelica_csv(result_path)
|
||||||
|
log.info(
|
||||||
|
"Loaded %d result columns from %s", len(data), result_path.name
|
||||||
|
)
|
||||||
|
return SimulationExecutionResult(
|
||||||
|
return_code=int(return_code),
|
||||||
|
result_file=str(result_path),
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _accept_simulation_connection(
|
||||||
|
server: socket.socket, command_finished: Event
|
||||||
|
) -> socket.socket:
|
||||||
|
deadline = time.monotonic() + 15.0
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
try:
|
||||||
|
connection, _address = server.accept()
|
||||||
|
return connection
|
||||||
|
except socket.timeout:
|
||||||
|
if command_finished.is_set():
|
||||||
|
raise RuntimeError(
|
||||||
|
"Simulation command finished before opening its progress connection"
|
||||||
|
)
|
||||||
|
raise TimeoutError("Simulation did not connect to the progress server")
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_simulation_xml(
|
||||||
|
line: str,
|
||||||
|
progress_callback: Callable[[SimulationProgress], None] | None,
|
||||||
|
message_callback: Callable[[SimulationMessage], None] | None,
|
||||||
|
) -> None:
|
||||||
|
text = line.strip()
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
element = ET.fromstring(text)
|
||||||
|
except ET.ParseError:
|
||||||
|
log.warning("Invalid simulation status XML: %s", text)
|
||||||
|
return
|
||||||
|
if element.tag == "status" and progress_callback is not None:
|
||||||
|
_deliver_callback(
|
||||||
|
progress_callback,
|
||||||
|
SimulationProgress(
|
||||||
|
phase=element.get("phase", ""),
|
||||||
|
current_step_size=float(element.get("currentStepSize", 0)),
|
||||||
|
time=float(element.get("time", 0)),
|
||||||
|
progress=int(float(element.get("progress", 0))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
elif element.tag == "message":
|
||||||
|
message = SimulationMessage(
|
||||||
|
stream=element.get("stream", ""),
|
||||||
|
type=element.get("type", ""),
|
||||||
|
text=element.get("text", ""),
|
||||||
|
)
|
||||||
|
log.info("OpenModelica %s: %s", message.stream, message.text)
|
||||||
|
if message_callback is not None:
|
||||||
|
_deliver_callback(message_callback, message)
|
||||||
|
|
||||||
|
|
||||||
|
def _ompython_transport_files(omc) -> set[Path]:
|
||||||
|
"""Return only the log and port files owned by this OMPython session."""
|
||||||
|
|
||||||
|
if omc is None:
|
||||||
|
return set()
|
||||||
|
process = getattr(omc, "omc_process", None)
|
||||||
|
if process is None:
|
||||||
|
return set()
|
||||||
|
files: set[Path] = set()
|
||||||
|
temp_dir = getattr(process, "_temp_dir", None)
|
||||||
|
file_base = getattr(process, "_omc_filebase", None)
|
||||||
|
if temp_dir is not None and file_base:
|
||||||
|
files.add(Path(temp_dir) / f"{file_base}.log")
|
||||||
|
try:
|
||||||
|
port_file = process._get_portfile_path()
|
||||||
|
except Exception:
|
||||||
|
port_file = None
|
||||||
|
if port_file is not None:
|
||||||
|
files.add(Path(port_file))
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def _openmodelica_home(executable_path: str) -> str | None:
|
||||||
|
"""Convert an optional omc executable path to the home expected by OMPython."""
|
||||||
|
|
||||||
|
if not executable_path.strip():
|
||||||
|
return None
|
||||||
|
path = Path(os.path.expandvars(executable_path)).expanduser()
|
||||||
|
if path.name.lower() in {"omc", "omc.exe"}:
|
||||||
|
return str(path.parent.parent)
|
||||||
|
return str(path)
|
||||||
199
BEdit/src/bedit/core/simulation/results.py
Normal file
199
BEdit/src/bedit/core/simulation/results.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import zlib
|
||||||
|
from dataclasses import asdict, dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import msgpack
|
||||||
|
|
||||||
|
|
||||||
|
RESULTS_FORMAT = "bedit-simulation-results"
|
||||||
|
RESULTS_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SimulationTrace:
|
||||||
|
"""One plottable series; samples can be filled by a future result importer."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
x_values: list[float] = field(default_factory=list)
|
||||||
|
y_values: list[float] = field(default_factory=list)
|
||||||
|
x_label: str = "time"
|
||||||
|
y_label: str = ""
|
||||||
|
unit: str = ""
|
||||||
|
properties: dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SimulationResults:
|
||||||
|
"""Serializable state displayed by the standalone simulation window."""
|
||||||
|
|
||||||
|
model_name: str = ""
|
||||||
|
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)
|
||||||
|
metadata: dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"format": RESULTS_FORMAT,
|
||||||
|
"version": RESULTS_VERSION,
|
||||||
|
"modelName": self.model_name,
|
||||||
|
"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],
|
||||||
|
"metadata": dict(self.metadata),
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any]) -> "SimulationResults":
|
||||||
|
if data.get("format") != RESULTS_FORMAT:
|
||||||
|
raise ValueError("Not a BEdit simulation-results file")
|
||||||
|
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", [])]
|
||||||
|
return cls(
|
||||||
|
model_name=str(data.get("modelName", "")),
|
||||||
|
status=dict(data.get("status", {})),
|
||||||
|
messages=[dict(message) for message in data.get("messages", [])],
|
||||||
|
data={
|
||||||
|
str(name): [float(value) for value in values]
|
||||||
|
for name, values in dict(data.get("data", {})).items()
|
||||||
|
},
|
||||||
|
traces=traces,
|
||||||
|
metadata=dict(data.get("metadata", {})),
|
||||||
|
)
|
||||||
|
except (TypeError, ValueError) as error:
|
||||||
|
raise ValueError("Malformed simulation-results data") from error
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SimulationExecutionResult:
|
||||||
|
"""Completed process information delivered before its temp files disappear."""
|
||||||
|
|
||||||
|
return_code: int
|
||||||
|
result_file: str
|
||||||
|
data: dict[str, list[float]]
|
||||||
|
|
||||||
|
|
||||||
|
def load_openmodelica_csv(path: str | Path) -> dict[str, list[float]]:
|
||||||
|
"""Read an OpenModelica CSV result as one numeric array per column."""
|
||||||
|
|
||||||
|
source = Path(path)
|
||||||
|
try:
|
||||||
|
with source.open(newline="", encoding="utf-8") as file:
|
||||||
|
reader = csv.reader(file)
|
||||||
|
headers = next(reader)
|
||||||
|
if not headers or any(not header for header in headers):
|
||||||
|
raise ValueError("The result CSV has an invalid header")
|
||||||
|
if len(set(headers)) != len(headers):
|
||||||
|
raise ValueError("The result CSV contains duplicate column names")
|
||||||
|
columns = {header: [] for header in headers}
|
||||||
|
for row_number, row in enumerate(reader, start=2):
|
||||||
|
if len(row) != len(headers):
|
||||||
|
raise ValueError(
|
||||||
|
f"Result CSV row {row_number} has {len(row)} values; "
|
||||||
|
f"expected {len(headers)}"
|
||||||
|
)
|
||||||
|
for header, value in zip(headers, row, strict=True):
|
||||||
|
columns[header].append(float(value))
|
||||||
|
except OSError as error:
|
||||||
|
raise ValueError(f"Could not read OpenModelica results: {error}") from error
|
||||||
|
except StopIteration as error:
|
||||||
|
raise ValueError("The OpenModelica result CSV is empty") from error
|
||||||
|
except ValueError as error:
|
||||||
|
if str(error).startswith(("The result CSV", "Result CSV")):
|
||||||
|
raise
|
||||||
|
raise ValueError(f"The OpenModelica result CSV is not numeric: {error}") from error
|
||||||
|
return columns
|
||||||
|
|
||||||
|
|
||||||
|
class JsonSimulationResultsSerializer:
|
||||||
|
@staticmethod
|
||||||
|
def load(path: Path) -> SimulationResults:
|
||||||
|
try:
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, json.JSONDecodeError) as error:
|
||||||
|
raise ValueError(f"Could not read simulation results: {error}") from error
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError("Simulation-results root must be an object")
|
||||||
|
return SimulationResults.from_dict(data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save(results: SimulationResults, path: Path) -> None:
|
||||||
|
temporary_path = path.with_suffix(path.suffix + ".tmp")
|
||||||
|
temporary_path.write_text(
|
||||||
|
json.dumps(results.to_dict(), indent=2, ensure_ascii=False) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
temporary_path.replace(path)
|
||||||
|
|
||||||
|
|
||||||
|
class BerSimulationResultsSerializer:
|
||||||
|
"""Compressed MessagePack serializer for binary simulation results."""
|
||||||
|
|
||||||
|
MAGIC = b"BER\x00"
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, path: Path) -> SimulationResults:
|
||||||
|
try:
|
||||||
|
payload = path.read_bytes()
|
||||||
|
except OSError as error:
|
||||||
|
raise ValueError(f"Could not read simulation results: {error}") from error
|
||||||
|
header = cls.MAGIC + bytes([cls.VERSION])
|
||||||
|
if not payload.startswith(header):
|
||||||
|
raise ValueError("This is not a supported BEdit binary results file")
|
||||||
|
try:
|
||||||
|
data = msgpack.unpackb(
|
||||||
|
zlib.decompress(payload[len(header) :]), raw=False
|
||||||
|
)
|
||||||
|
except (ValueError, zlib.error, msgpack.exceptions.MsgpackException) as error:
|
||||||
|
raise ValueError("The BEdit binary results file is damaged") from error
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError("The BEdit binary results file has an invalid root value")
|
||||||
|
return SimulationResults.from_dict(data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save(cls, results: SimulationResults, path: Path) -> None:
|
||||||
|
packed = msgpack.packb(results.to_dict(), use_bin_type=True)
|
||||||
|
payload = cls.MAGIC + bytes([cls.VERSION]) + zlib.compress(packed, level=9)
|
||||||
|
temporary_path = path.with_suffix(path.suffix + ".tmp")
|
||||||
|
temporary_path.write_bytes(payload)
|
||||||
|
temporary_path.replace(path)
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationResultsSerializer:
|
||||||
|
"""Select JSON or compressed MessagePack based on the file extension."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(path: str | Path) -> SimulationResults:
|
||||||
|
target = Path(path)
|
||||||
|
serializer = (
|
||||||
|
BerSimulationResultsSerializer
|
||||||
|
if target.suffix.lower() == ".ber"
|
||||||
|
else JsonSimulationResultsSerializer
|
||||||
|
)
|
||||||
|
return serializer.load(target)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save(results: SimulationResults, path: str | Path) -> None:
|
||||||
|
target = Path(path)
|
||||||
|
serializer = (
|
||||||
|
BerSimulationResultsSerializer
|
||||||
|
if target.suffix.lower() == ".ber"
|
||||||
|
else JsonSimulationResultsSerializer
|
||||||
|
)
|
||||||
|
serializer.save(results, target)
|
||||||
|
|
||||||
|
|
||||||
|
def save_simulation_results(path: str | Path, results: SimulationResults) -> None:
|
||||||
|
SimulationResultsSerializer.save(results, path)
|
||||||
|
|
||||||
|
|
||||||
|
def load_simulation_results(path: str | Path) -> SimulationResults:
|
||||||
|
return SimulationResultsSerializer.load(path)
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
from collections.abc import Callable
|
|
||||||
from pathlib import Path
|
|
||||||
from threading import Lock, Thread
|
|
||||||
|
|
||||||
from bedit.core.application_log import get_logger
|
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
|
||||||
_worker_lock = Lock()
|
|
||||||
_worker: Thread | None = None
|
|
||||||
|
|
||||||
|
|
||||||
def run_validation_async(
|
|
||||||
openmodelica_path: str = "",
|
|
||||||
model: str = "",
|
|
||||||
model_name: str = "",
|
|
||||||
task: Callable[[], None] | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Start one validation task in a background thread and return immediately."""
|
|
||||||
|
|
||||||
global _worker
|
|
||||||
with _worker_lock:
|
|
||||||
if _worker is not None and _worker.is_alive():
|
|
||||||
raise RuntimeError("An OpenModelica task is already running")
|
|
||||||
_worker = Thread(
|
|
||||||
target=_run_safely,
|
|
||||||
args=(
|
|
||||||
task
|
|
||||||
or (lambda: _run_validation(openmodelica_path, model, model_name)),
|
|
||||||
),
|
|
||||||
name="bedit-simulation",
|
|
||||||
daemon=True,
|
|
||||||
)
|
|
||||||
_worker.start()
|
|
||||||
|
|
||||||
|
|
||||||
def run_simulation_async(
|
|
||||||
openmodelica_path: str = "",
|
|
||||||
model: str = "",
|
|
||||||
model_name: str = "",
|
|
||||||
task: Callable[[], None] | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Start one validation-and-simulation task and return immediately."""
|
|
||||||
|
|
||||||
global _worker
|
|
||||||
with _worker_lock:
|
|
||||||
if _worker is not None and _worker.is_alive():
|
|
||||||
raise RuntimeError("An OpenModelica task is already running")
|
|
||||||
_worker = Thread(
|
|
||||||
target=_run_safely,
|
|
||||||
args=(
|
|
||||||
task
|
|
||||||
or (lambda: _run_openmodelica(openmodelica_path, model, model_name)),
|
|
||||||
),
|
|
||||||
name="bedit-simulation",
|
|
||||||
daemon=True,
|
|
||||||
)
|
|
||||||
_worker.start()
|
|
||||||
|
|
||||||
|
|
||||||
def simulation_is_running() -> bool:
|
|
||||||
"""Return whether the background simulation worker is active."""
|
|
||||||
|
|
||||||
with _worker_lock:
|
|
||||||
return _worker is not None and _worker.is_alive()
|
|
||||||
|
|
||||||
|
|
||||||
def _run_safely(task: Callable[[], None]) -> None:
|
|
||||||
global _worker
|
|
||||||
try:
|
|
||||||
task()
|
|
||||||
except Exception:
|
|
||||||
log.exception("Simulation run failed")
|
|
||||||
finally:
|
|
||||||
with _worker_lock:
|
|
||||||
_worker = None
|
|
||||||
|
|
||||||
|
|
||||||
def _run_validation(
|
|
||||||
openmodelica_path: str = "", model: str = "", model_name: str = ""
|
|
||||||
) -> None:
|
|
||||||
"""Create the OpenModelica session and perform a validation"""
|
|
||||||
|
|
||||||
from OMPython import OMCSessionZMQ
|
|
||||||
|
|
||||||
omhome = _openmodelica_home(openmodelica_path)
|
|
||||||
omc = OMCSessionZMQ(omhome=omhome)
|
|
||||||
_validate_model(omc, model, model_name)
|
|
||||||
|
|
||||||
|
|
||||||
def _run_openmodelica(
|
|
||||||
openmodelica_path: str = "", model: str = "", model_name: str = ""
|
|
||||||
) -> None:
|
|
||||||
"""Validate and run the current simulation stub in one OMC session."""
|
|
||||||
|
|
||||||
from OMPython import OMCSessionZMQ
|
|
||||||
|
|
||||||
omhome = _openmodelica_home(openmodelica_path)
|
|
||||||
omc = OMCSessionZMQ(omhome=omhome)
|
|
||||||
_validate_model(omc, model, model_name)
|
|
||||||
|
|
||||||
log.info("OpenModelica Version: %s", omc.sendExpression("getVersion()"))
|
|
||||||
|
|
||||||
loaded = omc.sendExpression(f"loadString({json.dumps(model)})")
|
|
||||||
if loaded is not True:
|
|
||||||
details = _get_error_string(omc)
|
|
||||||
raise RuntimeError(f"OpenModelica could not load the model: {details}")
|
|
||||||
|
|
||||||
result = omc.sendExpression(f"checkModel({model_name})")
|
|
||||||
details = _get_error_string(omc)
|
|
||||||
if not isinstance(result, str) or "completed successfully" not in result:
|
|
||||||
message = details or result or "Unknown OpenModelica validation error"
|
|
||||||
raise RuntimeError(f"OpenModelica model validation failed: {message}")
|
|
||||||
|
|
||||||
def _validate_model(omc, model: str, model_name: str) -> None:
|
|
||||||
if not model:
|
|
||||||
raise ValueError("There is no compiled Modelica source to validate")
|
|
||||||
if not model_name:
|
|
||||||
raise ValueError("The compiled model has no Modelica name")
|
|
||||||
|
|
||||||
loaded = omc.sendExpression(f"loadString({json.dumps(model)})")
|
|
||||||
if loaded is not True:
|
|
||||||
details = _get_error_string(omc)
|
|
||||||
raise RuntimeError(f"OpenModelica could not load the model: {details}")
|
|
||||||
|
|
||||||
result = omc.sendExpression(f"checkModel({model_name})")
|
|
||||||
details = _get_error_string(omc)
|
|
||||||
if not isinstance(result, str) or "completed successfully" not in result:
|
|
||||||
message = details or result or "Unknown OpenModelica validation error"
|
|
||||||
raise RuntimeError(f"OpenModelica model validation failed: {message}")
|
|
||||||
log.info("%s", result)
|
|
||||||
if details:
|
|
||||||
log.warning("OpenModelica validation messages: %s", details)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_error_string(omc) -> str:
|
|
||||||
"""Read OMC's deliberately unparsed error response as plain text."""
|
|
||||||
|
|
||||||
raw = omc.sendExpression("getErrorString()", parsed=False)
|
|
||||||
if not isinstance(raw, str):
|
|
||||||
return str(raw or "")
|
|
||||||
raw = raw.strip()
|
|
||||||
if not raw:
|
|
||||||
return ""
|
|
||||||
try:
|
|
||||||
decoded = json.loads(raw)
|
|
||||||
except (TypeError, json.JSONDecodeError):
|
|
||||||
return raw
|
|
||||||
return decoded if isinstance(decoded, str) else str(decoded)
|
|
||||||
|
|
||||||
|
|
||||||
def _openmodelica_home(openmodelica_path: str) -> str | None:
|
|
||||||
"""Convert an optional omc executable path to the home expected by OMPython."""
|
|
||||||
|
|
||||||
if not openmodelica_path.strip():
|
|
||||||
return None
|
|
||||||
path = Path(os.path.expandvars(openmodelica_path)).expanduser()
|
|
||||||
if path.name.lower() in {"omc", "omc.exe"}:
|
|
||||||
return str(path.parent.parent)
|
|
||||||
return str(path)
|
|
||||||
@@ -1,50 +1,151 @@
|
|||||||
|
from collections.abc import Callable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from bedit.core.application_log import get_logger
|
from bedit.core.application_log import get_logger
|
||||||
from bedit.core.simulation.compiler import compile_graph
|
from bedit.core.simulation.composer import compose_graph
|
||||||
from bedit.core.simulation.runner import run_simulation_async, run_validation_async
|
from bedit.core.simulation.openmodelica import (
|
||||||
|
ErrorCallback,
|
||||||
|
OpenModelicaInterface,
|
||||||
|
ResultCallback,
|
||||||
|
SimulationMessage,
|
||||||
|
SimulationProgress,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Simulation:
|
class Simulation:
|
||||||
"""Application-owned simulation state and compiler facade."""
|
"""Application-owned composition state and OpenModelica interface."""
|
||||||
|
|
||||||
def __init__(self, *, openmodelica_path: str = "") -> None:
|
def __init__(self, *, openmodelica_path: str = "") -> None:
|
||||||
self.state: dict[str, Any] = {}
|
self.state: dict[str, Any] = {}
|
||||||
self.last_compilation_input: dict[str, Any] | None = None
|
self.last_composition_input: dict[str, Any] | None = None
|
||||||
self.last_compilation_output: str | None = None
|
self.last_composition_output: str | None = None
|
||||||
self.id_list: dict[str, Any] = {}
|
self.id_list: dict[str, Any] = {}
|
||||||
self.openmodelica_path = openmodelica_path
|
self.model_name: str | None = None
|
||||||
|
self._openmodelica_path = openmodelica_path
|
||||||
|
self.openmodelica = OpenModelicaInterface(openmodelica_path)
|
||||||
|
self.model_path: str | None = None
|
||||||
|
self.simulation_progress: SimulationProgress | None = None
|
||||||
|
|
||||||
def compile(self, graph: dict[str, Any]) -> None:
|
@property
|
||||||
"""Compile a serialized component tree and retain the result."""
|
def openmodelica_path(self) -> str:
|
||||||
|
return self._openmodelica_path
|
||||||
|
|
||||||
self._prepare_compilation(graph)
|
@openmodelica_path.setter
|
||||||
run_validation_async(
|
def openmodelica_path(self, value: str) -> None:
|
||||||
self.openmodelica_path,
|
self._openmodelica_path = value
|
||||||
self.last_compilation_output,
|
self.openmodelica.configure(value)
|
||||||
self.model_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _prepare_compilation(self, graph: dict[str, Any]) -> None:
|
def compose(
|
||||||
"""Generate and retain Modelica without starting a background operation."""
|
self,
|
||||||
|
graph: dict[str, Any],
|
||||||
|
callback: Callable[[str], None] | None = None,
|
||||||
|
error_callback: ErrorCallback | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Compose and retain the active graph's Modelica representation."""
|
||||||
|
|
||||||
result = compile_graph(graph)
|
self.model_path = None
|
||||||
self.last_compilation_input = result.graph
|
|
||||||
|
# Create openmodelica model
|
||||||
|
result = compose_graph(graph)
|
||||||
|
self.last_composition_input = result.graph
|
||||||
self.id_list = result.objects_by_id
|
self.id_list = result.objects_by_id
|
||||||
self.last_compilation_output = result.modelica
|
self.last_composition_output = result.modelica
|
||||||
self.model_name = result.model_name
|
self.model_name = result.model_name
|
||||||
|
|
||||||
def run_simulation(self, graph: dict[str, Any]) -> None:
|
def _model_compiled(result):
|
||||||
"""Start the simulation without blocking the calling UI thread."""
|
log.info("Compiling OK: %s", result)
|
||||||
|
try:
|
||||||
|
self.model_path = str(result[0])
|
||||||
|
except (IndexError, TypeError) as error:
|
||||||
|
failure = RuntimeError(
|
||||||
|
f"OpenModelica returned an invalid build result: {result!r}"
|
||||||
|
)
|
||||||
|
failure.__cause__ = error
|
||||||
|
if error_callback is not None:
|
||||||
|
error_callback(failure)
|
||||||
|
else:
|
||||||
|
log.error("%s", failure)
|
||||||
|
return
|
||||||
|
if callback is not None:
|
||||||
|
callback(self.model_path)
|
||||||
|
|
||||||
# Always regenerate before running, then validate and simulate as one
|
self.openmodelica.build_model(
|
||||||
# background operation so the two phases cannot compete for the worker.
|
self.last_composition_output,
|
||||||
self._prepare_compilation(graph)
|
|
||||||
run_simulation_async(
|
|
||||||
self.openmodelica_path,
|
|
||||||
self.last_compilation_output,
|
|
||||||
self.model_name,
|
self.model_name,
|
||||||
|
_model_compiled,
|
||||||
|
error_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def run_simulation(
|
||||||
|
self,
|
||||||
|
graph: dict[str, Any],
|
||||||
|
progress_callback: Callable[[SimulationProgress], None] | None = None,
|
||||||
|
message_callback: Callable[[SimulationMessage], None] | None = None,
|
||||||
|
callback: ResultCallback | None = None,
|
||||||
|
error_callback: ErrorCallback | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Compose, build, and asynchronously run the current graph."""
|
||||||
|
|
||||||
|
self.simulation_progress = None
|
||||||
|
|
||||||
|
def report_progress(progress: SimulationProgress) -> None:
|
||||||
|
self.simulation_progress = progress
|
||||||
|
if progress_callback is not None:
|
||||||
|
progress_callback(progress)
|
||||||
|
|
||||||
|
def run_model(model_path: str) -> None:
|
||||||
|
try:
|
||||||
|
arguments = self.build_simulation_arguments()
|
||||||
|
except Exception as error:
|
||||||
|
if error_callback is not None:
|
||||||
|
error_callback(error)
|
||||||
|
else:
|
||||||
|
log.exception("Could not prepare simulation arguments")
|
||||||
|
return
|
||||||
|
self.openmodelica.run_model(
|
||||||
|
model_path,
|
||||||
|
arguments,
|
||||||
|
report_progress,
|
||||||
|
message_callback,
|
||||||
|
callback,
|
||||||
|
error_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.compose(graph, run_model, error_callback)
|
||||||
|
|
||||||
|
def get_progress(self) -> SimulationProgress | None:
|
||||||
|
"""Return the most recently received simulation status."""
|
||||||
|
|
||||||
|
return self.simulation_progress
|
||||||
|
|
||||||
|
def shutdown(self, *, wait: bool = True) -> None:
|
||||||
|
"""Close OpenModelica and clean its generated working directory."""
|
||||||
|
self.openmodelica.shutdown(wait=wait)
|
||||||
|
self.model_path = None
|
||||||
|
|
||||||
|
def build_simulation_arguments(self) -> list[str]:
|
||||||
|
opts = self.last_composition_input["implementation"]["graph"].get(
|
||||||
|
"simulation", {}
|
||||||
|
)
|
||||||
|
start_time = float(opts.get("startTime", 0.0))
|
||||||
|
stop_time = float(opts.get("stopTime", 1.0))
|
||||||
|
interval_mode = opts.get("intervalMode", "numberOfIntervals")
|
||||||
|
interval_time = float(opts.get("intervalTime", 0.002))
|
||||||
|
if interval_mode == "numberOfIntervals":
|
||||||
|
intervals = int(opts.get("numberOfIntervals", 500))
|
||||||
|
if intervals <= 0:
|
||||||
|
raise ValueError("Number of simulation intervals must be positive")
|
||||||
|
interval_time = (stop_time - start_time) / intervals
|
||||||
|
if interval_time <= 0:
|
||||||
|
raise ValueError("Simulation interval must be positive")
|
||||||
|
arguments = [
|
||||||
|
"-outputFormat=csv",
|
||||||
|
f"-startTime={start_time}",
|
||||||
|
f"-stopTime={stop_time}",
|
||||||
|
f"-stepSize={interval_time}",
|
||||||
|
]
|
||||||
|
log.info("Running model with: %s", arguments)
|
||||||
|
return arguments
|
||||||
|
|||||||
BIN
BEdit/src/bedit/data/libraries/default.beb
Normal file
BIN
BEdit/src/bedit/data/libraries/default.beb
Normal file
Binary file not shown.
@@ -345,7 +345,7 @@ class PasteSelectionCommand(QUndoCommand):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
controller,
|
controller,
|
||||||
owner_id: str,
|
owner_id: str | None,
|
||||||
blocks: dict[str, Component],
|
blocks: dict[str, Component],
|
||||||
connections: dict[str, Connection],
|
connections: dict[str, Connection],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -421,17 +421,29 @@ class DocumentController(QObject):
|
|||||||
if old != new:
|
if old != new:
|
||||||
self.undo_stack.push(EditGraphParametersCommand(self, old, new))
|
self.undo_stack.push(EditGraphParametersCommand(self, old, new))
|
||||||
|
|
||||||
def compile_active_graph(self) -> None:
|
def compose_active_graph(self) -> None:
|
||||||
component = self.active_component
|
component = self.active_component
|
||||||
if component is None or component.implementation_kind != "graph":
|
if component is None or component.implementation_kind != "graph":
|
||||||
raise ValueError("Open a graph component before compiling")
|
raise ValueError("Open a graph component before composing")
|
||||||
self.simulation.compile(component.to_dict())
|
self.simulation.compose(component.to_dict())
|
||||||
|
|
||||||
def run_simulation(self) -> None:
|
def run_simulation(
|
||||||
|
self,
|
||||||
|
progress_callback=None,
|
||||||
|
message_callback=None,
|
||||||
|
callback=None,
|
||||||
|
error_callback=None,
|
||||||
|
) -> None:
|
||||||
component = self.active_component
|
component = self.active_component
|
||||||
if component is None or component.implementation_kind != "graph":
|
if component is None or component.implementation_kind != "graph":
|
||||||
raise ValueError("Open a graph component before running a simulation")
|
raise ValueError("Open a graph component before running a simulation")
|
||||||
self.simulation.run_simulation(component.to_dict())
|
self.simulation.run_simulation(
|
||||||
|
component.to_dict(),
|
||||||
|
progress_callback,
|
||||||
|
message_callback,
|
||||||
|
callback,
|
||||||
|
error_callback,
|
||||||
|
)
|
||||||
|
|
||||||
def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
|
def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
|
||||||
item = (
|
item = (
|
||||||
@@ -951,6 +963,68 @@ class DocumentController(QObject):
|
|||||||
self.undo_stack.push(PasteSelectionCommand(self, owner.id, blocks, connections))
|
self.undo_stack.push(PasteSelectionCommand(self, owner.id, blocks, connections))
|
||||||
return list(blocks)
|
return list(blocks)
|
||||||
|
|
||||||
|
def paste_components_to(
|
||||||
|
self,
|
||||||
|
owner_id: str | None,
|
||||||
|
source_components: list[Component],
|
||||||
|
source_connections: list[Connection] | None = None,
|
||||||
|
) -> list[str]:
|
||||||
|
"""Clone components into the document root or a graph at origin."""
|
||||||
|
|
||||||
|
if self.document is None:
|
||||||
|
raise ValueError("Open or create a document before pasting components")
|
||||||
|
if owner_id is None:
|
||||||
|
siblings = self.document.roots.values()
|
||||||
|
else:
|
||||||
|
owner = self.document.find_component(owner_id)
|
||||||
|
if owner is None or owner.implementation_kind != "graph":
|
||||||
|
raise ValueError("Components can only be pasted into a graph")
|
||||||
|
siblings = owner.graph.blocks.values()
|
||||||
|
|
||||||
|
pairs = [(source, clone_component(source)) for source in source_components]
|
||||||
|
if not pairs:
|
||||||
|
return []
|
||||||
|
id_map = {source.id: clone.id for source, clone in pairs}
|
||||||
|
used = list(siblings)
|
||||||
|
minimum_x = min(source.x for source, _clone in pairs)
|
||||||
|
minimum_y = min(source.y for source, _clone in pairs)
|
||||||
|
blocks: dict[str, Component] = {}
|
||||||
|
for source, clone in pairs:
|
||||||
|
clone.name = self._available_component_name(source.name, used, 0)
|
||||||
|
if owner_id is not None:
|
||||||
|
clone.x = source.x - minimum_x
|
||||||
|
clone.y = source.y - minimum_y
|
||||||
|
blocks[clone.id] = clone
|
||||||
|
used.append(clone)
|
||||||
|
|
||||||
|
connections: dict[str, Connection] = {}
|
||||||
|
if owner_id is not None:
|
||||||
|
for source in source_connections or []:
|
||||||
|
if source.source.block not in id_map or source.target.block not in id_map:
|
||||||
|
continue
|
||||||
|
properties = deepcopy(source.properties)
|
||||||
|
for point in properties.get("waypoints", []):
|
||||||
|
if isinstance(point, dict):
|
||||||
|
point["x"] = float(point.get("x", 0)) - minimum_x
|
||||||
|
point["y"] = float(point.get("y", 0)) - minimum_y
|
||||||
|
connection = Connection(
|
||||||
|
id=str(uuid4()),
|
||||||
|
source=Endpoint(
|
||||||
|
block=id_map[source.source.block], port=source.source.port
|
||||||
|
),
|
||||||
|
target=Endpoint(
|
||||||
|
block=id_map[source.target.block], port=source.target.port
|
||||||
|
),
|
||||||
|
name=source.name,
|
||||||
|
properties=properties,
|
||||||
|
)
|
||||||
|
connections[connection.id] = connection
|
||||||
|
|
||||||
|
self.undo_stack.push(
|
||||||
|
PasteSelectionCommand(self, owner_id, blocks, connections)
|
||||||
|
)
|
||||||
|
return list(blocks)
|
||||||
|
|
||||||
def _graph_for(self, owner_id: str):
|
def _graph_for(self, owner_id: str):
|
||||||
if self.document is None:
|
if self.document is None:
|
||||||
raise ValueError("There is no open document")
|
raise ValueError("There is no open document")
|
||||||
|
|||||||
@@ -2326,6 +2326,63 @@ K\x80@\x89\x15\x8d\xc04\xd5\xb5^\xaf\x1bx\xfa\x19\
|
|||||||
\x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\
|
\x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\
|
||||||
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
|
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
|
||||||
\xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\
|
\xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\
|
||||||
|
\x00\x00\x03m\
|
||||||
|
\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\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||||
|
\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
|
||||||
|
\x00\x00\x09pHYs\x00\x00\x1b\xaf\x00\x00\x1b\xaf\x01\
|
||||||
|
^\x1a\x91\x1c\x00\x00\x00\x07tIME\x07\xd9\x02\x10\
|
||||||
|
\x17\x22\x16\x993\xa5<\x00\x00\x02\xedIDATx\
|
||||||
|
\xda\xed\x97_H\xd3Q\x14\xc7\xcf\xd9\xef7\xcb\xca\x89\
|
||||||
|
B \x91\x0f\xc9\xcczha\xf6\xa8\xb4\x06M\x90f\
|
||||||
|
aiV$\x96V\x12\xf9P-S\x90d%Lh\
|
||||||
|
V/B.\x8d\xc2\xb2\xc0\x1erS\xc1\x84\x1cdo\
|
||||||
|
a\xa0\x0f>h\x0f\xfdy\xf0-\x9b\xa6\x0f\xb1{\xfb\
|
||||||
|
n\xf4\x8b\x91\xe2\x5cn\xae\x07\x0f\xdc\x9dq/\xfc>\
|
||||||
|
\xe7{\xce\xe1\xdc\xdf\x8f\xd6M\xb3\x86\x86\x86\xe2Dp\
|
||||||
|
u\xbf\xe19pG\x82>!\x01\x04\xe1\x9aOd\x09\
|
||||||
|
:\x13V\x02\xcd\xd6\x03H\x84\xa9\x04\xab\xaf\xaf\xef\x84\
|
||||||
|
\xcb\x8f\x07\xe0z\xcf\x82AU\xc8\xa6\xeax\xfc\xf6\xd1\
|
||||||
|
\x8dcKf\xa0\xa5\xa5\xa5\x0an$V\xd0\xea\xc7\xf3\
|
||||||
|
\x86\x9a\xae\xf9\xd3\xb5\xdd\xf3\xaf\x84\xa4\xcfRR\xa9\x5c\
|
||||||
|
&\x031\xb3\xf2\xf6\x1f\xb9P\xdb\x14\x90\xd2\xacH\xf6\
|
||||||
|
\x01\xda\x13\x10T\xd1Z\x96\xec\xa7x\x07P\xd26\xc7\
|
||||||
|
P\xdb!\x04\xbd\x10L\x15\xedg7-\x82\xc65\x00\
|
||||||
|
\xc0m\x02?\x82\xd9\xf5\xa4j\xb3\x5c\xd3&,\xba\x1f\
|
||||||
|
R\x8f\xd4\x93\xe3\xe5\xc5\x08\xf0X7\xa1\xa6> B\
|
||||||
|
\xde\xbb&s\xc0\xb3\xf3x\x81'\xbbt\x1f\xc1,w\
|
||||||
|
f\x19M\xd7$\xa0\xbe\xf7\xf2\x16\x19\xf5\x1c\x88\x0e|\
|
||||||
|
L!\xc9\x8d$u\x97X\xa8\x0b\xde\xac\x0aS\xab$\
|
||||||
|
3\x07\xd5\xb3\xd4\xd4\xc7'\x00\xa8\xce\x00\xfc)\xe0\xcc\
|
||||||
|
\xa4\xdbK2\xe9\x96\x94\xaa\x0bu\xcfc\xa8\x1f\xbc\x92\
|
||||||
|
\x22\xffq\x14GnB\xa4\xdbBRy\xcfRy\xcb\
|
||||||
|
\xa4XmS\xcf\xa7I\xe8\xed,\xf4\x85\xbb&\xc7\xb4\
|
||||||
|
\xda\xc7\xfe:\x06X\xf1d\x975y\x8c\xe5_\xbd\xc6\
|
||||||
|
\x93\x96\xf0\xb3\x5c\x87\x9f++\xdf\x8d\xf6m\xbf\xfa\xa9\
|
||||||
|
o[\x9d!\xe6\x97\x11\xc0\x198\x1e\x84\xea\x02\xa4|\
|
||||||
|
?T\xbf\x09?\x97\x92\x8a\xc7\xb3\xf6H)\x92^#\
|
||||||
|
\x1b\xae\xe5 \x03\x06\xb7e \xa5\xc3\xb4\xd2\x00\x00?\
|
||||||
|
\x91\x87Z\x87R\x0e_h\x9b\xea\x9e\x0e?\xdf\xdd\xf8\
|
||||||
|
\x9d\x03B\xde\x0cv>\x9a\xf1\x1aK\xbd\xb5\x7fk\xb3\
|
||||||
|
u1\xb8\xdd0`x\xe8F\xf9\x1e\xe19\xea\x8a\x03\
|
||||||
|
`\xa9\xfb\x09\xf8)\x80\x1d\xb6\x8f\xcf\x02\xda~\xa6}\
|
||||||
|
\x86v\xdc\x981\x06\x045\x02NB\x90\xf7\xf0t\xb3\
|
||||||
|
\x1f\x19\xb8\x80\xe5\xeeOo5\x84\xa9>\x040\x1aD\
|
||||||
|
!\xc0MEs\xe7F\x97diM\xc8\xcc\xf9N\xa7\
|
||||||
|
3'\xfc0\xb9\xe6\x1b\xe9\x98\x8c\xaaB\x07\x92\x142\
|
||||||
|
\xebU6\xeb\xf1\xbc$\x85}\xd8\xbb;\xd1\x9c\xfa\xe1\
|
||||||
|
\x0f0\xfd\x9e;\x04\x13\x1b\xecD\xec\x02\xdcJ\x92\xcf\
|
||||||
|
\x03<\x14\xf5$\x04\xd8\x84\xd5\x85\xbf_P\xeba,\
|
||||||
|
3\x96\x0f\x8a\x0fB}\xe6\xa43\xf5L\x08\x1enB\
|
||||||
|
\xb5\x13J\x01\xb5\x13a\xaa\x87V3\x07|X\x0eI\
|
||||||
|
4\xe5oK\x8b|\x1f\xcc\xd4\xfa\xd1h%\x00\xa7\x02\
|
||||||
|
<\xbc\xaaA\xb4\xf0 m\x0c\x0e+:+\x9a\xad\x1e\
|
||||||
|
\xfd\xaf\xdf\x091k\xea\xfe\xfe\xf8\xe1H\xdf\x05R\xca\
|
||||||
|
|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\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\
|
||||||
@@ -2396,6 +2453,35 @@ f\xa2\xd1\xa5\x5c\x22\xb4\x91SZ\xd5u\xd7\x0a\xd2]\
|
|||||||
|\x01\x85\x09\x800\x7fss\xd3{\xf6\x7fABG\
|
|\x01\x85\x09\x800\x7fss\xd3{\xf6\x7fABG\
|
||||||
Y\x01\x05l*\xfc\x00!\x00\x12\xf1%U\xb6\x0e\x00\
|
Y\x01\x05l*\xfc\x00!\x00\x12\xf1%U\xb6\x0e\x00\
|
||||||
\x00\x00\x00IEND\xaeB`\x82\
|
\x00\x00\x00IEND\xaeB`\x82\
|
||||||
|
\x00\x00\x01\xad\
|
||||||
|
\x89\
|
||||||
|
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||||
|
\x00\x00 \x00\x00\x00 \x08\x03\x00\x00\x00D\xa4\x8a\xc6\
|
||||||
|
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||||
|
\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01B(\
|
||||||
|
\x9bx\x00\x00\x00\x07tIME\x07\xd9\x0c\x1c\x03\x1c\
|
||||||
|
\x0e%S,b\x00\x00\x00uPLTE\x00\x00\x00\
|
||||||
|
\x13\x13\x13\x0a\x0a\x0a\x0b\x0b\x0b\x00\x00\x00\x00\x00\x00\x00\
|
||||||
|
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6\xc6\xc8\xc7\xc7\
|
||||||
|
\xc9\xc9\xc9\xcb\xcc\xcc\xce\xce\xce\xd0\xd0\xd0\xd2\xd4\xd4\xd6\
|
||||||
|
\xd7\xd7\xd8\xd7\xd7\xd9\xd8\xd8\xda\xd9\xd9\xdb\xda\xda\xdb\xda\
|
||||||
|
\xda\xdc\xdb\xdb\xdc\xdb\xdb\xdd\xdd\xdd\xdf\xde\xde\xdf\xe2\xe2\
|
||||||
|
\xe3\xe3\xe3\xe5\xe3\xe3\xe6\xe4\xe4\xe6\xe6\xe6\xe7\xe7\xe7\xe9\
|
||||||
|
\xe9\xe9\xea\xeb\xeb\xec\xed\xed\xee\xf0\xf0\xf1\xf3\xf3\xf4\xff\
|
||||||
|
\xff\xff\xd3\x9b\xcc\x0e\x00\x00\x00\x0atRNS\x00\x09\
|
||||||
|
\x15\x15\x1825678\xb5\xcc\xc0\x1e\x00\x00\x00\x01\
|
||||||
|
bKGD&Z\x08\x98\xb5\x00\x00\x00\x9bIDA\
|
||||||
|
Tx\xda\xd5\x93\xcb\x0e\x820\x10\x00\x8b\x0aEP|\
|
||||||
|
u\xc1G)\x94\x02\xff\xff\x89.]\x0e\x18\xccr1\
|
||||||
|
F\xe72\xd9d\xd26\x9bT\xfc\x02\xc1\x86%\x10a\
|
||||||
|
\xcf\x12\x0a\xd9)\x86N\x0a\xd9^\x18Z\x0c\xdc\x91\xc1\
|
||||||
|
a\xd0\x1c\x18\x1a\x0c\xec\x9e\xc1bP'\x9et\x97\xce\
|
||||||
|
\x95\xd4\x18\x94=q#\xddIWR\x89\x81\x1e\x03E\
|
||||||
|
\x82\x97IO\x02\x98\x8b\x82G\x01\x9e\xec\x8d\x8aj\xf1\
|
||||||
|
\x84\xcf\xbc!W\x03\x90\x8d\x02oR^-^\xf1?\
|
||||||
|
{\xe0\x83\xc8\x1a=`\xce\xc6\xebd\xa6\x93\x8b\xc4*\
|
||||||
|
\xde2\xc4\xeb/|\xcd'\xec\xbfO\xbf\x90M\x1a\x0a\
|
||||||
|
\x00\x00\x00\x00IEND\xaeB`\x82\
|
||||||
\x00\x00\x02\x92\
|
\x00\x00\x02\x92\
|
||||||
\x89\
|
\x89\
|
||||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||||
@@ -2632,10 +2718,20 @@ qt_resource_name = b"\
|
|||||||
\x00d\
|
\x00d\
|
||||||
\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\
|
\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\
|
||||||
\
|
\
|
||||||
|
\x00\x15\
|
||||||
|
\x02\xb4\x1f\x07\
|
||||||
|
\x00o\
|
||||||
|
\x00f\x00f\x00i\x00c\x00e\x00-\x00c\x00h\x00a\x00r\x00t\x00-\x00l\x00i\x00n\x00e\
|
||||||
|
\x00.\x00p\x00n\x00g\
|
||||||
\x00\x10\
|
\x00\x10\
|
||||||
\x03\xe6\xd3g\
|
\x03\xe6\xd3g\
|
||||||
\x00d\
|
\x00d\
|
||||||
\x00r\x00a\x00w\x00-\x00e\x00l\x00l\x00i\x00p\x00s\x00e\x00.\x00p\x00n\x00g\
|
\x00r\x00a\x00w\x00-\x00e\x00l\x00l\x00i\x00p\x00s\x00e\x00.\x00p\x00n\x00g\
|
||||||
|
\x00\x13\
|
||||||
|
\x07\xd6O\x07\
|
||||||
|
\x00v\
|
||||||
|
\x00i\x00e\x00w\x00-\x00f\x00o\x00r\x00m\x00-\x00t\x00a\x00b\x00l\x00e\x00.\x00p\
|
||||||
|
\x00n\x00g\
|
||||||
\x00\x12\
|
\x00\x12\
|
||||||
\x09\xb3>\xc7\
|
\x09\xb3>\xc7\
|
||||||
\x00d\
|
\x00d\
|
||||||
@@ -2653,7 +2749,7 @@ 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\x1c\x00\x00\x00\x03\
|
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1e\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\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00N\xe8\
|
||||||
\x00\x00\x01\x9f{C\xf1'\
|
\x00\x00\x01\x9f{C\xf1'\
|
||||||
@@ -2661,11 +2757,13 @@ qt_resource_struct = b"\
|
|||||||
\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\x02\xec\x00\x00\x00\x00\x00\x01\x00\x00tV\
|
||||||
\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\x01\x9f\x84\x8f\x00\xb9\
|
||||||
\x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\
|
\x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\
|
||||||
\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\xa4\x00\x00\x00\x00\x00\x01\x00\x00<J\
|
||||||
\x00\x00\x01\x9f{C\xf1\x18\
|
\x00\x00\x01\x9f{C\xf1\x18\
|
||||||
\x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x8d\xaa\
|
\x00\x00\x03\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x91\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\xa2\x00\x00\x00\x00\x00\x01\x00\x00f\xc8\
|
||||||
\x00\x00\x01\x9f\x7fV\xd5\xc0\
|
\x00\x00\x01\x9f\x7fV\xd5\xc0\
|
||||||
@@ -2677,9 +2775,11 @@ qt_resource_struct = b"\
|
|||||||
\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\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00Kh\
|
||||||
\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\x01\x9f\x84\x8f\xa6\x10\
|
||||||
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\
|
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\
|
||||||
\x00\x00\x01\x9f{{\xa5\xd5\
|
\x00\x00\x01\x9f{{\xa5\xd5\
|
||||||
\x00\x00\x03\xca\x00\x00\x00\x00\x00\x01\x00\x00\x91\xea\
|
\x00\x00\x04&\x00\x00\x00\x00\x00\x01\x00\x00\x97\x0c\
|
||||||
\x00\x00\x01\x9f\x7f&\x83\x10\
|
\x00\x00\x01\x9f\x7f&\x83\x10\
|
||||||
\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~\
|
||||||
@@ -2703,7 +2803,7 @@ qt_resource_struct = b"\
|
|||||||
\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\x03\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x94\x80\
|
\x00\x00\x04P\x00\x00\x00\x00\x00\x01\x00\x00\x99\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\
|
||||||
|
|||||||
@@ -39,91 +39,96 @@ class Ui_MainWindow(object):
|
|||||||
self.actionGraphParameters = QAction(MainWindow)
|
self.actionGraphParameters = QAction(MainWindow)
|
||||||
self.actionGraphParameters.setObjectName(u"actionGraphParameters")
|
self.actionGraphParameters.setObjectName(u"actionGraphParameters")
|
||||||
icon1 = QIcon()
|
icon1 = QIcon()
|
||||||
icon1.addFile(u":/icons/icons/configure.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon1.addFile(u":/icons/icons/view-form-table.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionGraphParameters.setIcon(icon1)
|
self.actionGraphParameters.setIcon(icon1)
|
||||||
self.actionCompile = QAction(MainWindow)
|
self.actionCompose = QAction(MainWindow)
|
||||||
self.actionCompile.setObjectName(u"actionCompile")
|
self.actionCompose.setObjectName(u"actionCompose")
|
||||||
icon2 = QIcon()
|
icon2 = QIcon()
|
||||||
icon2.addFile(u":/icons/icons/run-build.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon2.addFile(u":/icons/icons/run-build.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionCompile.setIcon(icon2)
|
self.actionCompose.setIcon(icon2)
|
||||||
self.actionRunSimulation = QAction(MainWindow)
|
self.actionRunSimulation = QAction(MainWindow)
|
||||||
self.actionRunSimulation.setObjectName(u"actionRunSimulation")
|
self.actionRunSimulation.setObjectName(u"actionRunSimulation")
|
||||||
icon3 = QIcon()
|
icon3 = QIcon()
|
||||||
icon3.addFile(u":/icons/icons/media-playback-start.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon3.addFile(u":/icons/icons/media-playback-start.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionRunSimulation.setIcon(icon3)
|
self.actionRunSimulation.setIcon(icon3)
|
||||||
|
self.actionSimulationWindow = QAction(MainWindow)
|
||||||
|
self.actionSimulationWindow.setObjectName(u"actionSimulationWindow")
|
||||||
|
icon4 = QIcon()
|
||||||
|
icon4.addFile(u":/icons/icons/office-chart-line.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionSimulationWindow.setIcon(icon4)
|
||||||
self.actionNew = QAction(MainWindow)
|
self.actionNew = QAction(MainWindow)
|
||||||
self.actionNew.setObjectName(u"actionNew")
|
self.actionNew.setObjectName(u"actionNew")
|
||||||
icon4 = QIcon()
|
icon5 = QIcon()
|
||||||
icon4.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon5.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionNew.setIcon(icon4)
|
self.actionNew.setIcon(icon5)
|
||||||
self.actionRotateClockwise = QAction(MainWindow)
|
self.actionRotateClockwise = QAction(MainWindow)
|
||||||
self.actionRotateClockwise.setObjectName(u"actionRotateClockwise")
|
self.actionRotateClockwise.setObjectName(u"actionRotateClockwise")
|
||||||
icon5 = QIcon()
|
icon6 = QIcon()
|
||||||
icon5.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon6.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionRotateClockwise.setIcon(icon5)
|
self.actionRotateClockwise.setIcon(icon6)
|
||||||
self.actionZoomIn = QAction(MainWindow)
|
self.actionZoomIn = QAction(MainWindow)
|
||||||
self.actionZoomIn.setObjectName(u"actionZoomIn")
|
self.actionZoomIn.setObjectName(u"actionZoomIn")
|
||||||
icon6 = QIcon()
|
icon7 = QIcon()
|
||||||
icon6.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon7.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionZoomIn.setIcon(icon6)
|
self.actionZoomIn.setIcon(icon7)
|
||||||
self.actionZoomOut = QAction(MainWindow)
|
self.actionZoomOut = QAction(MainWindow)
|
||||||
self.actionZoomOut.setObjectName(u"actionZoomOut")
|
self.actionZoomOut.setObjectName(u"actionZoomOut")
|
||||||
icon7 = QIcon()
|
icon8 = QIcon()
|
||||||
icon7.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon8.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionZoomOut.setIcon(icon7)
|
self.actionZoomOut.setIcon(icon8)
|
||||||
self.actionCenterView = QAction(MainWindow)
|
self.actionCenterView = QAction(MainWindow)
|
||||||
self.actionCenterView.setObjectName(u"actionCenterView")
|
self.actionCenterView.setObjectName(u"actionCenterView")
|
||||||
icon8 = QIcon()
|
icon9 = QIcon()
|
||||||
icon8.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon9.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionCenterView.setIcon(icon8)
|
self.actionCenterView.setIcon(icon9)
|
||||||
self.actionOpen = QAction(MainWindow)
|
self.actionOpen = QAction(MainWindow)
|
||||||
self.actionOpen.setObjectName(u"actionOpen")
|
self.actionOpen.setObjectName(u"actionOpen")
|
||||||
icon9 = QIcon()
|
icon10 = QIcon()
|
||||||
icon9.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon10.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionOpen.setIcon(icon9)
|
self.actionOpen.setIcon(icon10)
|
||||||
self.actionReloadLibraries = QAction(MainWindow)
|
self.actionReloadLibraries = QAction(MainWindow)
|
||||||
self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
|
self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
|
||||||
self.actionReloadSimulation = QAction(MainWindow)
|
self.actionReloadSimulation = QAction(MainWindow)
|
||||||
self.actionReloadSimulation.setObjectName(u"actionReloadSimulation")
|
self.actionReloadSimulation.setObjectName(u"actionReloadSimulation")
|
||||||
self.actionSave = QAction(MainWindow)
|
self.actionSave = QAction(MainWindow)
|
||||||
self.actionSave.setObjectName(u"actionSave")
|
self.actionSave.setObjectName(u"actionSave")
|
||||||
icon10 = QIcon()
|
icon11 = QIcon()
|
||||||
icon10.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon11.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionSave.setIcon(icon10)
|
self.actionSave.setIcon(icon11)
|
||||||
self.actionSaveAs = QAction(MainWindow)
|
self.actionSaveAs = QAction(MainWindow)
|
||||||
self.actionSaveAs.setObjectName(u"actionSaveAs")
|
self.actionSaveAs.setObjectName(u"actionSaveAs")
|
||||||
icon11 = QIcon()
|
icon12 = QIcon()
|
||||||
icon11.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon12.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionSaveAs.setIcon(icon11)
|
self.actionSaveAs.setIcon(icon12)
|
||||||
self.actionExit = QAction(MainWindow)
|
self.actionExit = QAction(MainWindow)
|
||||||
self.actionExit.setObjectName(u"actionExit")
|
self.actionExit.setObjectName(u"actionExit")
|
||||||
self.actionClose = QAction(MainWindow)
|
self.actionClose = QAction(MainWindow)
|
||||||
self.actionClose.setObjectName(u"actionClose")
|
self.actionClose.setObjectName(u"actionClose")
|
||||||
self.actionUndo = QAction(MainWindow)
|
self.actionUndo = QAction(MainWindow)
|
||||||
self.actionUndo.setObjectName(u"actionUndo")
|
self.actionUndo.setObjectName(u"actionUndo")
|
||||||
icon12 = QIcon()
|
icon13 = QIcon()
|
||||||
icon12.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon13.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionUndo.setIcon(icon12)
|
self.actionUndo.setIcon(icon13)
|
||||||
self.actionRedo = QAction(MainWindow)
|
self.actionRedo = QAction(MainWindow)
|
||||||
self.actionRedo.setObjectName(u"actionRedo")
|
self.actionRedo.setObjectName(u"actionRedo")
|
||||||
icon13 = QIcon()
|
icon14 = QIcon()
|
||||||
icon13.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon14.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionRedo.setIcon(icon13)
|
self.actionRedo.setIcon(icon14)
|
||||||
self.actionCut = QAction(MainWindow)
|
self.actionCut = QAction(MainWindow)
|
||||||
self.actionCut.setObjectName(u"actionCut")
|
self.actionCut.setObjectName(u"actionCut")
|
||||||
icon14 = QIcon()
|
icon15 = QIcon()
|
||||||
icon14.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon15.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionCut.setIcon(icon14)
|
self.actionCut.setIcon(icon15)
|
||||||
self.actionCopy = QAction(MainWindow)
|
self.actionCopy = QAction(MainWindow)
|
||||||
self.actionCopy.setObjectName(u"actionCopy")
|
self.actionCopy.setObjectName(u"actionCopy")
|
||||||
icon15 = QIcon()
|
icon16 = QIcon()
|
||||||
icon15.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon16.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionCopy.setIcon(icon15)
|
self.actionCopy.setIcon(icon16)
|
||||||
self.actionPaste = QAction(MainWindow)
|
self.actionPaste = QAction(MainWindow)
|
||||||
self.actionPaste.setObjectName(u"actionPaste")
|
self.actionPaste.setObjectName(u"actionPaste")
|
||||||
icon16 = QIcon()
|
icon17 = QIcon()
|
||||||
icon16.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon17.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionPaste.setIcon(icon16)
|
self.actionPaste.setIcon(icon17)
|
||||||
self.actionSelectAll = QAction(MainWindow)
|
self.actionSelectAll = QAction(MainWindow)
|
||||||
self.actionSelectAll.setObjectName(u"actionSelectAll")
|
self.actionSelectAll.setObjectName(u"actionSelectAll")
|
||||||
self.actionDelete = QAction(MainWindow)
|
self.actionDelete = QAction(MainWindow)
|
||||||
@@ -213,18 +218,18 @@ class Ui_MainWindow(object):
|
|||||||
self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2)
|
self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2)
|
||||||
self.navigateUpButton = QToolButton(self.workspaceHeader)
|
self.navigateUpButton = QToolButton(self.workspaceHeader)
|
||||||
self.navigateUpButton.setObjectName(u"navigateUpButton")
|
self.navigateUpButton.setObjectName(u"navigateUpButton")
|
||||||
icon17 = QIcon()
|
icon18 = QIcon()
|
||||||
icon17.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon18.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.navigateUpButton.setIcon(icon17)
|
self.navigateUpButton.setIcon(icon18)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
|
self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
|
||||||
|
|
||||||
self.navigateDownButton = QToolButton(self.workspaceHeader)
|
self.navigateDownButton = QToolButton(self.workspaceHeader)
|
||||||
self.navigateDownButton.setObjectName(u"navigateDownButton")
|
self.navigateDownButton.setObjectName(u"navigateDownButton")
|
||||||
self.navigateDownButton.setEnabled(False)
|
self.navigateDownButton.setEnabled(False)
|
||||||
icon18 = QIcon()
|
icon19 = QIcon()
|
||||||
icon18.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon19.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.navigateDownButton.setIcon(icon18)
|
self.navigateDownButton.setIcon(icon19)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.navigateDownButton)
|
self.workspaceHeaderLayout.addWidget(self.navigateDownButton)
|
||||||
|
|
||||||
@@ -244,9 +249,9 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
self.pointerToolButton = QToolButton(self.workspaceHeader)
|
self.pointerToolButton = QToolButton(self.workspaceHeader)
|
||||||
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
||||||
icon19 = QIcon()
|
icon20 = QIcon()
|
||||||
icon19.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon20.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.pointerToolButton.setIcon(icon19)
|
self.pointerToolButton.setIcon(icon20)
|
||||||
self.pointerToolButton.setCheckable(True)
|
self.pointerToolButton.setCheckable(True)
|
||||||
self.pointerToolButton.setChecked(True)
|
self.pointerToolButton.setChecked(True)
|
||||||
|
|
||||||
@@ -254,43 +259,43 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
self.connectToolButton = QToolButton(self.workspaceHeader)
|
self.connectToolButton = QToolButton(self.workspaceHeader)
|
||||||
self.connectToolButton.setObjectName(u"connectToolButton")
|
self.connectToolButton.setObjectName(u"connectToolButton")
|
||||||
icon20 = QIcon()
|
icon21 = QIcon()
|
||||||
icon20.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon21.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.connectToolButton.setIcon(icon20)
|
self.connectToolButton.setIcon(icon21)
|
||||||
self.connectToolButton.setCheckable(True)
|
self.connectToolButton.setCheckable(True)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.connectToolButton)
|
self.workspaceHeaderLayout.addWidget(self.connectToolButton)
|
||||||
|
|
||||||
self.boxToolButton = QToolButton(self.workspaceHeader)
|
self.boxToolButton = QToolButton(self.workspaceHeader)
|
||||||
self.boxToolButton.setObjectName(u"boxToolButton")
|
self.boxToolButton.setObjectName(u"boxToolButton")
|
||||||
icon21 = QIcon()
|
icon22 = QIcon()
|
||||||
icon21.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon22.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.boxToolButton.setIcon(icon21)
|
self.boxToolButton.setIcon(icon22)
|
||||||
self.boxToolButton.setCheckable(True)
|
self.boxToolButton.setCheckable(True)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.boxToolButton)
|
self.workspaceHeaderLayout.addWidget(self.boxToolButton)
|
||||||
|
|
||||||
self.lineToolButton = QToolButton(self.workspaceHeader)
|
self.lineToolButton = QToolButton(self.workspaceHeader)
|
||||||
self.lineToolButton.setObjectName(u"lineToolButton")
|
self.lineToolButton.setObjectName(u"lineToolButton")
|
||||||
icon22 = QIcon()
|
icon23 = QIcon()
|
||||||
icon22.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon23.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.lineToolButton.setIcon(icon22)
|
self.lineToolButton.setIcon(icon23)
|
||||||
self.lineToolButton.setCheckable(True)
|
self.lineToolButton.setCheckable(True)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.lineToolButton)
|
self.workspaceHeaderLayout.addWidget(self.lineToolButton)
|
||||||
|
|
||||||
self.textToolButton = QToolButton(self.workspaceHeader)
|
self.textToolButton = QToolButton(self.workspaceHeader)
|
||||||
self.textToolButton.setObjectName(u"textToolButton")
|
self.textToolButton.setObjectName(u"textToolButton")
|
||||||
icon23 = QIcon()
|
icon24 = QIcon()
|
||||||
icon23.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon24.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.textToolButton.setIcon(icon23)
|
self.textToolButton.setIcon(icon24)
|
||||||
self.textToolButton.setCheckable(True)
|
self.textToolButton.setCheckable(True)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.textToolButton)
|
self.workspaceHeaderLayout.addWidget(self.textToolButton)
|
||||||
|
|
||||||
self.rotateToolButton = QToolButton(self.workspaceHeader)
|
self.rotateToolButton = QToolButton(self.workspaceHeader)
|
||||||
self.rotateToolButton.setObjectName(u"rotateToolButton")
|
self.rotateToolButton.setObjectName(u"rotateToolButton")
|
||||||
self.rotateToolButton.setIcon(icon5)
|
self.rotateToolButton.setIcon(icon6)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
|
self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
|
||||||
|
|
||||||
@@ -435,7 +440,8 @@ class Ui_MainWindow(object):
|
|||||||
self.menuHelp.addAction(self.actionAboutQt)
|
self.menuHelp.addAction(self.actionAboutQt)
|
||||||
self.menuSimulation.addAction(self.actionSimulationSettings)
|
self.menuSimulation.addAction(self.actionSimulationSettings)
|
||||||
self.menuSimulation.addAction(self.actionGraphParameters)
|
self.menuSimulation.addAction(self.actionGraphParameters)
|
||||||
self.menuSimulation.addAction(self.actionCompile)
|
self.menuSimulation.addAction(self.actionCompose)
|
||||||
|
self.menuSimulation.addAction(self.actionSimulationWindow)
|
||||||
self.menuSimulation.addAction(self.actionRunSimulation)
|
self.menuSimulation.addAction(self.actionRunSimulation)
|
||||||
self.fileToolbar.addAction(self.actionNew)
|
self.fileToolbar.addAction(self.actionNew)
|
||||||
self.fileToolbar.addAction(self.actionOpen)
|
self.fileToolbar.addAction(self.actionOpen)
|
||||||
@@ -451,7 +457,8 @@ class Ui_MainWindow(object):
|
|||||||
self.cameraToolbar.addAction(self.actionCenterView)
|
self.cameraToolbar.addAction(self.actionCenterView)
|
||||||
self.simulationToolbar.addAction(self.actionSimulationSettings)
|
self.simulationToolbar.addAction(self.actionSimulationSettings)
|
||||||
self.simulationToolbar.addAction(self.actionGraphParameters)
|
self.simulationToolbar.addAction(self.actionGraphParameters)
|
||||||
self.simulationToolbar.addAction(self.actionCompile)
|
self.simulationToolbar.addAction(self.actionCompose)
|
||||||
|
self.simulationToolbar.addAction(self.actionSimulationWindow)
|
||||||
self.simulationToolbar.addAction(self.actionRunSimulation)
|
self.simulationToolbar.addAction(self.actionRunSimulation)
|
||||||
|
|
||||||
self.retranslateUi(MainWindow)
|
self.retranslateUi(MainWindow)
|
||||||
@@ -472,12 +479,12 @@ class Ui_MainWindow(object):
|
|||||||
#if QT_CONFIG(statustip)
|
#if QT_CONFIG(statustip)
|
||||||
self.actionGraphParameters.setStatusTip(QCoreApplication.translate("MainWindow", u"Edit parameters throughout the active graph", None))
|
self.actionGraphParameters.setStatusTip(QCoreApplication.translate("MainWindow", u"Edit parameters throughout the active graph", None))
|
||||||
#endif // QT_CONFIG(statustip)
|
#endif // QT_CONFIG(statustip)
|
||||||
self.actionCompile.setText(QCoreApplication.translate("MainWindow", u"Compile", None))
|
self.actionCompose.setText(QCoreApplication.translate("MainWindow", u"Compose", None))
|
||||||
#if QT_CONFIG(statustip)
|
#if QT_CONFIG(statustip)
|
||||||
self.actionCompile.setStatusTip(QCoreApplication.translate("MainWindow", u"Compile the active graph for simulation", None))
|
self.actionCompose.setStatusTip(QCoreApplication.translate("MainWindow", u"Compose the active graph as an OpenModelica model", None))
|
||||||
#endif // QT_CONFIG(statustip)
|
#endif // QT_CONFIG(statustip)
|
||||||
#if QT_CONFIG(shortcut)
|
#if QT_CONFIG(shortcut)
|
||||||
self.actionCompile.setShortcut(QCoreApplication.translate("MainWindow", u"F5", None))
|
self.actionCompose.setShortcut(QCoreApplication.translate("MainWindow", u"F5", None))
|
||||||
#endif // QT_CONFIG(shortcut)
|
#endif // QT_CONFIG(shortcut)
|
||||||
self.actionRunSimulation.setText(QCoreApplication.translate("MainWindow", u"Run", None))
|
self.actionRunSimulation.setText(QCoreApplication.translate("MainWindow", u"Run", None))
|
||||||
#if QT_CONFIG(statustip)
|
#if QT_CONFIG(statustip)
|
||||||
@@ -486,6 +493,10 @@ class Ui_MainWindow(object):
|
|||||||
#if QT_CONFIG(shortcut)
|
#if QT_CONFIG(shortcut)
|
||||||
self.actionRunSimulation.setShortcut(QCoreApplication.translate("MainWindow", u"F6", None))
|
self.actionRunSimulation.setShortcut(QCoreApplication.translate("MainWindow", u"F6", None))
|
||||||
#endif // QT_CONFIG(shortcut)
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
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.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None))
|
self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None))
|
||||||
#if QT_CONFIG(statustip)
|
#if QT_CONFIG(statustip)
|
||||||
self.actionNew.setStatusTip(QCoreApplication.translate("MainWindow", u"Create a new document", None))
|
self.actionNew.setStatusTip(QCoreApplication.translate("MainWindow", u"Create a new document", None))
|
||||||
|
|||||||
193
BEdit/src/bedit/gui/generated/ui_simulation_window.py
Normal file
193
BEdit/src/bedit/gui/generated/ui_simulation_window.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Form generated from reading UI file 'simulation_window.ui'
|
||||||
|
##
|
||||||
|
## Created by: Qt User Interface Compiler version 6.11.1
|
||||||
|
##
|
||||||
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||||
|
QMetaObject, QObject, QPoint, QRect,
|
||||||
|
QSize, QTime, QUrl, Qt)
|
||||||
|
from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
|
||||||
|
QCursor, QFont, QFontDatabase, QGradient,
|
||||||
|
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 . import resources_rc
|
||||||
|
|
||||||
|
class Ui_SimulationWindow(object):
|
||||||
|
def setupUi(self, SimulationWindow):
|
||||||
|
if not SimulationWindow.objectName():
|
||||||
|
SimulationWindow.setObjectName(u"SimulationWindow")
|
||||||
|
SimulationWindow.resize(900, 650)
|
||||||
|
icon = QIcon()
|
||||||
|
icon.addFile(u":/icons/icons/office-chart-line.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
SimulationWindow.setWindowIcon(icon)
|
||||||
|
self.actionOpen = QAction(SimulationWindow)
|
||||||
|
self.actionOpen.setObjectName(u"actionOpen")
|
||||||
|
icon1 = QIcon()
|
||||||
|
icon1.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionOpen.setIcon(icon1)
|
||||||
|
self.actionSave = QAction(SimulationWindow)
|
||||||
|
self.actionSave.setObjectName(u"actionSave")
|
||||||
|
icon2 = QIcon()
|
||||||
|
icon2.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionSave.setIcon(icon2)
|
||||||
|
self.actionClear = QAction(SimulationWindow)
|
||||||
|
self.actionClear.setObjectName(u"actionClear")
|
||||||
|
icon3 = QIcon()
|
||||||
|
icon3.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionClear.setIcon(icon3)
|
||||||
|
self.actionSaveAs = QAction(SimulationWindow)
|
||||||
|
self.actionSaveAs.setObjectName(u"actionSaveAs")
|
||||||
|
icon4 = QIcon()
|
||||||
|
icon4.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionSaveAs.setIcon(icon4)
|
||||||
|
self.actionExit = QAction(SimulationWindow)
|
||||||
|
self.actionExit.setObjectName(u"actionExit")
|
||||||
|
self.actionAbout = QAction(SimulationWindow)
|
||||||
|
self.actionAbout.setObjectName(u"actionAbout")
|
||||||
|
self.actionAboutQt = QAction(SimulationWindow)
|
||||||
|
self.actionAboutQt.setObjectName(u"actionAboutQt")
|
||||||
|
self.actionToggleResults = QAction(SimulationWindow)
|
||||||
|
self.actionToggleResults.setObjectName(u"actionToggleResults")
|
||||||
|
self.actionToggleResults.setCheckable(True)
|
||||||
|
self.actionToggleResults.setChecked(True)
|
||||||
|
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.resultsLayout.addWidget(self.resultsPlaceholder)
|
||||||
|
|
||||||
|
SimulationWindow.setCentralWidget(self.centralWidget)
|
||||||
|
self.menuBar = QMenuBar(SimulationWindow)
|
||||||
|
self.menuBar.setObjectName(u"menuBar")
|
||||||
|
self.menuBar.setGeometry(QRect(0, 0, 900, 24))
|
||||||
|
self.menuFile = QMenu(self.menuBar)
|
||||||
|
self.menuFile.setObjectName(u"menuFile")
|
||||||
|
self.menuView = QMenu(self.menuBar)
|
||||||
|
self.menuView.setObjectName(u"menuView")
|
||||||
|
self.menuPanels = QMenu(self.menuView)
|
||||||
|
self.menuPanels.setObjectName(u"menuPanels")
|
||||||
|
self.menuToolbars = QMenu(self.menuView)
|
||||||
|
self.menuToolbars.setObjectName(u"menuToolbars")
|
||||||
|
self.menuHelp = QMenu(self.menuBar)
|
||||||
|
self.menuHelp.setObjectName(u"menuHelp")
|
||||||
|
SimulationWindow.setMenuBar(self.menuBar)
|
||||||
|
self.fileToolbar = QToolBar(SimulationWindow)
|
||||||
|
self.fileToolbar.setObjectName(u"fileToolbar")
|
||||||
|
self.fileToolbar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||||
|
SimulationWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.fileToolbar)
|
||||||
|
self.statusDock = QDockWidget(SimulationWindow)
|
||||||
|
self.statusDock.setObjectName(u"statusDock")
|
||||||
|
self.statusDockContents = QWidget()
|
||||||
|
self.statusDockContents.setObjectName(u"statusDockContents")
|
||||||
|
self.horizontalLayout = QHBoxLayout(self.statusDockContents)
|
||||||
|
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||||
|
self.timeLabel = QLabel(self.statusDockContents)
|
||||||
|
self.timeLabel.setObjectName(u"timeLabel")
|
||||||
|
|
||||||
|
self.horizontalLayout.addWidget(self.timeLabel)
|
||||||
|
|
||||||
|
self.progressBar = QProgressBar(self.statusDockContents)
|
||||||
|
self.progressBar.setObjectName(u"progressBar")
|
||||||
|
self.progressBar.setMaximum(10000)
|
||||||
|
self.progressBar.setValue(0)
|
||||||
|
|
||||||
|
self.horizontalLayout.addWidget(self.progressBar)
|
||||||
|
|
||||||
|
self.statusLabel = QLabel(self.statusDockContents)
|
||||||
|
self.statusLabel.setObjectName(u"statusLabel")
|
||||||
|
|
||||||
|
self.horizontalLayout.addWidget(self.statusLabel)
|
||||||
|
|
||||||
|
self.statusDock.setWidget(self.statusDockContents)
|
||||||
|
SimulationWindow.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.statusDock)
|
||||||
|
self.logDock = QDockWidget(SimulationWindow)
|
||||||
|
self.logDock.setObjectName(u"logDock")
|
||||||
|
self.logDock.setFloating(False)
|
||||||
|
self.logDock.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetFloatable|QDockWidget.DockWidgetFeature.DockWidgetMovable)
|
||||||
|
self.logDockContents = QWidget()
|
||||||
|
self.logDockContents.setObjectName(u"logDockContents")
|
||||||
|
self.logLayout = QVBoxLayout(self.logDockContents)
|
||||||
|
self.logLayout.setObjectName(u"logLayout")
|
||||||
|
self.messageList = QListWidget(self.logDockContents)
|
||||||
|
self.messageList.setObjectName(u"messageList")
|
||||||
|
self.messageList.setAlternatingRowColors(True)
|
||||||
|
|
||||||
|
self.logLayout.addWidget(self.messageList)
|
||||||
|
|
||||||
|
self.logDock.setWidget(self.logDockContents)
|
||||||
|
SimulationWindow.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.logDock)
|
||||||
|
|
||||||
|
self.menuBar.addAction(self.menuFile.menuAction())
|
||||||
|
self.menuBar.addAction(self.menuView.menuAction())
|
||||||
|
self.menuBar.addAction(self.menuHelp.menuAction())
|
||||||
|
self.menuFile.addAction(self.actionOpen)
|
||||||
|
self.menuFile.addAction(self.actionSave)
|
||||||
|
self.menuFile.addAction(self.actionSaveAs)
|
||||||
|
self.menuFile.addSeparator()
|
||||||
|
self.menuFile.addAction(self.actionClear)
|
||||||
|
self.menuFile.addSeparator()
|
||||||
|
self.menuFile.addAction(self.actionExit)
|
||||||
|
self.menuView.addAction(self.menuPanels.menuAction())
|
||||||
|
self.menuView.addAction(self.menuToolbars.menuAction())
|
||||||
|
self.menuHelp.addAction(self.actionAbout)
|
||||||
|
self.menuHelp.addAction(self.actionAboutQt)
|
||||||
|
self.fileToolbar.addAction(self.actionOpen)
|
||||||
|
self.fileToolbar.addAction(self.actionSave)
|
||||||
|
self.fileToolbar.addAction(self.actionSaveAs)
|
||||||
|
self.fileToolbar.addAction(self.actionClear)
|
||||||
|
|
||||||
|
self.retranslateUi(SimulationWindow)
|
||||||
|
|
||||||
|
QMetaObject.connectSlotsByName(SimulationWindow)
|
||||||
|
# setupUi
|
||||||
|
|
||||||
|
def retranslateUi(self, SimulationWindow):
|
||||||
|
SimulationWindow.setWindowTitle(QCoreApplication.translate("SimulationWindow", u"Simulation", None))
|
||||||
|
self.actionOpen.setText(QCoreApplication.translate("SimulationWindow", u"&Open\u2026", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.actionOpen.setShortcut(QCoreApplication.translate("SimulationWindow", u"Ctrl+O", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
self.actionSave.setText(QCoreApplication.translate("SimulationWindow", u"&Save\u2026", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.actionSave.setShortcut(QCoreApplication.translate("SimulationWindow", u"Ctrl+S", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
self.actionClear.setText(QCoreApplication.translate("SimulationWindow", u"&Clear", None))
|
||||||
|
self.actionSaveAs.setText(QCoreApplication.translate("SimulationWindow", u"Save &As\u2026", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.actionSaveAs.setShortcut(QCoreApplication.translate("SimulationWindow", u"Ctrl+Shift+S", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
self.actionExit.setText(QCoreApplication.translate("SimulationWindow", u"E&xit", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.actionExit.setShortcut(QCoreApplication.translate("SimulationWindow", u"Ctrl+W", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
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.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.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))
|
||||||
|
# retranslateUi
|
||||||
|
|
||||||
@@ -1892,6 +1892,22 @@ class GraphWorkspaceView(QGraphicsView):
|
|||||||
if self.controller is None:
|
if self.controller is None:
|
||||||
return
|
return
|
||||||
mime_data = QApplication.clipboard().mimeData()
|
mime_data = QApplication.clipboard().mimeData()
|
||||||
|
if mime_data.hasFormat(COMPONENT_MIME_TYPE):
|
||||||
|
try:
|
||||||
|
payload = json.loads(
|
||||||
|
bytes(mime_data.data(COMPONENT_MIME_TYPE)).decode("utf-8")
|
||||||
|
)
|
||||||
|
source = Component.from_dict(payload)
|
||||||
|
except (KeyError, TypeError, ValueError, json.JSONDecodeError):
|
||||||
|
return
|
||||||
|
component_id = self.controller.add_component_copy(source, QPointF(0, 0))
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
scene.clearSelection()
|
||||||
|
item = scene.component_items.get(component_id)
|
||||||
|
if item is not None:
|
||||||
|
item.setSelected(True)
|
||||||
|
return
|
||||||
if not mime_data.hasFormat(SELECTION_MIME_TYPE):
|
if not mime_data.hasFormat(SELECTION_MIME_TYPE):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import json
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PySide6.QtCore import Qt, Slot
|
from PySide6.QtCore import QByteArray, QMimeData, Qt, Slot
|
||||||
from PySide6.QtGui import QCloseEvent
|
from PySide6.QtGui import QCloseEvent
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QButtonGroup,
|
QButtonGroup,
|
||||||
|
QApplication,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QMenu,
|
QMenu,
|
||||||
@@ -12,7 +14,7 @@ from PySide6.QtWidgets import (
|
|||||||
QTabWidget,
|
QTabWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from bedit.core.model import Component
|
from bedit.core.model import Component, Connection
|
||||||
from bedit.core.application_log import get_logger
|
from bedit.core.application_log import get_logger
|
||||||
from bedit.core.serializer import DocumentSerializer
|
from bedit.core.serializer import DocumentSerializer
|
||||||
from bedit.core.simulation import Simulation
|
from bedit.core.simulation import Simulation
|
||||||
@@ -22,16 +24,20 @@ from bedit.gui.dialogs.graph_parameters import GraphParametersDialog
|
|||||||
from bedit.gui.dialogs.item_options import ItemOptionsDialog
|
from bedit.gui.dialogs.item_options import ItemOptionsDialog
|
||||||
from bedit.gui.models.library_repository import LibraryRepository
|
from bedit.gui.models.library_repository import LibraryRepository
|
||||||
from bedit.gui.models.library_tree import (
|
from bedit.gui.models.library_tree import (
|
||||||
|
COMPONENT_MIME_TYPE,
|
||||||
|
COMPONENT_ROLE,
|
||||||
COMPONENT_ID_ROLE,
|
COMPONENT_ID_ROLE,
|
||||||
COMPONENT_INSTANCE_ROLE,
|
COMPONENT_INSTANCE_ROLE,
|
||||||
ITEM_KIND_ROLE,
|
ITEM_KIND_ROLE,
|
||||||
DocumentTreeModel,
|
DocumentTreeModel,
|
||||||
LibraryTreeModel,
|
LibraryTreeModel,
|
||||||
)
|
)
|
||||||
|
from bedit.gui.graphics.workspace import SELECTION_MIME_TYPE
|
||||||
from bedit.gui.dialogs.settings import SettingsDialog
|
from bedit.gui.dialogs.settings import SettingsDialog
|
||||||
from bedit.gui.dialogs.port_options import PortOptionsDialog
|
from bedit.gui.dialogs.port_options import PortOptionsDialog
|
||||||
from bedit.gui.dialogs.parameter_options import ParameterOptionsDialog
|
from bedit.gui.dialogs.parameter_options import ParameterOptionsDialog
|
||||||
from bedit.gui.dialogs.simulation_settings import SimulationSettingsDialog
|
from bedit.gui.dialogs.simulation_settings import SimulationSettingsDialog
|
||||||
|
from bedit.gui.simulation_window import SimulationWindow
|
||||||
from bedit.gui.preferences import application_settings
|
from bedit.gui.preferences import application_settings
|
||||||
from bedit.gui.simulation_reload import reload_simulation
|
from bedit.gui.simulation_reload import reload_simulation
|
||||||
from bedit.gui.generated.ui_main_window import Ui_MainWindow
|
from bedit.gui.generated.ui_main_window import Ui_MainWindow
|
||||||
@@ -54,6 +60,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.log.info("BEdit started")
|
self.log.info("BEdit started")
|
||||||
self.settings = application_settings()
|
self.settings = application_settings()
|
||||||
self._applying_text_definition = False
|
self._applying_text_definition = False
|
||||||
|
# Keep a Python-owned top-level window. Giving it MainWindow as its Qt
|
||||||
|
# parent makes some Linux window managers inherit the BEdit window icon.
|
||||||
|
self._simulation_window = SimulationWindow()
|
||||||
|
|
||||||
self.libraries = LibraryRepository(self)
|
self.libraries = LibraryRepository(self)
|
||||||
self.simulation = Simulation(
|
self.simulation = Simulation(
|
||||||
@@ -67,6 +76,7 @@ class MainWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
self.document_tree_model = DocumentTreeModel(self.document_controller, self)
|
self.document_tree_model = DocumentTreeModel(self.document_controller, self)
|
||||||
self._configure_models()
|
self._configure_models()
|
||||||
|
QApplication.clipboard().dataChanged.connect(self._update_edit_actions)
|
||||||
self._connect_actions()
|
self._connect_actions()
|
||||||
self._populate_view_menu()
|
self._populate_view_menu()
|
||||||
self._restore_window_geometry()
|
self._restore_window_geometry()
|
||||||
@@ -100,6 +110,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.documentTreeView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
self.ui.documentTreeView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self.ui.documentTreeView.customContextMenuRequested.connect(self.show_library_context_menu)
|
self.ui.documentTreeView.customContextMenuRequested.connect(self.show_library_context_menu)
|
||||||
self.ui.documentTreeView.doubleClicked.connect(self.activate_tree_component)
|
self.ui.documentTreeView.doubleClicked.connect(self.activate_tree_component)
|
||||||
|
self.ui.documentTreeView.clicked.connect(
|
||||||
|
lambda _index: self._update_edit_actions()
|
||||||
|
)
|
||||||
|
self.ui.treeView.clicked.connect(lambda _index: self._update_edit_actions())
|
||||||
self.document_tree_model.rebuilt.connect(self.ui.documentTreeView.expandAll)
|
self.document_tree_model.rebuilt.connect(self.ui.documentTreeView.expandAll)
|
||||||
self.ui.graphView.set_model(self.document_controller)
|
self.ui.graphView.set_model(self.document_controller)
|
||||||
self.ui.graphView.componentOptionsRequested.connect(self.show_component_options)
|
self.ui.graphView.componentOptionsRequested.connect(self.show_component_options)
|
||||||
@@ -153,9 +167,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.actionUndo.triggered.connect(self.document_controller.undo_stack.undo)
|
self.ui.actionUndo.triggered.connect(self.document_controller.undo_stack.undo)
|
||||||
self.ui.actionRedo.triggered.connect(self.document_controller.undo_stack.redo)
|
self.ui.actionRedo.triggered.connect(self.document_controller.undo_stack.redo)
|
||||||
self.ui.actionDelete.triggered.connect(self.ui.graphView.delete_selected)
|
self.ui.actionDelete.triggered.connect(self.ui.graphView.delete_selected)
|
||||||
self.ui.actionCopy.triggered.connect(self.ui.graphView.copy_selection)
|
self.ui.actionCopy.triggered.connect(self.copy_selection)
|
||||||
self.ui.actionCut.triggered.connect(self.ui.graphView.cut_selection)
|
self.ui.actionCut.triggered.connect(self.cut_selection)
|
||||||
self.ui.actionPaste.triggered.connect(self.ui.graphView.paste_selection)
|
self.ui.actionPaste.triggered.connect(self.paste_selection)
|
||||||
self.ui.actionSelectAll.triggered.connect(self.ui.graphView.select_all)
|
self.ui.actionSelectAll.triggered.connect(self.ui.graphView.select_all)
|
||||||
self.ui.actionRotateClockwise.triggered.connect(self.ui.graphView.rotate_selected)
|
self.ui.actionRotateClockwise.triggered.connect(self.ui.graphView.rotate_selected)
|
||||||
self.addAction(self.ui.actionRotateClockwise)
|
self.addAction(self.ui.actionRotateClockwise)
|
||||||
@@ -166,7 +180,8 @@ class MainWindow(QMainWindow):
|
|||||||
self.show_simulation_settings
|
self.show_simulation_settings
|
||||||
)
|
)
|
||||||
self.ui.actionGraphParameters.triggered.connect(self.show_graph_parameters)
|
self.ui.actionGraphParameters.triggered.connect(self.show_graph_parameters)
|
||||||
self.ui.actionCompile.triggered.connect(self.compile_active_graph)
|
self.ui.actionCompose.triggered.connect(self.compose_active_graph)
|
||||||
|
self.ui.actionSimulationWindow.triggered.connect(self.show_simulation_window)
|
||||||
self.ui.actionRunSimulation.triggered.connect(self.run_simulation)
|
self.ui.actionRunSimulation.triggered.connect(self.run_simulation)
|
||||||
self.document_controller.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled)
|
self.document_controller.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled)
|
||||||
self.document_controller.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled)
|
self.document_controller.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled)
|
||||||
@@ -239,20 +254,30 @@ class MainWindow(QMainWindow):
|
|||||||
QMessageBox.warning(self, "Cannot change graph parameters", str(error))
|
QMessageBox.warning(self, "Cannot change graph parameters", str(error))
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def compile_active_graph(self) -> None:
|
def compose_active_graph(self) -> None:
|
||||||
try:
|
try:
|
||||||
self.document_controller.compile_active_graph()
|
self.document_controller.compose_active_graph()
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
self.log.error("Compile failed: %s", error)
|
self.log.error("Composition failed: %s", error)
|
||||||
QMessageBox.warning(self, "Cannot compile", str(error))
|
QMessageBox.warning(self, "Cannot compose", str(error))
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def show_simulation_window(self) -> None:
|
||||||
|
self._simulation_window.show()
|
||||||
|
self._simulation_window.raise_()
|
||||||
|
self._simulation_window.activateWindow()
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def run_simulation(self) -> None:
|
def run_simulation(self) -> None:
|
||||||
|
window = self._simulation_window
|
||||||
|
callbacks = window.begin_run()
|
||||||
|
self.show_simulation_window()
|
||||||
try:
|
try:
|
||||||
self.document_controller.run_simulation()
|
self.document_controller.run_simulation(*callbacks)
|
||||||
|
window.set_model_name(self.simulation.model_name)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
self.log.exception("Simulation run failed")
|
self.log.exception("Simulation run failed")
|
||||||
QMessageBox.warning(self, "Cannot run simulation", str(error))
|
window.report_start_error(error)
|
||||||
|
|
||||||
def _restore_window_geometry(self) -> None:
|
def _restore_window_geometry(self) -> None:
|
||||||
geometry = self.settings.value("window/geometry")
|
geometry = self.settings.value("window/geometry")
|
||||||
@@ -281,7 +306,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._set_graph_controls_visible(False)
|
self._set_graph_controls_visible(False)
|
||||||
self.ui.actionSimulationSettings.setEnabled(False)
|
self.ui.actionSimulationSettings.setEnabled(False)
|
||||||
self.ui.actionGraphParameters.setEnabled(False)
|
self.ui.actionGraphParameters.setEnabled(False)
|
||||||
self.ui.actionCompile.setEnabled(False)
|
self.ui.actionCompose.setEnabled(False)
|
||||||
self.ui.actionRunSimulation.setEnabled(False)
|
self.ui.actionRunSimulation.setEnabled(False)
|
||||||
self._update_edit_actions()
|
self._update_edit_actions()
|
||||||
return
|
return
|
||||||
@@ -292,7 +317,7 @@ class MainWindow(QMainWindow):
|
|||||||
is_graph = component.implementation_kind == "graph"
|
is_graph = component.implementation_kind == "graph"
|
||||||
self.ui.actionSimulationSettings.setEnabled(is_graph)
|
self.ui.actionSimulationSettings.setEnabled(is_graph)
|
||||||
self.ui.actionGraphParameters.setEnabled(is_graph)
|
self.ui.actionGraphParameters.setEnabled(is_graph)
|
||||||
self.ui.actionCompile.setEnabled(is_graph)
|
self.ui.actionCompose.setEnabled(is_graph)
|
||||||
self.ui.actionRunSimulation.setEnabled(is_graph)
|
self.ui.actionRunSimulation.setEnabled(is_graph)
|
||||||
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
|
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
|
||||||
self.ui.workspaceStack.setCurrentWidget(
|
self.ui.workspaceStack.setCurrentWidget(
|
||||||
@@ -311,8 +336,24 @@ class MainWindow(QMainWindow):
|
|||||||
has_selection = bool(
|
has_selection = bool(
|
||||||
self.ui.graphView.scene() and self.ui.graphView.scene().selectedItems()
|
self.ui.graphView.scene() and self.ui.graphView.scene().selectedItems()
|
||||||
)
|
)
|
||||||
for action in (self.ui.actionCut, self.ui.actionCopy, self.ui.actionDelete):
|
document_component_selected = bool(
|
||||||
action.setEnabled(is_graph and has_selection)
|
self.ui.documentTreeView.currentIndex().data(COMPONENT_ID_ROLE)
|
||||||
|
)
|
||||||
|
library_component_selected = isinstance(
|
||||||
|
self.ui.treeView.currentIndex().data(COMPONENT_ROLE), dict
|
||||||
|
)
|
||||||
|
document_has_focus = self._view_has_focus(self.ui.documentTreeView)
|
||||||
|
library_has_focus = self._view_has_focus(self.ui.treeView)
|
||||||
|
self.ui.actionCopy.setEnabled(
|
||||||
|
(is_graph and has_selection)
|
||||||
|
or (document_has_focus and document_component_selected)
|
||||||
|
or (library_has_focus and library_component_selected)
|
||||||
|
)
|
||||||
|
self.ui.actionCut.setEnabled(
|
||||||
|
(is_graph and has_selection)
|
||||||
|
or (document_has_focus and document_component_selected)
|
||||||
|
)
|
||||||
|
self.ui.actionDelete.setEnabled(is_graph and has_selection)
|
||||||
self.ui.actionRotateClockwise.setEnabled(
|
self.ui.actionRotateClockwise.setEnabled(
|
||||||
is_graph and self.ui.graphView.has_selected_components()
|
is_graph and self.ui.graphView.has_selected_components()
|
||||||
)
|
)
|
||||||
@@ -320,11 +361,114 @@ class MainWindow(QMainWindow):
|
|||||||
is_graph and self.ui.graphView.has_selected_components()
|
is_graph and self.ui.graphView.has_selected_components()
|
||||||
)
|
)
|
||||||
self.ui.actionSelectAll.setEnabled(is_graph)
|
self.ui.actionSelectAll.setEnabled(is_graph)
|
||||||
self.ui.actionPaste.setEnabled(is_graph)
|
can_paste_component = QApplication.clipboard().mimeData().hasFormat(
|
||||||
|
COMPONENT_MIME_TYPE
|
||||||
|
) or QApplication.clipboard().mimeData().hasFormat(SELECTION_MIME_TYPE)
|
||||||
|
self.ui.actionPaste.setEnabled(
|
||||||
|
(is_graph and not document_has_focus and not library_has_focus)
|
||||||
|
or (
|
||||||
|
document_has_focus
|
||||||
|
and self.document_controller.document is not None
|
||||||
|
and can_paste_component
|
||||||
|
)
|
||||||
|
)
|
||||||
self.ui.navigateDownButton.setEnabled(
|
self.ui.navigateDownButton.setEnabled(
|
||||||
is_graph and self.ui.graphView.has_single_selected_component()
|
is_graph and self.ui.graphView.has_single_selected_component()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def copy_selection(self) -> bool:
|
||||||
|
if self._view_has_focus(self.ui.documentTreeView):
|
||||||
|
return self._copy_tree_component(self.ui.documentTreeView)
|
||||||
|
if self._view_has_focus(self.ui.treeView):
|
||||||
|
return self._copy_tree_component(self.ui.treeView)
|
||||||
|
return self.ui.graphView.copy_selection()
|
||||||
|
|
||||||
|
def cut_selection(self) -> None:
|
||||||
|
if self._view_has_focus(self.ui.documentTreeView):
|
||||||
|
index = self.ui.documentTreeView.currentIndex()
|
||||||
|
component_id = index.data(COMPONENT_ID_ROLE)
|
||||||
|
if component_id and self._copy_tree_component(self.ui.documentTreeView):
|
||||||
|
self.document_controller.delete_component(component_id)
|
||||||
|
return
|
||||||
|
if self._view_has_focus(self.ui.treeView):
|
||||||
|
self._copy_tree_component(self.ui.treeView)
|
||||||
|
return
|
||||||
|
self.ui.graphView.cut_selection()
|
||||||
|
|
||||||
|
def paste_selection(self) -> None:
|
||||||
|
if self._view_has_focus(self.ui.documentTreeView):
|
||||||
|
self._paste_into_document_tree()
|
||||||
|
return
|
||||||
|
self.ui.graphView.paste_selection()
|
||||||
|
|
||||||
|
def _copy_tree_component(self, tree) -> bool:
|
||||||
|
component = tree.currentIndex().data(COMPONENT_ROLE)
|
||||||
|
if not isinstance(component, dict):
|
||||||
|
return False
|
||||||
|
mime_data = QMimeData()
|
||||||
|
mime_data.setData(
|
||||||
|
COMPONENT_MIME_TYPE,
|
||||||
|
QByteArray(json.dumps(component).encode("utf-8")),
|
||||||
|
)
|
||||||
|
QApplication.clipboard().setMimeData(mime_data)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _paste_into_document_tree(self) -> None:
|
||||||
|
index = self.ui.documentTreeView.currentIndex()
|
||||||
|
kind = index.data(ITEM_KIND_ROLE)
|
||||||
|
owner_id = None
|
||||||
|
if kind == "current-component":
|
||||||
|
component_id = index.data(COMPONENT_ID_ROLE)
|
||||||
|
component = (
|
||||||
|
self.document_controller.document.find_component(component_id)
|
||||||
|
if self.document_controller.document
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if component is None or component.implementation_kind != "graph":
|
||||||
|
QMessageBox.warning(
|
||||||
|
self, "Cannot Paste", "Select a graph component or the document root."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
owner_id = component.id
|
||||||
|
elif kind != "current-document":
|
||||||
|
QMessageBox.warning(
|
||||||
|
self, "Cannot Paste", "Select a graph component or the document root."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
components, connections = self._clipboard_components()
|
||||||
|
self.document_controller.paste_components_to(
|
||||||
|
owner_id, components, connections
|
||||||
|
)
|
||||||
|
except ValueError as error:
|
||||||
|
QMessageBox.warning(self, "Cannot Paste", str(error))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _clipboard_components() -> tuple[list[Component], list]:
|
||||||
|
mime_data = QApplication.clipboard().mimeData()
|
||||||
|
try:
|
||||||
|
if mime_data.hasFormat(SELECTION_MIME_TYPE):
|
||||||
|
payload = json.loads(
|
||||||
|
bytes(mime_data.data(SELECTION_MIME_TYPE)).decode("utf-8")
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
[Component.from_dict(item) for item in payload.get("components", [])],
|
||||||
|
[Connection.from_dict(item) for item in payload.get("connections", [])],
|
||||||
|
)
|
||||||
|
if mime_data.hasFormat(COMPONENT_MIME_TYPE):
|
||||||
|
payload = json.loads(
|
||||||
|
bytes(mime_data.data(COMPONENT_MIME_TYPE)).decode("utf-8")
|
||||||
|
)
|
||||||
|
return [Component.from_dict(payload)], []
|
||||||
|
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as error:
|
||||||
|
raise ValueError("The clipboard does not contain a valid component") from error
|
||||||
|
raise ValueError("The clipboard does not contain a BEdit component")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _view_has_focus(view) -> bool:
|
||||||
|
focus = QApplication.focusWidget()
|
||||||
|
return focus is view or (focus is not None and view.isAncestorOf(focus))
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def navigate_up(self) -> None:
|
def navigate_up(self) -> None:
|
||||||
if self._resolve_source_edits():
|
if self._resolve_source_edits():
|
||||||
@@ -547,6 +691,8 @@ class MainWindow(QMainWindow):
|
|||||||
def show_library_context_menu(self, position) -> None:
|
def show_library_context_menu(self, position) -> None:
|
||||||
tree_view = self.ui.documentTreeView
|
tree_view = self.ui.documentTreeView
|
||||||
index = tree_view.indexAt(position)
|
index = tree_view.indexAt(position)
|
||||||
|
if index.isValid():
|
||||||
|
tree_view.setCurrentIndex(index)
|
||||||
kind = index.data(ITEM_KIND_ROLE)
|
kind = index.data(ITEM_KIND_ROLE)
|
||||||
if kind == "current-component":
|
if kind == "current-component":
|
||||||
component_id = index.data(COMPONENT_ID_ROLE)
|
component_id = index.data(COMPONENT_ID_ROLE)
|
||||||
@@ -564,6 +710,11 @@ class MainWindow(QMainWindow):
|
|||||||
options_action = menu.addAction("Component Options…")
|
options_action = menu.addAction("Component Options…")
|
||||||
ports_action = menu.addAction("Port Options…")
|
ports_action = menu.addAction("Port Options…")
|
||||||
parameters_action = menu.addAction("Parameter Options…")
|
parameters_action = menu.addAction("Parameter Options…")
|
||||||
|
menu.addSeparator()
|
||||||
|
copy_action = menu.addAction("Copy")
|
||||||
|
cut_action = menu.addAction("Cut")
|
||||||
|
paste_action = menu.addAction("Paste")
|
||||||
|
paste_action.setEnabled(component.implementation_kind == "graph")
|
||||||
delete_action = menu.addAction("Delete")
|
delete_action = menu.addAction("Delete")
|
||||||
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
||||||
if graph_action is not None and selected is graph_action:
|
if graph_action is not None and selected is graph_action:
|
||||||
@@ -576,6 +727,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.show_component_port_options(component_id)
|
self.show_component_port_options(component_id)
|
||||||
elif selected is parameters_action:
|
elif selected is parameters_action:
|
||||||
self.show_component_parameter_options(component_id)
|
self.show_component_parameter_options(component_id)
|
||||||
|
elif selected is copy_action:
|
||||||
|
self._copy_tree_component(tree_view)
|
||||||
|
elif selected is cut_action:
|
||||||
|
if self._copy_tree_component(tree_view):
|
||||||
|
self.document_controller.delete_component(component_id)
|
||||||
|
elif selected is paste_action:
|
||||||
|
self._paste_into_document_tree()
|
||||||
elif selected is delete_action:
|
elif selected is delete_action:
|
||||||
answer = QMessageBox.question(
|
answer = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
@@ -591,24 +749,34 @@ class MainWindow(QMainWindow):
|
|||||||
menu = QMenu(self)
|
menu = QMenu(self)
|
||||||
graph_action = menu.addAction("New Graph Block")
|
graph_action = menu.addAction("New Graph Block")
|
||||||
text_action = menu.addAction("New Text Block")
|
text_action = menu.addAction("New Text Block")
|
||||||
|
menu.addSeparator()
|
||||||
|
paste_action = menu.addAction("Paste")
|
||||||
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
||||||
if selected is graph_action:
|
if selected is graph_action:
|
||||||
self.document_controller.add_root("graph")
|
self.document_controller.add_root("graph")
|
||||||
elif selected is text_action:
|
elif selected is text_action:
|
||||||
self.document_controller.add_root("text")
|
self.document_controller.add_root("text")
|
||||||
|
elif selected is paste_action:
|
||||||
|
self._paste_into_document_tree()
|
||||||
|
|
||||||
@Slot(object)
|
@Slot(object)
|
||||||
def show_external_library_context_menu(self, position) -> None:
|
def show_external_library_context_menu(self, position) -> None:
|
||||||
tree = self.ui.treeView
|
tree = self.ui.treeView
|
||||||
index = tree.indexAt(position)
|
index = tree.indexAt(position)
|
||||||
|
if index.isValid():
|
||||||
|
tree.setCurrentIndex(index)
|
||||||
component = index.data(COMPONENT_INSTANCE_ROLE)
|
component = index.data(COMPONENT_INSTANCE_ROLE)
|
||||||
if not isinstance(component, Component):
|
if not isinstance(component, Component):
|
||||||
return
|
return
|
||||||
menu = QMenu(self)
|
menu = QMenu(self)
|
||||||
|
copy_action = menu.addAction("Copy")
|
||||||
|
menu.addSeparator()
|
||||||
ports_action = menu.addAction("Port Options…")
|
ports_action = menu.addAction("Port Options…")
|
||||||
parameters_action = menu.addAction("Parameter Options…")
|
parameters_action = menu.addAction("Parameter Options…")
|
||||||
selected = menu.exec(tree.viewport().mapToGlobal(position))
|
selected = menu.exec(tree.viewport().mapToGlobal(position))
|
||||||
if selected is ports_action:
|
if selected is copy_action:
|
||||||
|
self._copy_tree_component(tree)
|
||||||
|
elif selected is ports_action:
|
||||||
dialog = PortOptionsDialog(component, self)
|
dialog = PortOptionsDialog(component, self)
|
||||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||||
old_inputs, old_outputs = deepcopy(component.inputs), deepcopy(component.outputs)
|
old_inputs, old_outputs = deepcopy(component.inputs), deepcopy(component.outputs)
|
||||||
@@ -760,6 +928,8 @@ class MainWindow(QMainWindow):
|
|||||||
event.ignore()
|
event.ignore()
|
||||||
return
|
return
|
||||||
self.settings.setValue("window/geometry", self.saveGeometry())
|
self.settings.setValue("window/geometry", self.saveGeometry())
|
||||||
|
self._simulation_window.close()
|
||||||
|
self.simulation.shutdown()
|
||||||
self.log.info("BEdit closed")
|
self.log.info("BEdit closed")
|
||||||
self.application_logger.removeHandler(self.log_handler)
|
self.application_logger.removeHandler(self.log_handler)
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ def _saved_instance_state(instance) -> dict:
|
|||||||
def reload_simulation(current):
|
def reload_simulation(current):
|
||||||
"""Reload the simulation package and return a fresh state-preserving instance."""
|
"""Reload the simulation package and return a fresh state-preserving instance."""
|
||||||
saved_state = _saved_instance_state(current)
|
saved_state = _saved_instance_state(current)
|
||||||
|
saved_state.pop("openmodelica", None)
|
||||||
|
current.shutdown(wait=True)
|
||||||
importlib.invalidate_caches()
|
importlib.invalidate_caches()
|
||||||
package = importlib.import_module(SIMULATION_PACKAGE)
|
package = importlib.import_module(SIMULATION_PACKAGE)
|
||||||
discovered: list[ModuleType] = []
|
discovered: list[ModuleType] = []
|
||||||
@@ -30,4 +32,5 @@ def reload_simulation(current):
|
|||||||
package = importlib.reload(package)
|
package = importlib.reload(package)
|
||||||
replacement = package.Simulation()
|
replacement = package.Simulation()
|
||||||
vars(replacement).update(saved_state)
|
vars(replacement).update(saved_state)
|
||||||
|
replacement.openmodelica.configure(replacement.openmodelica_path)
|
||||||
return replacement
|
return replacement
|
||||||
|
|||||||
266
BEdit/src/bedit/gui/simulation_window.py
Normal file
266
BEdit/src/bedit/gui/simulation_window.py
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PySide6.QtCore import Qt, Signal
|
||||||
|
from PySide6.QtWidgets import QFileDialog, QMainWindow, QMessageBox
|
||||||
|
|
||||||
|
from bedit.core.simulation.openmodelica import SimulationMessage, SimulationProgress
|
||||||
|
from bedit.core.simulation.results import (
|
||||||
|
SimulationExecutionResult,
|
||||||
|
SimulationResults,
|
||||||
|
load_simulation_results,
|
||||||
|
save_simulation_results,
|
||||||
|
)
|
||||||
|
from bedit.gui.generated.ui_simulation_window import Ui_SimulationWindow
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationWindow(QMainWindow):
|
||||||
|
"""Persistent viewer for live and previously saved simulation results."""
|
||||||
|
|
||||||
|
progressReceived = Signal(object)
|
||||||
|
messageReceived = Signal(object)
|
||||||
|
simulationFinished = Signal(object)
|
||||||
|
simulationFailed = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, parent=None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.ui = Ui_SimulationWindow()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
self._running = False
|
||||||
|
self._run_generation = 0
|
||||||
|
self._file_path: Path | None = None
|
||||||
|
self.results = SimulationResults()
|
||||||
|
self._connect_actions()
|
||||||
|
self._populate_view_menu()
|
||||||
|
self.splitDockWidget(
|
||||||
|
self.ui.statusDock, self.ui.logDock, Qt.Orientation.Vertical
|
||||||
|
)
|
||||||
|
self.ui.statusDock.setFixedHeight(self.ui.statusDock.sizeHint().height())
|
||||||
|
self.progressReceived.connect(self._show_progress)
|
||||||
|
self.messageReceived.connect(self._show_message)
|
||||||
|
self.simulationFinished.connect(self._show_finished)
|
||||||
|
self.simulationFailed.connect(self._show_error)
|
||||||
|
|
||||||
|
def _connect_actions(self) -> None:
|
||||||
|
self.ui.actionOpen.triggered.connect(self.open_results)
|
||||||
|
self.ui.actionSave.triggered.connect(self.save_results)
|
||||||
|
self.ui.actionSaveAs.triggered.connect(self.save_results_as)
|
||||||
|
self.ui.actionClear.triggered.connect(self.clear)
|
||||||
|
self.ui.actionExit.triggered.connect(self.close)
|
||||||
|
self.ui.actionAbout.triggered.connect(self.show_about)
|
||||||
|
self.ui.actionAboutQt.triggered.connect(
|
||||||
|
lambda: QMessageBox.aboutQt(self, "About Qt Framework")
|
||||||
|
)
|
||||||
|
self.ui.actionToggleResults.toggled.connect(self.ui.centralWidget.setVisible)
|
||||||
|
|
||||||
|
def _populate_view_menu(self) -> None:
|
||||||
|
self.ui.menuPanels.addAction(self.ui.actionToggleResults)
|
||||||
|
for panel in (self.ui.statusDock, self.ui.logDock):
|
||||||
|
self.ui.menuPanels.addAction(panel.toggleViewAction())
|
||||||
|
self.ui.menuToolbars.addAction(self.ui.fileToolbar.toggleViewAction())
|
||||||
|
|
||||||
|
def begin_run(self, model_name: str = "") -> tuple:
|
||||||
|
"""Reset the viewer and return callbacks bound to this run."""
|
||||||
|
|
||||||
|
self.clear(model_name=model_name)
|
||||||
|
generation = self._run_generation
|
||||||
|
self._running = True
|
||||||
|
self.ui.statusLabel.setText("Preparing simulation…")
|
||||||
|
return (
|
||||||
|
lambda progress: self._report_progress(generation, progress),
|
||||||
|
lambda message: self._report_message(generation, message),
|
||||||
|
lambda result: self._report_finished(generation, result),
|
||||||
|
lambda error: self._report_error(generation, error),
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_model_name(self, model_name: str | None) -> None:
|
||||||
|
if model_name:
|
||||||
|
self.results.model_name = model_name
|
||||||
|
self._update_title()
|
||||||
|
|
||||||
|
def clear(self, checked: bool = False, *, model_name: str = "") -> None:
|
||||||
|
"""Discard the displayed run and prepare an empty results document."""
|
||||||
|
|
||||||
|
del checked
|
||||||
|
self._run_generation += 1
|
||||||
|
self._running = False
|
||||||
|
self._file_path = None
|
||||||
|
self.results = SimulationResults(model_name=model_name)
|
||||||
|
self.ui.statusLabel.setText("No simulation has been run yet.")
|
||||||
|
self.ui.progressBar.setValue(0)
|
||||||
|
self.ui.timeLabel.setText("Time: 0 s")
|
||||||
|
self.ui.messageList.clear()
|
||||||
|
self.clear_result_views()
|
||||||
|
self._update_title()
|
||||||
|
|
||||||
|
def clear_result_views(self) -> None:
|
||||||
|
"""Clear custom plots before a run or loaded document is displayed.
|
||||||
|
|
||||||
|
Future graph widgets should be placed in the Designer-owned
|
||||||
|
``resultsLayout`` and reset here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.ui.resultsPlaceholder.setText(
|
||||||
|
"Simulation graphs and result controls can be added here."
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_result_views(self) -> None:
|
||||||
|
"""Populate custom plots from ``self.results.data`` and traces.
|
||||||
|
|
||||||
|
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."
|
||||||
|
)
|
||||||
|
|
||||||
|
def open_results(self) -> None:
|
||||||
|
file_name, _selected_filter = QFileDialog.getOpenFileName(
|
||||||
|
self,
|
||||||
|
"Open Simulation Results",
|
||||||
|
"",
|
||||||
|
"BEdit Binary Simulation Results (*.ber);;JSON Simulation Results (*.json)",
|
||||||
|
)
|
||||||
|
if not file_name:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
results = load_simulation_results(file_name)
|
||||||
|
except ValueError as error:
|
||||||
|
QMessageBox.warning(self, "Cannot Open Simulation Results", str(error))
|
||||||
|
return
|
||||||
|
self._run_generation += 1
|
||||||
|
self._running = False
|
||||||
|
self._file_path = Path(file_name)
|
||||||
|
self.results = results
|
||||||
|
self._display_results()
|
||||||
|
|
||||||
|
def save_results(self) -> None:
|
||||||
|
file_path = self._file_path
|
||||||
|
if file_path is None:
|
||||||
|
self.save_results_as()
|
||||||
|
return
|
||||||
|
self._save_results_to(file_path)
|
||||||
|
|
||||||
|
def save_results_as(self) -> None:
|
||||||
|
default_name = f"{_safe_file_stem(self.results.model_name)}-results.ber"
|
||||||
|
file_name, selected_filter = QFileDialog.getSaveFileName(
|
||||||
|
self,
|
||||||
|
"Save Simulation Results As",
|
||||||
|
default_name,
|
||||||
|
"BEdit Binary Simulation Results (*.ber);;JSON Simulation Results (*.json)",
|
||||||
|
)
|
||||||
|
if not file_name:
|
||||||
|
return
|
||||||
|
file_path = Path(file_name)
|
||||||
|
if file_path.suffix.lower() not in {".ber", ".json"}:
|
||||||
|
suffix = ".json" if selected_filter.startswith("JSON") else ".ber"
|
||||||
|
file_path = file_path.with_suffix(suffix)
|
||||||
|
self._save_results_to(file_path)
|
||||||
|
|
||||||
|
def _save_results_to(self, file_path: Path) -> None:
|
||||||
|
try:
|
||||||
|
save_simulation_results(file_path, self.results)
|
||||||
|
except OSError as error:
|
||||||
|
QMessageBox.warning(self, "Cannot Save Simulation Results", str(error))
|
||||||
|
return
|
||||||
|
self._file_path = file_path
|
||||||
|
self._update_title()
|
||||||
|
|
||||||
|
def _display_results(self) -> None:
|
||||||
|
status = self.results.status
|
||||||
|
self.ui.statusLabel.setText(str(status.get("phase", "Loaded results")))
|
||||||
|
self.ui.progressBar.setValue(int(status.get("progress", 0)))
|
||||||
|
self.ui.timeLabel.setText(f"Time: {float(status.get('time', 0)):g} s")
|
||||||
|
self.ui.messageList.clear()
|
||||||
|
for message in self.results.messages:
|
||||||
|
prefix = message.get("stream") or message.get("type") or "OpenModelica"
|
||||||
|
self.ui.messageList.addItem(f"{prefix}: {message.get('text', '')}")
|
||||||
|
self.clear_result_views()
|
||||||
|
self.load_result_views()
|
||||||
|
self._update_title()
|
||||||
|
|
||||||
|
def _update_title(self) -> None:
|
||||||
|
name = self.results.model_name or "Simulation"
|
||||||
|
self.setWindowTitle(f"{name} — Simulation")
|
||||||
|
|
||||||
|
def _report_progress(self, generation: int, progress: SimulationProgress) -> None:
|
||||||
|
if generation == self._run_generation:
|
||||||
|
self.progressReceived.emit(progress)
|
||||||
|
|
||||||
|
def _report_message(self, generation: int, message: SimulationMessage) -> None:
|
||||||
|
if generation == self._run_generation:
|
||||||
|
self.messageReceived.emit(message)
|
||||||
|
|
||||||
|
def _report_finished(self, generation: int, result) -> None:
|
||||||
|
if generation == self._run_generation:
|
||||||
|
self.simulationFinished.emit(result)
|
||||||
|
|
||||||
|
def _report_error(self, generation: int, error: Exception) -> None:
|
||||||
|
if generation == self._run_generation:
|
||||||
|
self.simulationFailed.emit(str(error))
|
||||||
|
|
||||||
|
def report_start_error(self, error: Exception) -> None:
|
||||||
|
self.simulationFailed.emit(str(error))
|
||||||
|
|
||||||
|
def _show_progress(self, progress: SimulationProgress) -> None:
|
||||||
|
self.results.status = {
|
||||||
|
"phase": progress.phase,
|
||||||
|
"currentStepSize": progress.current_step_size,
|
||||||
|
"time": progress.time,
|
||||||
|
"progress": progress.progress,
|
||||||
|
}
|
||||||
|
self.ui.statusLabel.setText(progress.phase or "Running")
|
||||||
|
self.ui.timeLabel.setText(f"Time: {progress.time:g} s")
|
||||||
|
self.ui.progressBar.setValue(max(0, min(10000, progress.progress)))
|
||||||
|
|
||||||
|
def _show_message(self, message: SimulationMessage) -> None:
|
||||||
|
self.results.messages.append(
|
||||||
|
{"stream": message.stream, "type": message.type, "text": message.text}
|
||||||
|
)
|
||||||
|
prefix = message.stream or message.type or "OpenModelica"
|
||||||
|
self.ui.messageList.addItem(f"{prefix}: {message.text}")
|
||||||
|
self.ui.messageList.scrollToBottom()
|
||||||
|
|
||||||
|
def _show_finished(self, result) -> None:
|
||||||
|
self._running = False
|
||||||
|
self.results.status.update(phase="Simulation finished", progress=10000)
|
||||||
|
if isinstance(result, SimulationExecutionResult):
|
||||||
|
self.results.data = result.data
|
||||||
|
self.results.metadata["processReturnCode"] = result.return_code
|
||||||
|
self.results.metadata["sourceResultFile"] = Path(result.result_file).name
|
||||||
|
else:
|
||||||
|
self.results.metadata["processResult"] = result
|
||||||
|
self.ui.progressBar.setValue(10000)
|
||||||
|
self.ui.statusLabel.setText("Simulation finished")
|
||||||
|
self.load_result_views()
|
||||||
|
|
||||||
|
def _show_error(self, message: str) -> None:
|
||||||
|
self._running = False
|
||||||
|
self.results.status["phase"] = "Simulation failed"
|
||||||
|
self.results.messages.append(
|
||||||
|
{"stream": "BEdit", "type": "error", "text": message}
|
||||||
|
)
|
||||||
|
self.ui.statusLabel.setText("Simulation failed")
|
||||||
|
self.ui.messageList.addItem(f"Error: {message}")
|
||||||
|
|
||||||
|
def show_about(self) -> None:
|
||||||
|
QMessageBox.about(
|
||||||
|
self,
|
||||||
|
"About BEdit Simulation",
|
||||||
|
"<h3>BEdit Simulation</h3>"
|
||||||
|
"<p>View live progress and open or save simulation results.</p>",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_running(self) -> bool:
|
||||||
|
return self._running
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_file_stem(model_name: str) -> str:
|
||||||
|
stem = re.sub(r"[^A-Za-z0-9_.-]+", "_", model_name).strip("._")
|
||||||
|
return stem or "simulation"
|
||||||
@@ -551,7 +551,8 @@
|
|||||||
</property>
|
</property>
|
||||||
<addaction name="actionSimulationSettings"/>
|
<addaction name="actionSimulationSettings"/>
|
||||||
<addaction name="actionGraphParameters"/>
|
<addaction name="actionGraphParameters"/>
|
||||||
<addaction name="actionCompile"/>
|
<addaction name="actionCompose"/>
|
||||||
|
<addaction name="actionSimulationWindow"/>
|
||||||
<addaction name="actionRunSimulation"/>
|
<addaction name="actionRunSimulation"/>
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="menuFile"/>
|
<addaction name="menuFile"/>
|
||||||
@@ -644,7 +645,8 @@
|
|||||||
</attribute>
|
</attribute>
|
||||||
<addaction name="actionSimulationSettings"/>
|
<addaction name="actionSimulationSettings"/>
|
||||||
<addaction name="actionGraphParameters"/>
|
<addaction name="actionGraphParameters"/>
|
||||||
<addaction name="actionCompile"/>
|
<addaction name="actionCompose"/>
|
||||||
|
<addaction name="actionSimulationWindow"/>
|
||||||
<addaction name="actionRunSimulation"/>
|
<addaction name="actionRunSimulation"/>
|
||||||
</widget>
|
</widget>
|
||||||
<action name="actionSimulationSettings">
|
<action name="actionSimulationSettings">
|
||||||
@@ -662,7 +664,7 @@
|
|||||||
<action name="actionGraphParameters">
|
<action name="actionGraphParameters">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../resources/resources.qrc">
|
<iconset resource="../resources/resources.qrc">
|
||||||
<normaloff>:/icons/icons/configure.png</normaloff>:/icons/icons/configure.png</iconset>
|
<normaloff>:/icons/icons/view-form-table.png</normaloff>:/icons/icons/view-form-table.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Graph Parameters</string>
|
<string>Graph Parameters</string>
|
||||||
@@ -671,16 +673,16 @@
|
|||||||
<string>Edit parameters throughout the active graph</string>
|
<string>Edit parameters throughout the active graph</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionCompile">
|
<action name="actionCompose">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../resources/resources.qrc">
|
<iconset resource="../resources/resources.qrc">
|
||||||
<normaloff>:/icons/icons/run-build.png</normaloff>:/icons/icons/run-build.png</iconset>
|
<normaloff>:/icons/icons/run-build.png</normaloff>:/icons/icons/run-build.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Compile</string>
|
<string>Compose</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string>Compile the active graph for simulation</string>
|
<string>Compose the active graph as an OpenModelica model</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="shortcut">
|
<property name="shortcut">
|
||||||
<string>F5</string>
|
<string>F5</string>
|
||||||
@@ -701,6 +703,18 @@
|
|||||||
<string>F6</string>
|
<string>F6</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionSimulationWindow">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/office-chart-line.png</normaloff>:/icons/icons/office-chart-line.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Simulation Window</string>
|
||||||
|
</property>
|
||||||
|
<property name="statusTip">
|
||||||
|
<string>Show the simulation results window</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionNew">
|
<action name="actionNew">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../resources/resources.qrc">
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
|||||||
244
BEdit/ui/simulation_window.ui
Normal file
244
BEdit/ui/simulation_window.ui
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>SimulationWindow</class>
|
||||||
|
<widget class="QMainWindow" name="SimulationWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>900</width>
|
||||||
|
<height>650</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Simulation</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/office-chart-line.png</normaloff>:/icons/icons/office-chart-line.png</iconset>
|
||||||
|
</property>
|
||||||
|
<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>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menuBar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>900</width>
|
||||||
|
<height>24</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<widget class="QMenu" name="menuFile">
|
||||||
|
<property name="title">
|
||||||
|
<string>&File</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionOpen"/>
|
||||||
|
<addaction name="actionSave"/>
|
||||||
|
<addaction name="actionSaveAs"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionClear"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionExit"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuView">
|
||||||
|
<property name="title">
|
||||||
|
<string>&View</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QMenu" name="menuPanels">
|
||||||
|
<property name="title">
|
||||||
|
<string>&Panels</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuToolbars">
|
||||||
|
<property name="title">
|
||||||
|
<string>&Toolbars</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<addaction name="menuPanels"/>
|
||||||
|
<addaction name="menuToolbars"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuHelp">
|
||||||
|
<property name="title">
|
||||||
|
<string>&Help</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionAbout"/>
|
||||||
|
<addaction name="actionAboutQt"/>
|
||||||
|
</widget>
|
||||||
|
<addaction name="menuFile"/>
|
||||||
|
<addaction name="menuView"/>
|
||||||
|
<addaction name="menuHelp"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QToolBar" name="fileToolbar">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>File</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="actionOpen"/>
|
||||||
|
<addaction name="actionSave"/>
|
||||||
|
<addaction name="actionSaveAs"/>
|
||||||
|
<addaction name="actionClear"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QDockWidget" name="statusDock">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Status</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="dockWidgetArea">
|
||||||
|
<number>8</number>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="statusDockContents">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="timeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Time: 0 s</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="format">
|
||||||
|
<string>%p%</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="statusLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>No simulation has been run yet.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QDockWidget" name="logDock">
|
||||||
|
<property name="floating">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="features">
|
||||||
|
<set>QDockWidget::DockWidgetFeature::DockWidgetFloatable|QDockWidget::DockWidgetFeature::DockWidgetMovable</set>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Log</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="dockWidgetArea">
|
||||||
|
<number>8</number>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="logDockContents">
|
||||||
|
<layout class="QVBoxLayout" name="logLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="messageList">
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<action name="actionOpen">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/document-open.png</normaloff>:/icons/icons/document-open.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Open…</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+O</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSave">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/document-save.png</normaloff>:/icons/icons/document-save.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Save…</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+S</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionClear">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/document-new.png</normaloff>:/icons/icons/document-new.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Clear</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSaveAs">
|
||||||
|
<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>Save &As…</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+Shift+S</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionExit">
|
||||||
|
<property name="text">
|
||||||
|
<string>E&xit</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+W</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionAbout">
|
||||||
|
<property name="text">
|
||||||
|
<string>&About Simulation Window</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionAboutQt">
|
||||||
|
<property name="text">
|
||||||
|
<string>About &Qt</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionToggleResults">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Results</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../resources/resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
"showName": false
|
"showName": false
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"showSubtree": false
|
"showSubtree": true
|
||||||
},
|
},
|
||||||
"implementation": {
|
"implementation": {
|
||||||
"kind": "graph",
|
"kind": "graph",
|
||||||
@@ -377,7 +377,7 @@
|
|||||||
"implementation": {
|
"implementation": {
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
"source": {
|
"source": {
|
||||||
"equations": "y = v+time;"
|
"equations": "y = v+sin(time);"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -652,8 +652,8 @@
|
|||||||
"enabled": false,
|
"enabled": false,
|
||||||
"startTime": 0.0,
|
"startTime": 0.0,
|
||||||
"stopTime": 10.0,
|
"stopTime": 10.0,
|
||||||
"intervalMode": "numberOfIntervals",
|
"intervalMode": "intervalTime",
|
||||||
"numberOfIntervals": 5000,
|
"numberOfIntervals": 50000,
|
||||||
"intervalTime": 0.002
|
"intervalTime": 0.002
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
m_Test.mo
Normal file
44
m_Test.mo
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
model m_Test
|
||||||
|
model m_Constant0
|
||||||
|
output Real y;
|
||||||
|
parameter Real v = 2;
|
||||||
|
equation
|
||||||
|
y = v;
|
||||||
|
end m_Constant0;
|
||||||
|
model m_gain0
|
||||||
|
input Real u;
|
||||||
|
output Real y;
|
||||||
|
parameter Real k = 2.5;
|
||||||
|
equation
|
||||||
|
y = k*u;
|
||||||
|
end m_gain0;
|
||||||
|
model m_const_and_time
|
||||||
|
output Real y;
|
||||||
|
parameter Real v = 4.8;
|
||||||
|
equation
|
||||||
|
y = v+sin(time);
|
||||||
|
end m_const_and_time;
|
||||||
|
model m_gain1
|
||||||
|
input Real u;
|
||||||
|
output Real y;
|
||||||
|
parameter Real k = -5;
|
||||||
|
equation
|
||||||
|
y = k*u;
|
||||||
|
end m_gain1;
|
||||||
|
model m_add0
|
||||||
|
input Real u[2];
|
||||||
|
output Real y;
|
||||||
|
equation
|
||||||
|
y = sum(u[i] for i in 1:2 );
|
||||||
|
end m_add0;
|
||||||
|
m_Constant0 Constant0;
|
||||||
|
m_gain0 gain0;
|
||||||
|
m_const_and_time const_and_time;
|
||||||
|
m_gain1 gain1;
|
||||||
|
m_add0 add0;
|
||||||
|
equation
|
||||||
|
gain0.u = Constant0.y;
|
||||||
|
gain1.u = const_and_time.y;
|
||||||
|
add0.u[1] = gain0.y;
|
||||||
|
add0.u[2] = gain1.y;
|
||||||
|
end m_Test;
|
||||||
Reference in New Issue
Block a user