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.
Files changed (32) hide show
  1. fmu_manipulation_toolbox/__main__.py +2 -2
  2. fmu_manipulation_toolbox/__version__.py +1 -1
  3. fmu_manipulation_toolbox/assembly.py +12 -8
  4. fmu_manipulation_toolbox/checker.py +4 -2
  5. fmu_manipulation_toolbox/cli/fmucontainer.py +27 -19
  6. fmu_manipulation_toolbox/cli/fmusplit.py +5 -2
  7. fmu_manipulation_toolbox/cli/fmutool.py +8 -3
  8. fmu_manipulation_toolbox/cli/utils.py +11 -1
  9. fmu_manipulation_toolbox/container.py +292 -82
  10. fmu_manipulation_toolbox/ls.py +35 -0
  11. fmu_manipulation_toolbox/operations.py +3 -2
  12. fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
  13. fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
  14. fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
  15. fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
  16. fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
  17. fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
  18. fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
  19. fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
  20. fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
  21. fmu_manipulation_toolbox/split.py +59 -3
  22. fmu_manipulation_toolbox/terminals.py +137 -0
  23. fmu_manipulation_toolbox/version.py +1 -1
  24. fmu_manipulation_toolbox-1.9.2b2.dist-info/METADATA +42 -0
  25. {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/RECORD +29 -29
  26. {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/entry_points.txt +1 -1
  27. {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/licenses/LICENSE.txt +1 -1
  28. fmu_manipulation_toolbox/gui.py +0 -749
  29. fmu_manipulation_toolbox/gui_style.py +0 -252
  30. fmu_manipulation_toolbox-1.9.1.3.dist-info/METADATA +0 -30
  31. {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/WHEEL +0 -0
  32. {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b2.dist-info}/top_level.txt +0 -0
@@ -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()