fmu-manipulation-toolbox 1.9.1.3__py3-none-any.whl → 1.9.2b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fmu_manipulation_toolbox/__main__.py +2 -2
- fmu_manipulation_toolbox/__version__.py +1 -1
- fmu_manipulation_toolbox/assembly.py +12 -8
- fmu_manipulation_toolbox/checker.py +4 -2
- fmu_manipulation_toolbox/cli/fmucontainer.py +27 -19
- fmu_manipulation_toolbox/cli/fmusplit.py +5 -2
- fmu_manipulation_toolbox/cli/fmutool.py +8 -3
- fmu_manipulation_toolbox/cli/utils.py +11 -1
- fmu_manipulation_toolbox/container.py +292 -82
- fmu_manipulation_toolbox/ls.py +35 -0
- fmu_manipulation_toolbox/operations.py +3 -2
- fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
- fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- fmu_manipulation_toolbox/split.py +59 -3
- fmu_manipulation_toolbox/terminals.py +137 -0
- fmu_manipulation_toolbox/version.py +1 -1
- fmu_manipulation_toolbox-1.9.2b2.dist-info/METADATA +42 -0
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/RECORD +29 -29
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/entry_points.txt +1 -1
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/licenses/LICENSE.txt +1 -1
- fmu_manipulation_toolbox/gui.py +0 -749
- fmu_manipulation_toolbox/gui_style.py +0 -252
- fmu_manipulation_toolbox-1.9.1.3.dist-info/METADATA +0 -30
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/WHEEL +0 -0
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/top_level.txt +0 -0
fmu_manipulation_toolbox/gui.py
DELETED
|
@@ -1,749 +0,0 @@
|
|
|
1
|
-
import os.path
|
|
2
|
-
import sys
|
|
3
|
-
import textwrap
|
|
4
|
-
|
|
5
|
-
from PySide6.QtCore import Qt, QUrl, QDir, Signal, QPoint, QModelIndex
|
|
6
|
-
from PySide6.QtWidgets import (QApplication, QWidget, QGridLayout, QLabel, QLineEdit, QPushButton, QFileDialog,
|
|
7
|
-
QTextBrowser, QInputDialog, QMenu, QTreeView, QAbstractItemView, QTabWidget, QTableView,
|
|
8
|
-
QCheckBox)
|
|
9
|
-
from PySide6.QtGui import (QPixmap, QTextCursor, QStandardItem, QIcon, QDesktopServices, QAction,
|
|
10
|
-
QPainter, QColor, QImage, QStandardItemModel)
|
|
11
|
-
from functools import partial
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from .gui_style import gui_style, log_color
|
|
15
|
-
from .operations import *
|
|
16
|
-
from .remoting import (OperationAddRemotingWin32, OperationAddRemotingWin64, OperationAddFrontendWin32,
|
|
17
|
-
OperationAddFrontendWin64)
|
|
18
|
-
from .assembly import Assembly, AssemblyNode
|
|
19
|
-
from .checker import get_checkers
|
|
20
|
-
from .help import Help
|
|
21
|
-
from .version import __version__ as version
|
|
22
|
-
|
|
23
|
-
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class DropZoneWidget(QLabel):
|
|
27
|
-
WIDTH = 150
|
|
28
|
-
HEIGHT = 150
|
|
29
|
-
fmu = None
|
|
30
|
-
last_directory = None
|
|
31
|
-
clicked = Signal()
|
|
32
|
-
|
|
33
|
-
def __init__(self, parent=None):
|
|
34
|
-
super().__init__(parent)
|
|
35
|
-
self.setAcceptDrops(True)
|
|
36
|
-
self.set_image(None)
|
|
37
|
-
self.setProperty("class", "dropped_fmu")
|
|
38
|
-
self.setFixedSize(self.WIDTH, self.HEIGHT)
|
|
39
|
-
|
|
40
|
-
def dragEnterEvent(self, event):
|
|
41
|
-
if event.mimeData().hasUrls():
|
|
42
|
-
event.accept()
|
|
43
|
-
else:
|
|
44
|
-
event.ignore()
|
|
45
|
-
|
|
46
|
-
def dragMoveEvent(self, event):
|
|
47
|
-
if event.mimeData().hasUrls():
|
|
48
|
-
event.accept()
|
|
49
|
-
else:
|
|
50
|
-
event.ignore()
|
|
51
|
-
|
|
52
|
-
def dropEvent(self, event):
|
|
53
|
-
if event.mimeData().hasUrls():
|
|
54
|
-
event.setDropAction(Qt.DropAction.CopyAction)
|
|
55
|
-
try:
|
|
56
|
-
file_path = event.mimeData().urls()[0].toLocalFile()
|
|
57
|
-
except IndexError:
|
|
58
|
-
logger.error("Please select a regular file.")
|
|
59
|
-
return
|
|
60
|
-
self.set_fmu(file_path)
|
|
61
|
-
event.accept()
|
|
62
|
-
else:
|
|
63
|
-
event.ignore()
|
|
64
|
-
|
|
65
|
-
def mousePressEvent(self, event):
|
|
66
|
-
if self.last_directory:
|
|
67
|
-
default_directory = self.last_directory
|
|
68
|
-
else:
|
|
69
|
-
default_directory = os.path.expanduser('~')
|
|
70
|
-
|
|
71
|
-
fmu_filename, _ = QFileDialog.getOpenFileName(parent=self, caption='Select FMU to Manipulate',
|
|
72
|
-
dir=default_directory, filter="FMU files (*.fmu)")
|
|
73
|
-
if fmu_filename:
|
|
74
|
-
self.set_fmu(fmu_filename)
|
|
75
|
-
|
|
76
|
-
def set_image(self, filename=None):
|
|
77
|
-
if not filename:
|
|
78
|
-
filename = os.path.join(os.path.dirname(__file__), "resources", "drop_fmu.png")
|
|
79
|
-
elif not os.path.isfile(filename):
|
|
80
|
-
filename = os.path.join(os.path.dirname(__file__), "resources", "fmu.png")
|
|
81
|
-
|
|
82
|
-
base_image = QImage(filename).scaled(self.WIDTH, self.HEIGHT, Qt.AspectRatioMode.IgnoreAspectRatio,
|
|
83
|
-
Qt.TransformationMode.SmoothTransformation)
|
|
84
|
-
mask_filename = os.path.join(os.path.dirname(__file__), "resources", "mask.png")
|
|
85
|
-
mask_image = QImage(mask_filename).scaled(self.WIDTH, self.HEIGHT, Qt.AspectRatioMode.IgnoreAspectRatio,
|
|
86
|
-
Qt.TransformationMode.SmoothTransformation)
|
|
87
|
-
rounded_image = QImage(self.WIDTH, self.HEIGHT, QImage.Format.Format_ARGB32)
|
|
88
|
-
rounded_image.fill(QColor(0, 0, 0, 0))
|
|
89
|
-
painter = QPainter()
|
|
90
|
-
painter.begin(rounded_image)
|
|
91
|
-
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
92
|
-
painter.drawImage(QPoint(0, 0), base_image)
|
|
93
|
-
painter.drawImage(QPoint(0, 0), mask_image)
|
|
94
|
-
painter.end()
|
|
95
|
-
pixmap = QPixmap.fromImage(rounded_image)
|
|
96
|
-
|
|
97
|
-
self.setPixmap(pixmap)
|
|
98
|
-
|
|
99
|
-
def set_fmu(self, filename: str):
|
|
100
|
-
try:
|
|
101
|
-
self.last_directory = os.path.dirname(filename)
|
|
102
|
-
self.fmu = FMU(filename)
|
|
103
|
-
self.set_image(os.path.join(self.fmu.tmp_directory, "model.png"))
|
|
104
|
-
except Exception as e:
|
|
105
|
-
logger.error(f"Cannot load this FMU: {e}")
|
|
106
|
-
self.set_image(None)
|
|
107
|
-
self.fmu = None
|
|
108
|
-
self.clicked.emit()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class LogHandler(logging.Handler):
|
|
112
|
-
LOG_COLOR = {
|
|
113
|
-
logging.DEBUG: QColor(log_color["DEBUG"]),
|
|
114
|
-
logging.INFO: QColor(log_color["INFO"]),
|
|
115
|
-
logging.WARNING: QColor(log_color["WARNING"]),
|
|
116
|
-
logging.ERROR: QColor(log_color["ERROR"]),
|
|
117
|
-
logging.CRITICAL: QColor(log_color["CRITICAL"]),
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
def __init__(self, text_browser, level):
|
|
121
|
-
super().__init__(level)
|
|
122
|
-
self.text_browser: QTextBrowser = text_browser
|
|
123
|
-
logger.addHandler(self)
|
|
124
|
-
logger.setLevel(level)
|
|
125
|
-
|
|
126
|
-
def emit(self, record) -> None:
|
|
127
|
-
self.text_browser.setTextColor(self.LOG_COLOR[record.levelno])
|
|
128
|
-
self.text_browser.insertPlainText(record.msg+"\n")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
class LogWidget(QTextBrowser):
|
|
132
|
-
def __init__(self, parent=None, level=logging.INFO):
|
|
133
|
-
super().__init__(parent)
|
|
134
|
-
|
|
135
|
-
self.setMinimumWidth(900)
|
|
136
|
-
self.setMinimumHeight(500)
|
|
137
|
-
self.setSearchPaths([os.path.join(os.path.dirname(__file__), "resources")])
|
|
138
|
-
self.insertHtml('<center><img src="fmu_manipulation_toolbox.png"/></center><br/>')
|
|
139
|
-
self.log_handler = LogHandler(self, logging.DEBUG)
|
|
140
|
-
|
|
141
|
-
def loadResource(self, _, name):
|
|
142
|
-
image_path = os.path.join(os.path.dirname(__file__), "resources", name.toString())
|
|
143
|
-
return QPixmap(image_path)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class HelpWidget(QLabel):
|
|
147
|
-
HELP_URL = "https://github.com/grouperenault/fmu_manipulation_toolbox/blob/main/README.md"
|
|
148
|
-
|
|
149
|
-
def __init__(self):
|
|
150
|
-
super().__init__()
|
|
151
|
-
self.setProperty("class", "help")
|
|
152
|
-
|
|
153
|
-
filename = os.path.join(os.path.dirname(__file__), "resources", "help.png")
|
|
154
|
-
image = QPixmap(filename)
|
|
155
|
-
self.setPixmap(image)
|
|
156
|
-
self.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
157
|
-
|
|
158
|
-
def mousePressEvent(self, event):
|
|
159
|
-
QDesktopServices.openUrl(QUrl(self.HELP_URL))
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
class FilterWidget(QPushButton):
|
|
163
|
-
def __init__(self, items: Optional[list[str]] = (), parent=None):
|
|
164
|
-
super().__init__(parent)
|
|
165
|
-
self.items_selected = set(items)
|
|
166
|
-
self.nb_items = len(items)
|
|
167
|
-
self.update_filter_text()
|
|
168
|
-
if items:
|
|
169
|
-
self.menu = QMenu()
|
|
170
|
-
for item in items:
|
|
171
|
-
action = QAction(item, self)
|
|
172
|
-
action.setCheckable(True)
|
|
173
|
-
action.setChecked(True)
|
|
174
|
-
action.triggered.connect(partial(self.toggle_item, action))
|
|
175
|
-
self.menu.addAction(action)
|
|
176
|
-
self.setMenu(self.menu)
|
|
177
|
-
|
|
178
|
-
def toggle_item(self, action: QAction):
|
|
179
|
-
if not action.isChecked() and len(self.items_selected) == 1:
|
|
180
|
-
action.setChecked(True)
|
|
181
|
-
|
|
182
|
-
if action.isChecked():
|
|
183
|
-
self.items_selected.add(action.text())
|
|
184
|
-
else:
|
|
185
|
-
self.items_selected.remove(action.text())
|
|
186
|
-
|
|
187
|
-
self.update_filter_text()
|
|
188
|
-
|
|
189
|
-
def update_filter_text(self):
|
|
190
|
-
if len(self.items_selected) == self.nb_items:
|
|
191
|
-
self.setText("All causalities")
|
|
192
|
-
else:
|
|
193
|
-
self.setText(", ".join(sorted(self.items_selected)))
|
|
194
|
-
|
|
195
|
-
def get(self):
|
|
196
|
-
if len(self.items_selected) == self.nb_items:
|
|
197
|
-
return []
|
|
198
|
-
else:
|
|
199
|
-
return sorted(self.items_selected)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
class AssemblyTreeWidget(QTreeView):
|
|
203
|
-
class AssemblyTreeModel(QStandardItemModel):
|
|
204
|
-
|
|
205
|
-
def __init__(self, assembly: Assembly, parent=None):
|
|
206
|
-
super().__init__(parent)
|
|
207
|
-
|
|
208
|
-
self.lastDroppedItems = []
|
|
209
|
-
self.pendingRemoveRowsAfterDrop = False
|
|
210
|
-
self.setHorizontalHeaderLabels(['col1'])
|
|
211
|
-
self.dnd_target_node: Optional[AssemblyNode] = None
|
|
212
|
-
|
|
213
|
-
self.icon_container = QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'container.png'))
|
|
214
|
-
self.icon_fmu = QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon_fmu.png'))
|
|
215
|
-
|
|
216
|
-
if assembly:
|
|
217
|
-
self.add_node(assembly.root, self)
|
|
218
|
-
|
|
219
|
-
def add_node(self, node: AssemblyNode, parent_item):
|
|
220
|
-
# Add Container
|
|
221
|
-
item = QStandardItem(self.icon_container, node.name)
|
|
222
|
-
parent_item.appendRow(item)
|
|
223
|
-
item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsDragEnabled |
|
|
224
|
-
Qt.ItemFlag.ItemIsDropEnabled)
|
|
225
|
-
item.setData(node, role=Qt.ItemDataRole.UserRole + 1)
|
|
226
|
-
item.setData("container", role=Qt.ItemDataRole.UserRole + 2)
|
|
227
|
-
|
|
228
|
-
# Add FMU's
|
|
229
|
-
children_name = node.children.keys()
|
|
230
|
-
for fmu_name in node.fmu_names_list:
|
|
231
|
-
if fmu_name not in children_name:
|
|
232
|
-
fmu_node = QStandardItem(self.icon_fmu, fmu_name)
|
|
233
|
-
fmu_node.setData(node, role=Qt.ItemDataRole.UserRole + 1)
|
|
234
|
-
fmu_node.setData("fmu", role=Qt.ItemDataRole.UserRole + 2)
|
|
235
|
-
fmu_node.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable |
|
|
236
|
-
Qt.ItemFlag.ItemIsDragEnabled)
|
|
237
|
-
item.appendRow(fmu_node)
|
|
238
|
-
|
|
239
|
-
# Add Sub-Containers
|
|
240
|
-
for child in node.children.values():
|
|
241
|
-
self.add_node(child, item)
|
|
242
|
-
|
|
243
|
-
def insertRows(self, row, count, parent=QModelIndex()):
|
|
244
|
-
self.dnd_target_node = parent.data(role=Qt.ItemDataRole.UserRole + 1)
|
|
245
|
-
return super().insertRows(row, count, parent=parent)
|
|
246
|
-
|
|
247
|
-
def removeRows(self, row, count, parent=QModelIndex()):
|
|
248
|
-
if not self.dnd_target_node:
|
|
249
|
-
logger.error("NO DROP NODE!?")
|
|
250
|
-
|
|
251
|
-
source_index = self.itemFromIndex(parent).child(row, 0).data(role=Qt.ItemDataRole.UserRole+1)
|
|
252
|
-
logger.debug(f"{source_index} ==> {self.dnd_target_node.name}")
|
|
253
|
-
|
|
254
|
-
self.dnd_target_node = None
|
|
255
|
-
return super().removeRows(row, count, parent)
|
|
256
|
-
|
|
257
|
-
def dropMimeData(self, data, action, row, column, parent: QModelIndex):
|
|
258
|
-
if parent.column() < 0: # Avoid to drop item as a sibling of the root.
|
|
259
|
-
return False
|
|
260
|
-
return super().dropMimeData(data, action, row, column, parent)
|
|
261
|
-
|
|
262
|
-
def __init__(self, parent=None):
|
|
263
|
-
super().__init__(parent)
|
|
264
|
-
|
|
265
|
-
self.model = self.AssemblyTreeModel(None)
|
|
266
|
-
self.setModel(self.model)
|
|
267
|
-
|
|
268
|
-
self.expandAll()
|
|
269
|
-
self.setDropIndicatorShown(True)
|
|
270
|
-
self.setDragDropOverwriteMode(False)
|
|
271
|
-
self.setAcceptDrops(True)
|
|
272
|
-
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
|
273
|
-
self.setRootIsDecorated(True)
|
|
274
|
-
self.setHeaderHidden(True)
|
|
275
|
-
|
|
276
|
-
def load_container(self, filename):
|
|
277
|
-
assembly = Assembly(filename)
|
|
278
|
-
self.model = self.AssemblyTreeModel(assembly)
|
|
279
|
-
self.setModel(self.model)
|
|
280
|
-
|
|
281
|
-
def setTopIndex(self):
|
|
282
|
-
topIndex = self.model.index(0, 0, self.rootIndex())
|
|
283
|
-
logger.debug(topIndex.isValid(), topIndex.model())
|
|
284
|
-
if topIndex.isValid():
|
|
285
|
-
self.setCurrentIndex(topIndex)
|
|
286
|
-
if self.layoutCheck:
|
|
287
|
-
self.model.layoutChanged.disconnect(self.setTopIndex)
|
|
288
|
-
else:
|
|
289
|
-
if not self.layoutCheck:
|
|
290
|
-
self.model.layoutChanged.connect(self.setTopIndex)
|
|
291
|
-
self.layoutCheck = True
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def dragEnterEvent2(self, event):
|
|
295
|
-
if event.mimeData().hasImage:
|
|
296
|
-
event.accept()
|
|
297
|
-
else:
|
|
298
|
-
event.ignore()
|
|
299
|
-
|
|
300
|
-
def dragMoveEvent2(self, event):
|
|
301
|
-
if event.mimeData().hasImage:
|
|
302
|
-
event.accept()
|
|
303
|
-
else:
|
|
304
|
-
event.ignore()
|
|
305
|
-
|
|
306
|
-
def dropEvent2(self, event):
|
|
307
|
-
if event.mimeData().hasImage:
|
|
308
|
-
event.setDropAction(Qt.DropAction.CopyAction)
|
|
309
|
-
try:
|
|
310
|
-
file_path = event.mimeData().urls()[0].toLocalFile()
|
|
311
|
-
except IndexError:
|
|
312
|
-
logger.error("Please select a regular file.")
|
|
313
|
-
return
|
|
314
|
-
logger.debug(f"DROP: {file_path}")
|
|
315
|
-
event.accept()
|
|
316
|
-
else:
|
|
317
|
-
event.ignore()
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
class AssemblPropertiesWidget(QWidget):
|
|
321
|
-
def __init__(self, parent=None):
|
|
322
|
-
super().__init__(parent)
|
|
323
|
-
|
|
324
|
-
self.layout = QGridLayout()
|
|
325
|
-
self.layout.setVerticalSpacing(4)
|
|
326
|
-
self.layout.setHorizontalSpacing(4)
|
|
327
|
-
self.layout.setContentsMargins(10, 10, 10, 10)
|
|
328
|
-
self.setLayout(self.layout)
|
|
329
|
-
|
|
330
|
-
mt_check = QCheckBox("Multi-Threaded", self)
|
|
331
|
-
self.layout.addWidget(mt_check, 1, 0)
|
|
332
|
-
|
|
333
|
-
profiling_check = QCheckBox("Profiling", self)
|
|
334
|
-
self.layout.addWidget(profiling_check, 1, 1)
|
|
335
|
-
|
|
336
|
-
auto_inputs_check = QCheckBox("Auto Inputs", self)
|
|
337
|
-
self.layout.addWidget(auto_inputs_check, 0, 0)
|
|
338
|
-
|
|
339
|
-
auto_outputs_check = QCheckBox("Auto Outputs", self)
|
|
340
|
-
self.layout.addWidget(auto_outputs_check, 0, 1)
|
|
341
|
-
|
|
342
|
-
auto_links_check = QCheckBox("Auto Links", self)
|
|
343
|
-
self.layout.addWidget(auto_links_check, 0, 2)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
class AssemblyTabWidget(QTabWidget):
|
|
347
|
-
def __init__(self, parent=None):
|
|
348
|
-
super().__init__(parent)
|
|
349
|
-
|
|
350
|
-
table = AssemblPropertiesWidget(parent=self)
|
|
351
|
-
self.addTab(table, "Properties")
|
|
352
|
-
table = QTableView()
|
|
353
|
-
self.addTab(table, "Links")
|
|
354
|
-
table = QTableView()
|
|
355
|
-
self.addTab(table, "Inputs")
|
|
356
|
-
table = QTableView()
|
|
357
|
-
self.addTab(table, "Outputs")
|
|
358
|
-
table = QTableView()
|
|
359
|
-
self.addTab(table, "Start values")
|
|
360
|
-
|
|
361
|
-
self.tabBar().setDocumentMode(True)
|
|
362
|
-
self.tabBar().setExpanding(True)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
class WindowWithLayout(QWidget):
|
|
366
|
-
def __init__(self, title: str):
|
|
367
|
-
super().__init__(None) # Do not set parent to have a separated window
|
|
368
|
-
self.setWindowTitle(title)
|
|
369
|
-
|
|
370
|
-
self.layout = QGridLayout()
|
|
371
|
-
self.layout.setVerticalSpacing(4)
|
|
372
|
-
self.layout.setHorizontalSpacing(4)
|
|
373
|
-
self.layout.setContentsMargins(10, 10, 10, 10)
|
|
374
|
-
self.setLayout(self.layout)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
class MainWindow(WindowWithLayout):
|
|
378
|
-
def __init__(self):
|
|
379
|
-
super().__init__('FMU Manipulation Toolbox')
|
|
380
|
-
|
|
381
|
-
self.dropped_fmu = DropZoneWidget()
|
|
382
|
-
self.dropped_fmu.clicked.connect(self.update_fmu)
|
|
383
|
-
|
|
384
|
-
self.layout.addWidget(self.dropped_fmu, 0, 0, 4, 1)
|
|
385
|
-
|
|
386
|
-
self.fmu_title = QLabel()
|
|
387
|
-
self.fmu_title.setProperty("class", "title")
|
|
388
|
-
self.layout.addWidget(self.fmu_title, 0, 1, 1, 4)
|
|
389
|
-
|
|
390
|
-
self.container_window = None
|
|
391
|
-
#TODO: Container Window
|
|
392
|
-
#container_button = QPushButton("Make a container")
|
|
393
|
-
#container_button.setProperty("class", "quit")
|
|
394
|
-
#container_button.clicked.connect(self.launch_container)
|
|
395
|
-
#self.layout.addWidget(container_button, 4, 1, 1, 1)
|
|
396
|
-
|
|
397
|
-
help_widget = HelpWidget()
|
|
398
|
-
self.layout.addWidget(help_widget, 0, 5, 1, 1)
|
|
399
|
-
|
|
400
|
-
# Operations
|
|
401
|
-
self.help = Help()
|
|
402
|
-
operations_list = [
|
|
403
|
-
("Save port names", '-dump-csv', 'save', OperationSaveNamesToCSV, {"prompt_file": "write"}),
|
|
404
|
-
("Rename ports from CSV", '-rename-from-csv', 'modify', OperationRenameFromCSV, {"prompt_file": "read"}),
|
|
405
|
-
("Remove Toplevel", '-remove-toplevel', 'modify', OperationStripTopLevel),
|
|
406
|
-
("Remove Regexp", '-remove-regexp', 'removal', OperationRemoveRegexp, {"prompt": "regexp"}),
|
|
407
|
-
("Keep only Regexp", '-keep-only-regexp', 'removal', OperationKeepOnlyRegexp, {"prompt": "regexp"}),
|
|
408
|
-
("Save description.xml", '-extract-descriptor', 'save', None, {"func": self.save_descriptor}),
|
|
409
|
-
("Trim Until", '-trim-until', 'modify', OperationTrimUntil, {"prompt": "Prefix"}),
|
|
410
|
-
("Merge Toplevel", '-merge-toplevel', 'modify', OperationMergeTopLevel),
|
|
411
|
-
("Remove all", '-remove-all', 'removal', OperationRemoveRegexp, {"arg": ".*"}),
|
|
412
|
-
("Remove sources", '-remove-sources', 'removal', OperationRemoveSources),
|
|
413
|
-
("Add Win32 remoting", '-add-remoting-win32', 'info', OperationAddRemotingWin32),
|
|
414
|
-
("Add Win64 remoting", '-add-remoting-win64', 'info', OperationAddRemotingWin64),
|
|
415
|
-
("Add Win32 frontend", '-add-frontend-win32', 'info', OperationAddFrontendWin32),
|
|
416
|
-
("Add Win64 frontend", '-add-frontend-win64', 'info', OperationAddFrontendWin64),
|
|
417
|
-
("Check", '-check', 'info', get_checkers()),
|
|
418
|
-
]
|
|
419
|
-
|
|
420
|
-
width = 5
|
|
421
|
-
line = 1
|
|
422
|
-
for i, operation in enumerate(operations_list):
|
|
423
|
-
col = i % width + 1
|
|
424
|
-
line = int(i / width) + 1
|
|
425
|
-
|
|
426
|
-
if len(operation) < 5:
|
|
427
|
-
self.add_operation(operation[0], operation[1], operation[2], operation[3], line, col)
|
|
428
|
-
else:
|
|
429
|
-
self.add_operation(operation[0], operation[1], operation[2], operation[3], line, col, **operation[4])
|
|
430
|
-
|
|
431
|
-
line += 1
|
|
432
|
-
self.apply_filter_label = QLabel("Apply only on: ")
|
|
433
|
-
self.layout.addWidget(self.apply_filter_label, line, 2, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
|
|
434
|
-
self.set_tooltip(self.apply_filter_label, 'gui-apply-only')
|
|
435
|
-
|
|
436
|
-
causality = ["parameter", "calculatedParameter", "input", "output", "local", "independent"]
|
|
437
|
-
self.filter_list = FilterWidget(items=causality)
|
|
438
|
-
self.layout.addWidget(self.filter_list, line, 3, 1, 3)
|
|
439
|
-
self.filter_list.setProperty("class", "quit")
|
|
440
|
-
|
|
441
|
-
# Text
|
|
442
|
-
line += 1
|
|
443
|
-
self.log_widget = LogWidget()
|
|
444
|
-
self.layout.addWidget(self.log_widget, line, 0, 1, width + 1)
|
|
445
|
-
|
|
446
|
-
# buttons
|
|
447
|
-
line += 1
|
|
448
|
-
|
|
449
|
-
reload_button = QPushButton('Reload')
|
|
450
|
-
self.layout.addWidget(reload_button, 4, 0, 1, 1)
|
|
451
|
-
reload_button.clicked.connect(self.reload_fmu)
|
|
452
|
-
reload_button.setProperty("class", "quit")
|
|
453
|
-
|
|
454
|
-
exit_button = QPushButton('Exit')
|
|
455
|
-
self.layout.addWidget(exit_button, line, 0, 1, 2)
|
|
456
|
-
exit_button.clicked.connect(self.close)
|
|
457
|
-
exit_button.setProperty("class", "quit")
|
|
458
|
-
|
|
459
|
-
save_log_button = QPushButton('Save log as')
|
|
460
|
-
self.layout.addWidget(save_log_button, line, 2, 1, 2)
|
|
461
|
-
save_log_button.clicked.connect(self.save_log)
|
|
462
|
-
save_log_button.setProperty("class", "save")
|
|
463
|
-
|
|
464
|
-
save_fmu_button = QPushButton('Save modified FMU as')
|
|
465
|
-
self.layout.addWidget(save_fmu_button, line, 4, 1, 2)
|
|
466
|
-
save_fmu_button.clicked.connect(self.save_fmu)
|
|
467
|
-
save_fmu_button.setProperty("class", "save")
|
|
468
|
-
self.set_tooltip(save_fmu_button, '-output')
|
|
469
|
-
|
|
470
|
-
# show the window
|
|
471
|
-
self.show()
|
|
472
|
-
|
|
473
|
-
def closeEvent(self, event):
|
|
474
|
-
if self.container_window:
|
|
475
|
-
self.container_window.close()
|
|
476
|
-
self.container_window = None
|
|
477
|
-
event.accept()
|
|
478
|
-
|
|
479
|
-
def launch_container(self):
|
|
480
|
-
if not self.container_window:
|
|
481
|
-
self.container_window = ContainerWindow(self)
|
|
482
|
-
|
|
483
|
-
def closing_container(self):
|
|
484
|
-
self.container_window = None
|
|
485
|
-
|
|
486
|
-
def set_tooltip(self, widget, usage):
|
|
487
|
-
widget.setToolTip("\n".join(textwrap.wrap(self.help.usage(usage))))
|
|
488
|
-
|
|
489
|
-
def reload_fmu(self):
|
|
490
|
-
if self.dropped_fmu.fmu:
|
|
491
|
-
filename = self.dropped_fmu.fmu.fmu_filename
|
|
492
|
-
self.dropped_fmu.fmu = None
|
|
493
|
-
self.dropped_fmu.set_fmu(filename)
|
|
494
|
-
|
|
495
|
-
def save_descriptor(self):
|
|
496
|
-
if self.dropped_fmu.fmu:
|
|
497
|
-
fmu = self.dropped_fmu.fmu
|
|
498
|
-
filename, ok = QFileDialog.getSaveFileName(self, "Select a file",
|
|
499
|
-
os.path.dirname(fmu.fmu_filename),
|
|
500
|
-
"XML files (*.xml)")
|
|
501
|
-
if ok and filename:
|
|
502
|
-
fmu.save_descriptor(filename)
|
|
503
|
-
|
|
504
|
-
def save_fmu(self):
|
|
505
|
-
if self.dropped_fmu.fmu:
|
|
506
|
-
fmu = self.dropped_fmu.fmu
|
|
507
|
-
filename, ok = QFileDialog.getSaveFileName(self, "Select a file",
|
|
508
|
-
os.path.dirname(fmu.fmu_filename),
|
|
509
|
-
"FMU files (*.fmu)")
|
|
510
|
-
if ok and filename:
|
|
511
|
-
fmu.repack(filename)
|
|
512
|
-
logger.info(f"Modified version saved as {filename}.")
|
|
513
|
-
|
|
514
|
-
def save_log(self):
|
|
515
|
-
if self.dropped_fmu.fmu:
|
|
516
|
-
default_dir = os.path.dirname(self.dropped_fmu.fmu.fmu_filename)
|
|
517
|
-
else:
|
|
518
|
-
default_dir = None
|
|
519
|
-
filename, ok = QFileDialog.getSaveFileName(self, "Select a file",
|
|
520
|
-
default_dir,
|
|
521
|
-
"TXT files (*.txt)")
|
|
522
|
-
if ok and filename:
|
|
523
|
-
try:
|
|
524
|
-
with open(filename, "wt") as file:
|
|
525
|
-
file.write(str(self.log_widget.toPlainText()))
|
|
526
|
-
except Exception as e:
|
|
527
|
-
logger.error(f"{e}")
|
|
528
|
-
|
|
529
|
-
def add_operation(self, name, usage, severity, operation, x, y, prompt=None, prompt_file=None, arg=None,
|
|
530
|
-
func=None):
|
|
531
|
-
if prompt:
|
|
532
|
-
def operation_handler():
|
|
533
|
-
local_arg = self.prompt_string(prompt)
|
|
534
|
-
if local_arg:
|
|
535
|
-
self.apply_operation(operation(local_arg))
|
|
536
|
-
elif prompt_file:
|
|
537
|
-
def operation_handler():
|
|
538
|
-
local_arg = self.prompt_file(prompt_file)
|
|
539
|
-
if local_arg:
|
|
540
|
-
self.apply_operation(operation(local_arg))
|
|
541
|
-
elif arg:
|
|
542
|
-
def operation_handler():
|
|
543
|
-
self.apply_operation(operation(arg))
|
|
544
|
-
else:
|
|
545
|
-
def operation_handler():
|
|
546
|
-
# Checker can be a list of operations!
|
|
547
|
-
if isinstance(operation, list):
|
|
548
|
-
for op in operation:
|
|
549
|
-
self.apply_operation(op())
|
|
550
|
-
else:
|
|
551
|
-
self.apply_operation(operation())
|
|
552
|
-
|
|
553
|
-
button = QPushButton(name)
|
|
554
|
-
self.set_tooltip(button, usage)
|
|
555
|
-
button.setProperty("class", severity)
|
|
556
|
-
if func:
|
|
557
|
-
button.clicked.connect(func)
|
|
558
|
-
else:
|
|
559
|
-
button.clicked.connect(operation_handler)
|
|
560
|
-
self.layout.addWidget(button, x, y)
|
|
561
|
-
|
|
562
|
-
def prompt_string(self, message):
|
|
563
|
-
text, ok = QInputDialog().getText(self, "Enter value", f"{message}:", QLineEdit.EchoMode.Normal, "")
|
|
564
|
-
|
|
565
|
-
if ok and text:
|
|
566
|
-
return text
|
|
567
|
-
else:
|
|
568
|
-
return None
|
|
569
|
-
|
|
570
|
-
def prompt_file(self, access):
|
|
571
|
-
if self.dropped_fmu.fmu:
|
|
572
|
-
default_dir = os.path.dirname(self.dropped_fmu.fmu.fmu_filename)
|
|
573
|
-
|
|
574
|
-
if access == 'read':
|
|
575
|
-
filename, ok = QFileDialog.getOpenFileName(self, "Select a file",
|
|
576
|
-
default_dir, "CSV files (*.csv)")
|
|
577
|
-
else:
|
|
578
|
-
filename, ok = QFileDialog.getSaveFileName(self, "Select a file",
|
|
579
|
-
default_dir, "CSV files (*.csv)")
|
|
580
|
-
|
|
581
|
-
if ok and filename:
|
|
582
|
-
return filename
|
|
583
|
-
return None
|
|
584
|
-
|
|
585
|
-
def update_fmu(self):
|
|
586
|
-
if self.dropped_fmu.fmu:
|
|
587
|
-
self.fmu_title.setText(os.path.basename(self.dropped_fmu.fmu.fmu_filename))
|
|
588
|
-
self.log_widget.clear()
|
|
589
|
-
self.apply_operation(OperationSummary())
|
|
590
|
-
else:
|
|
591
|
-
self.fmu_title.setText('')
|
|
592
|
-
|
|
593
|
-
def apply_operation(self, operation):
|
|
594
|
-
if self.dropped_fmu.fmu:
|
|
595
|
-
self.log_widget.moveCursor(QTextCursor.MoveOperation.End)
|
|
596
|
-
fmu_filename = os.path.basename(self.dropped_fmu.fmu.fmu_filename)
|
|
597
|
-
logger.info('-' * 100)
|
|
598
|
-
self.log_widget.insertHtml(f"<strong>{fmu_filename}: {operation}</strong><br>")
|
|
599
|
-
|
|
600
|
-
apply_on = self.filter_list.get()
|
|
601
|
-
if apply_on:
|
|
602
|
-
self.log_widget.insertHtml(f"<i>Applied only for ports with causality = " +
|
|
603
|
-
", ".join(apply_on) + "</i><br>")
|
|
604
|
-
logger.info('-' * 100)
|
|
605
|
-
try:
|
|
606
|
-
self.dropped_fmu.fmu.apply_operation(operation, apply_on=apply_on)
|
|
607
|
-
except Exception as e:
|
|
608
|
-
logger.error(f"{e}")
|
|
609
|
-
|
|
610
|
-
scroll_bar = self.log_widget.verticalScrollBar()
|
|
611
|
-
scroll_bar.setValue(scroll_bar.maximum())
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
class ContainerWindow(WindowWithLayout):
|
|
615
|
-
def __init__(self, parent: MainWindow):
|
|
616
|
-
super().__init__('FMU Manipulation Toolbox - Container')
|
|
617
|
-
self.main_window = parent
|
|
618
|
-
self.last_directory = None
|
|
619
|
-
|
|
620
|
-
# ROW 0
|
|
621
|
-
load_button = QPushButton("Load Description")
|
|
622
|
-
load_button.clicked.connect(self.load_container)
|
|
623
|
-
load_button.setProperty("class", "quit")
|
|
624
|
-
self.layout.addWidget(load_button, 0, 0)
|
|
625
|
-
|
|
626
|
-
self.container_label = QLabel()
|
|
627
|
-
self.container_label.setProperty("class", "title")
|
|
628
|
-
self.container_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
629
|
-
self.layout.addWidget(self.container_label, 0, 1, 1, 2)
|
|
630
|
-
|
|
631
|
-
# ROW 1
|
|
632
|
-
add_fmu_button = QPushButton("Add FMU")
|
|
633
|
-
add_fmu_button.setProperty("class", "modify")
|
|
634
|
-
add_fmu_button.setDisabled(True)
|
|
635
|
-
self.layout.addWidget(add_fmu_button, 1, 1)
|
|
636
|
-
|
|
637
|
-
add_sub_button = QPushButton("Add SubContainer")
|
|
638
|
-
add_sub_button.setProperty("class", "modify")
|
|
639
|
-
add_sub_button.setDisabled(True)
|
|
640
|
-
self.layout.addWidget(add_sub_button, 1, 2)
|
|
641
|
-
|
|
642
|
-
self.assembly_tree = AssemblyTreeWidget(parent=self)
|
|
643
|
-
self.assembly_tree.setMinimumHeight(600)
|
|
644
|
-
self.assembly_tree.setMinimumWidth(200)
|
|
645
|
-
self.layout.addWidget(self.assembly_tree, 1, 0, 3, 1)
|
|
646
|
-
|
|
647
|
-
# ROW 2
|
|
648
|
-
del_fmu_button = QPushButton("Remove FMU")
|
|
649
|
-
del_fmu_button.setProperty("class", "removal")
|
|
650
|
-
del_fmu_button.setDisabled(True)
|
|
651
|
-
self.layout.addWidget(del_fmu_button, 2, 1)
|
|
652
|
-
|
|
653
|
-
del_sub_button = QPushButton("Remove SubContainer")
|
|
654
|
-
del_sub_button.setProperty("class", "removal")
|
|
655
|
-
del_sub_button.setDisabled(True)
|
|
656
|
-
self.layout.addWidget(del_sub_button, 2, 2)
|
|
657
|
-
|
|
658
|
-
# ROW 3
|
|
659
|
-
self.assembly_tab = AssemblyTabWidget(parent=self)
|
|
660
|
-
self.assembly_tab.setMinimumWidth(600)
|
|
661
|
-
self.layout.addWidget(self.assembly_tab, 3, 1, 1, 2)
|
|
662
|
-
|
|
663
|
-
# ROW 4
|
|
664
|
-
close_button = QPushButton("Close")
|
|
665
|
-
close_button.setProperty("class", "quit")
|
|
666
|
-
close_button.clicked.connect(self.close)
|
|
667
|
-
self.layout.addWidget(close_button, 4, 0)
|
|
668
|
-
|
|
669
|
-
save_button = QPushButton("Save Container")
|
|
670
|
-
save_button.setProperty("class", "save")
|
|
671
|
-
self.layout.addWidget(save_button, 4, 2)
|
|
672
|
-
|
|
673
|
-
self.assembly_tree.selectionModel().currentChanged.connect(self.item_selected)
|
|
674
|
-
topIndex = self.assembly_tree.model.index(0, 0, self.assembly_tree.rootIndex())
|
|
675
|
-
self.assembly_tree.setCurrentIndex(topIndex)
|
|
676
|
-
|
|
677
|
-
self.show()
|
|
678
|
-
|
|
679
|
-
def closeEvent(self, event):
|
|
680
|
-
if self.main_window:
|
|
681
|
-
self.main_window.closing_container()
|
|
682
|
-
event.accept()
|
|
683
|
-
|
|
684
|
-
def item_selected(self, current: QModelIndex, previous: QModelIndex):
|
|
685
|
-
if current.isValid():
|
|
686
|
-
node = current.data(role=Qt.ItemDataRole.UserRole + 1)
|
|
687
|
-
node_type = current.data(role=Qt.ItemDataRole.UserRole + 2)
|
|
688
|
-
self.container_label.setText(f"{node.name} ({node_type})")
|
|
689
|
-
else:
|
|
690
|
-
self.container_label.setText("")
|
|
691
|
-
|
|
692
|
-
def load_container(self):
|
|
693
|
-
if self.last_directory:
|
|
694
|
-
default_directory = self.last_directory
|
|
695
|
-
else:
|
|
696
|
-
default_directory = os.path.expanduser('~')
|
|
697
|
-
|
|
698
|
-
filename, _ = QFileDialog.getOpenFileName(parent=self, caption='Select FMU to Manipulate',
|
|
699
|
-
dir=default_directory,
|
|
700
|
-
filter="JSON files (*.json);;SSP files (*.ssp)")
|
|
701
|
-
if filename:
|
|
702
|
-
try:
|
|
703
|
-
self.last_directory = os.path.dirname(filename)
|
|
704
|
-
self.assembly_tree.load_container(filename)
|
|
705
|
-
except Exception as e:
|
|
706
|
-
logger.error(e)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
class Application(QApplication):
|
|
710
|
-
"""
|
|
711
|
-
Analyse and modify your FMUs.
|
|
712
|
-
|
|
713
|
-
Note: modifying the modelDescription.xml can damage your FMU !
|
|
714
|
-
Communicating with the FMU-developer and adapting the way the FMU is generated, is preferable when possible.
|
|
715
|
-
|
|
716
|
-
"""
|
|
717
|
-
def __init__(self, *args, **kwargs):
|
|
718
|
-
self.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.RoundPreferFloor)
|
|
719
|
-
super().__init__(*args, **kwargs)
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
QDir.addSearchPath('images', os.path.join(os.path.dirname(__file__), "resources"))
|
|
723
|
-
self.setStyleSheet(gui_style)
|
|
724
|
-
|
|
725
|
-
if os.name == 'nt':
|
|
726
|
-
import ctypes
|
|
727
|
-
self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon-round.png')))
|
|
728
|
-
|
|
729
|
-
# https://stackoverflow.com/questions/1551605/how-to-set-applications-taskbar-icon-in-windows-7/1552105#1552105
|
|
730
|
-
|
|
731
|
-
application_id = 'FMU_Manipulation_Toolbox' # arbitrary string
|
|
732
|
-
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(application_id)
|
|
733
|
-
else:
|
|
734
|
-
self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon.png')))
|
|
735
|
-
|
|
736
|
-
self.window = MainWindow()
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
def main():
|
|
740
|
-
application = Application(sys.argv)
|
|
741
|
-
|
|
742
|
-
logger.info(" " * 80 + f"Version {version}")
|
|
743
|
-
logger.info(application.__doc__)
|
|
744
|
-
|
|
745
|
-
sys.exit(application.exec())
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if __name__ == "__main__":
|
|
749
|
-
main()
|