fmu-manipulation-toolbox 1.9.2b4__py3-none-any.whl → 1.9.2rc2__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 (21) hide show
  1. fmu_manipulation_toolbox/__version__.py +1 -1
  2. fmu_manipulation_toolbox/cli/datalog2pcap.py +34 -4
  3. fmu_manipulation_toolbox/container.py +56 -17
  4. fmu_manipulation_toolbox/gui/__init__.py +0 -0
  5. fmu_manipulation_toolbox/gui/gui.py +749 -0
  6. fmu_manipulation_toolbox/gui/gui_style.py +252 -0
  7. fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
  8. fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
  9. fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
  10. fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
  11. fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
  12. fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
  13. fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
  14. fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
  15. fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
  16. {fmu_manipulation_toolbox-1.9.2b4.dist-info → fmu_manipulation_toolbox-1.9.2rc2.dist-info}/METADATA +1 -1
  17. {fmu_manipulation_toolbox-1.9.2b4.dist-info → fmu_manipulation_toolbox-1.9.2rc2.dist-info}/RECORD +21 -18
  18. {fmu_manipulation_toolbox-1.9.2b4.dist-info → fmu_manipulation_toolbox-1.9.2rc2.dist-info}/WHEEL +1 -1
  19. {fmu_manipulation_toolbox-1.9.2b4.dist-info → fmu_manipulation_toolbox-1.9.2rc2.dist-info}/entry_points.txt +0 -0
  20. {fmu_manipulation_toolbox-1.9.2b4.dist-info → fmu_manipulation_toolbox-1.9.2rc2.dist-info}/licenses/LICENSE.txt +0 -0
  21. {fmu_manipulation_toolbox-1.9.2b4.dist-info → fmu_manipulation_toolbox-1.9.2rc2.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- 'V1.9.2b4'
1
+ 'V1.9.2rc2'
@@ -15,6 +15,20 @@ class DatalogConverter:
15
15
  def __init__(self, cvs_filename: Union[Path, str]):
16
16
  self.csv_filename = Path(cvs_filename)
17
17
  self.pcap_filename = self.csv_filename.with_suffix(".pcap")
18
+ self.opcode_name = {
19
+ "CAN": {
20
+ 0x0001: "FMI3_LS_BUS_OP_FORMAT_ERROR",
21
+ 0x0010: "FMI3_LS_BUS_CAN_OP_CAN_TRANSMIT",
22
+ 0x0011: "FMI3_LS_BUS_CAN_OP_CANFD_TRANSMIT",
23
+ 0x0012: "FMI3_LS_BUS_CAN_OP_CANXL_TRANSMIT",
24
+ 0x0020: "FMI3_LS_BUS_CAN_OP_CONFIRM",
25
+ 0x0030: "FMI3_LS_BUS_CAN_OP_ARBITRATION_LOST",
26
+ 0x0031: "FMI3_LS_BUS_CAN_OP_BUS_ERROR",
27
+ 0x0040: "FMI3_LS_BUS_CAN_OP_CONFIGURATION",
28
+ 0x0041: "FMI3_LS_BUS_CAN_OP_STATUS",
29
+ 0x0042: "FMI3_LS_BUS_CAN_OP_WAKEUP"
30
+ }
31
+ }
18
32
 
19
33
  def open_pcap(self):
20
34
  logger.info(f"Creating PCAP file '{self.pcap_filename}'...")
@@ -31,7 +45,10 @@ class DatalogConverter:
31
45
  # captured from each packet.
32
46
 
33
47
  file.write(int(227).to_bytes(4, byteorder="big")) # link type. his field is defined in the Section
34
- # # 8.1 IANA registry.
48
+ # 8.1 IANA registry.
49
+ # 190: Reserved for Controller Area Network (CAN) v. 2.0B packets
50
+ # 210: Reserved for FlexRay automotive bus
51
+ # 227: CAN (Controller Area Network) frames, with a pseudo-header followed by the frame payload.
35
52
  return file
36
53
 
37
54
  def open_csv(self):
@@ -42,15 +59,21 @@ class DatalogConverter:
42
59
  def decode_hexstring(self, hex_string: bytes, time_s, time_us):
43
60
  opcode = int.from_bytes(hex_string[0:4], byteorder="little")
44
61
  length = int.from_bytes(hex_string[4:8], byteorder="little")
45
- can_id = int.from_bytes(hex_string[8:12], byteorder="little")
62
+
46
63
  if opcode == 0x10: # TRANSMIT
64
+ can_id = int.from_bytes(hex_string[8:12], byteorder="little")
47
65
  rtr = int.from_bytes(hex_string[13:14], byteorder="little")
48
66
  ide = int.from_bytes(hex_string[12:13], byteorder="little")
49
67
  data_length = int.from_bytes(hex_string[14:16], byteorder="little")
50
68
  raw_data = hex_string[16:]
51
69
 
70
+ raw_str = ""
71
+ for i, b in enumerate(raw_data):
72
+ raw_str += f"{i}:{b:d} "
73
+ logger.info(f"time={time_s}.{time_us:06d} data length {data_length} with data {raw_str}")
74
+
52
75
  logger.debug(f"time={time_s}.{time_us:06d} OP=0x{opcode:04X} len={length} {data_length} id={can_id}"
53
- f" ide={ide} rtr={rtr} len={data_length} {raw_data}")
76
+ f" ide={ide} rtr={rtr} len={data_length} raw_len={len(raw_data)} {raw_str}")
54
77
 
55
78
  # TimeStamp
56
79
  self.pcapfile.write(time_s.to_bytes(4, byteorder="big"))
@@ -76,7 +99,14 @@ class DatalogConverter:
76
99
  self.pcapfile.write(dlc.to_bytes(1, byteorder="big"))
77
100
 
78
101
  # PAYLOAD
79
- self.pcapfile.write(raw_data)
102
+ self.pcapfile.write(raw_data[0:data_length])
103
+
104
+ elif opcode == 0x40:
105
+ parameter = int.from_bytes(hex_string[8:9], byteorder="little")
106
+ logger.debug(f"Config parameter: {parameter}")
107
+ if (parameter == 1):
108
+ baudrate = int.from_bytes(hex_string[9:13], byteorder="little")
109
+ logger.debug(f"baudrate parameter: {baudrate}")
80
110
 
81
111
  def convert(self):
82
112
  with self.open_csv() as self.csvfile, self.open_pcap() as self.pcapfile:
@@ -196,6 +196,7 @@ class EmbeddedFMU(OperationAbstract):
196
196
  self.model_identifier = None
197
197
  self.guid = None
198
198
  self.fmi_version = None
199
+ self.platforms = set()
199
200
  self.ports: Dict[str, EmbeddedFMUPort] = {}
200
201
 
201
202
  self.has_event_mode = False
@@ -240,6 +241,22 @@ class EmbeddedFMU(OperationAbstract):
240
241
  port = EmbeddedFMUPort(fmu_port.fmi_type, fmu_port, fmi_version=self.fmi_version)
241
242
  self.ports[port.name] = port
242
243
 
244
+ def closure(self):
245
+ osname = {
246
+ "win64": "Windows",
247
+ "linux64": "Linux",
248
+ "darwin64": "Darwin",
249
+ "x86_64-windows": "Windows",
250
+ "x86_64-linux": "Linux",
251
+ "aarch64-darwin": "Darwin"
252
+ }
253
+ try:
254
+ for directory in (Path(self.fmu.tmp_directory) / "binaries").iterdir():
255
+ if directory.is_dir() and str(directory.stem) in osname:
256
+ self.platforms.add(osname[str(directory.stem)])
257
+ except FileNotFoundError:
258
+ pass # no binaries
259
+
243
260
  def __repr__(self):
244
261
  properties = f"{len(self.ports)} variables, ts={self.step_size}s"
245
262
  if len(self.terminals) > 0:
@@ -602,9 +619,13 @@ class FMUContainer:
602
619
  canHandleVariableCommunicationStepSize="true"
603
620
  canBeInstantiatedOnlyOncePerProcess="{only_once}"
604
621
  canNotUseMemoryManagementFunctions="true"
605
- canGetAndSetFMUstate="false"
606
- canSerializeFMUstate="false"
607
- providesDirectionalDerivative="false"
622
+ canGetAndSetFMUState="false"
623
+ canSerializeFMUState="false"
624
+ providesDirectionalDerivatives="false"
625
+ providesAdjointDerivatives="false"
626
+ providesPerElementDependencies="false"
627
+ providesEvaluateDiscreteStates="false"
628
+ hasEventMode="true"
608
629
  needsExecutionTool="{execution_tool}">
609
630
  </CoSimulation>
610
631
 
@@ -1214,7 +1235,20 @@ class FMUContainer:
1214
1235
  logger.debug(f"Copying {origin} in {destination}")
1215
1236
  shutil.copy(origin, destination)
1216
1237
 
1217
- def get_bindir_and_suffixe(self) -> Tuple[str, str, str]:
1238
+ def get_bindir_and_suffixe(self) -> Generator[Tuple[str, str, str], Any, None]:
1239
+ fmu_iter = iter(self.involved_fmu.values())
1240
+ try:
1241
+ fmu = next(fmu_iter)
1242
+ except StopIteration:
1243
+ raise FMUContainerError("No fmu declared in this container.")
1244
+
1245
+ os_list = fmu.platforms
1246
+ logger.debug(f"FMU '{fmu.name}' OS support: {', '.join(fmu.platforms)}.")
1247
+
1248
+ for fmu in fmu_iter:
1249
+ logger.debug(f"FMU '{fmu.name}' OS support: {', '.join(fmu.platforms)}.")
1250
+ os_list &= fmu.platforms
1251
+
1218
1252
  suffixes = {
1219
1253
  "Windows": "dll",
1220
1254
  "Linux": "so",
@@ -1236,11 +1270,16 @@ class FMUContainer:
1236
1270
  else:
1237
1271
  target_bindirs = origin_bindirs
1238
1272
 
1239
- os_name = platform.system()
1240
- try:
1241
- return origin_bindirs[os_name], suffixes[os_name], target_bindirs[os_name]
1242
- except KeyError:
1243
- raise FMUContainerError(f"OS '{os_name}' is not supported.")
1273
+ if os_list:
1274
+ logger.info(f"Container will be built for {', '.join(os_list)}.")
1275
+ else:
1276
+ logger.critical("No common OS found for embedded FMU. Try to re-run with '-debug'. Container won't be runnable.")
1277
+
1278
+ for os_name in os_list:
1279
+ try:
1280
+ yield origin_bindirs[os_name], suffixes[os_name], target_bindirs[os_name]
1281
+ except KeyError:
1282
+ raise FMUContainerError(f"OS '{os_name}' is not supported.")
1244
1283
 
1245
1284
  def make_fmu_skeleton(self, base_directory: Path) -> Path:
1246
1285
  logger.debug(f"Initialize directory '{base_directory}'")
@@ -1260,14 +1299,14 @@ class FMUContainer:
1260
1299
 
1261
1300
  self.copyfile(origin / "model.png", base_directory)
1262
1301
 
1263
- origin_bindir, suffixe, target_bindir = self.get_bindir_and_suffixe()
1264
-
1265
- library_filename = origin / origin_bindir / f"container.{suffixe}"
1266
- if not library_filename.is_file():
1267
- raise FMUContainerError(f"File {library_filename} not found")
1268
- binary_directory = binaries_directory / target_bindir
1269
- binary_directory.mkdir(exist_ok=True)
1270
- self.copyfile(library_filename, binary_directory / f"{self.identifier}.{suffixe}")
1302
+ for origin_bindir, suffixe, target_bindir in self.get_bindir_and_suffixe():
1303
+ library_filename = origin / origin_bindir / f"container.{suffixe}"
1304
+ if library_filename.is_file():
1305
+ binary_directory = binaries_directory / target_bindir
1306
+ binary_directory.mkdir(exist_ok=True)
1307
+ self.copyfile(library_filename, binary_directory / f"{self.identifier}.{suffixe}")
1308
+ else:
1309
+ logger.critical(f"File {library_filename} not found.")
1271
1310
 
1272
1311
  for i, fmu in enumerate(self.involved_fmu.values()):
1273
1312
  shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
File without changes
@@ -0,0 +1,749 @@
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 fmu_manipulation_toolbox.gui.gui_style import gui_style, log_color
15
+ from fmu_manipulation_toolbox.operations import *
16
+ from fmu_manipulation_toolbox.remoting import (OperationAddRemotingWin32, OperationAddRemotingWin64, OperationAddFrontendWin32,
17
+ OperationAddFrontendWin64)
18
+ from fmu_manipulation_toolbox.assembly import Assembly, AssemblyNode
19
+ from fmu_manipulation_toolbox.checker import get_checkers
20
+ from fmu_manipulation_toolbox.help import Help
21
+ from fmu_manipulation_toolbox.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()
@@ -0,0 +1,252 @@
1
+ import os
2
+
3
+ if os.name == 'nt':
4
+ gui_style = """
5
+ QWidget {
6
+ font: 10pt "Verdana";
7
+ background: #4b4e51;
8
+ color: #b5bab9;
9
+ }
10
+ QPushButton, QComboBox {
11
+ min-height: 30px;
12
+ padding: 1px 1px 0.2em 0.2em;
13
+ border: 1px solid #282830;
14
+ border-radius: 5px;
15
+ color: #dddddd;
16
+ }
17
+ QPushButton:pressed {
18
+ border: 2px solid #282830;
19
+ }
20
+ QPushButton.info {
21
+ background-color: #4e6749;
22
+ }
23
+ QPushButton.info:hover {
24
+ background-color: #5f7850;
25
+ }
26
+ QPushButton.modify {
27
+ background-color: #98763f;
28
+ }
29
+ QPushButton.modify:hover {
30
+ background-color: #a9874f;
31
+ }
32
+ QPushButton.removal {
33
+ background-color: #692e2e;
34
+ }
35
+ QPushButton.removal:hover {
36
+ background-color: #7a3f3f;
37
+ }
38
+ QPushButton.save {
39
+ background-color: #564967;
40
+ }
41
+ QPushButton.save:hover {
42
+ background-color: #675a78;
43
+ }
44
+ QPushButton.quit {
45
+ background-color: #4571a4;
46
+ }
47
+ QPushButton.quit:hover {
48
+ background-color: #5682b5;
49
+ }
50
+ QPushButton::disabled {
51
+ background-color: gray;
52
+ }
53
+ QToolTip {
54
+ color: black
55
+ }
56
+ QLabel.dropped_fmu {
57
+ background-color: #b5bab9
58
+ }
59
+ QLabel.title {
60
+ font: 14pt bold "Verdana";
61
+ }
62
+ QLabel.dropped_fmu:hover {
63
+ background-color: #c6cbca
64
+ }
65
+ QTextBrowser, QTreeView {
66
+ font: 11pt "Consolas";
67
+ background-color: #282830;
68
+ color: #b5bab9;
69
+ }
70
+ QMenu::item {
71
+ padding: 2px 250px 2px 20px;
72
+ border: 1px solid transparent;
73
+ }
74
+ QMenu::item::indicator, QCheckBox::item::indicator {
75
+ width: 32px;
76
+ height: 32px;
77
+ }
78
+ QMenu::indicator:checked, QCheckBox::indicator:checked {
79
+ image: url(images:checkbox-checked.png);
80
+ }
81
+ QMenu::indicator:checked:hover, QCheckBox::indicator:checked:hover {
82
+ image: url(images:checkbox-checked-hover.png);
83
+ }
84
+ QMenu::indicator:checked:disabled, QCheckBox::indicator:checked:disabled {
85
+ image: url(images:checkbox-checked-disabled.png);
86
+ }
87
+ QMenu::indicator:unchecked, QCheckBox::indicator:unchecked {
88
+ image: url(images:checkbox-unchecked.png);
89
+ }
90
+ QMenu::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover {
91
+ image: url(images:checkbox-unchecked-hover.png);
92
+ }
93
+ QMenu::indicator:unchecked:disabled, QCheckBox::indicator:unchecked:disabled {
94
+ image: url(images:checkbox-unchecked-disabled.png);
95
+ }
96
+ QCheckBox::item {
97
+ padding: 2px 250px 2px 20px;
98
+ border: 1px solid transparent;
99
+ }
100
+ QTabBar::tab {
101
+ min-height: 30px;
102
+ padding: 1px 1px 0.2em 0.2em;
103
+ color: #dddddd;
104
+ margin: 2px;
105
+ margin-bottom: 0px;
106
+ border: 1px solid #282830;
107
+ border-top-left-radius: 5px;
108
+ border-top-right-radius: 5px;
109
+ }
110
+ QTabBar::tab:selected, QTabBar::tab:hover {
111
+ background-color: #5f7850;
112
+ margin-bottom:-1px;
113
+ }
114
+ QTabBar {
115
+ border-bottom: 1px solid #282830;
116
+ }
117
+ QTabBar::tab:top:last, QTabBar::tab:bottom:last {
118
+ margin-right: 0;
119
+ }
120
+ QTabBar::tab:top:first, QTabBar::tab:bottom:first {
121
+ margin-left: 0;
122
+ }
123
+ """
124
+ else:
125
+ gui_style = """
126
+ QWidget {
127
+ font: 12pt;
128
+ background: #4b4e51;
129
+ color: #b5bab9;
130
+ }
131
+ QPushButton, QComboBox {
132
+ min-height: 30px;
133
+ padding: 1px 1px 0.2em 0.2em;
134
+ border: 1px solid #282830;
135
+ border-radius: 5px;
136
+ color: #dddddd;
137
+ }
138
+ QPushButton:pressed {
139
+ border: 2px solid #282830;
140
+ }
141
+ QPushButton.info {
142
+ background-color: #4e6749;
143
+ }
144
+ QPushButton.info:hover {
145
+ background-color: #5f7850;
146
+ }
147
+ QPushButton.modify {
148
+ background-color: #98763f;
149
+ }
150
+ QPushButton.modify:hover {
151
+ background-color: #a9874f;
152
+ }
153
+ QPushButton.removal {
154
+ background-color: #692e2e;
155
+ }
156
+ QPushButton.removal:hover {
157
+ background-color: #7a3f3f;
158
+ }
159
+ QPushButton.save {
160
+ background-color: #564967;
161
+ }
162
+ QPushButton.save:hover {
163
+ background-color: #675a78;
164
+ }
165
+ QPushButton.quit {
166
+ background-color: #4571a4;
167
+ }
168
+ QPushButton.quit:hover {
169
+ background-color: #5682b5;
170
+ }
171
+ QPushButton::disabled {
172
+ background-color: gray;
173
+ }
174
+ QToolTip {
175
+ color: black
176
+ }
177
+ QLabel.dropped_fmu {
178
+ background-color: #b5bab9
179
+ }
180
+ QLabel.title {
181
+ font: 14pt bold "Verdana";
182
+ }
183
+ QLabel.dropped_fmu:hover {
184
+ background-color: #c6cbca
185
+ }
186
+ QTextBrowser, QTreeView {
187
+ font: 14pt "Courier New";
188
+ background-color: #282830;
189
+ color: #b5bab9;
190
+ }
191
+ QMenu::item {
192
+ padding: 2px 250px 2px 20px;
193
+ border: 1px solid transparent;
194
+ }
195
+ QMenu::item::indicator, QCheckBox::item::indicator {
196
+ width: 32px;
197
+ height: 32px;
198
+ }
199
+ QMenu::indicator:checked, QCheckBox::indicator:checked {
200
+ image: url(images:checkbox-checked.png);
201
+ }
202
+ QMenu::indicator:checked:hover, QCheckBox::indicator:checked:hover {
203
+ image: url(images:checkbox-checked-hover.png);
204
+ }
205
+ QMenu::indicator:checked:disabled, QCheckBox::indicator:checked:disabled {
206
+ image: url(images:checkbox-checked-disabled.png);
207
+ }
208
+ QMenu::indicator:unchecked, QCheckBox::indicator:unchecked {
209
+ image: url(images:checkbox-unchecked.png);
210
+ }
211
+ QMenu::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover {
212
+ image: url(images:checkbox-unchecked-hover.png);
213
+ }
214
+ QMenu::indicator:unchecked:disabled, QCheckBox::indicator:unchecked:disabled {
215
+ image: url(images:checkbox-unchecked-disabled.png);
216
+ }
217
+ QCheckBox::item {
218
+ padding: 2px 250px 2px 20px;
219
+ border: 1px solid transparent;
220
+ }
221
+ QTabBar::tab {
222
+ min-height: 30px;
223
+ padding: 1px 1px 0.2em 0.2em;
224
+ color: #dddddd;
225
+ margin: 2px;
226
+ margin-bottom: 0px;
227
+ border: 1px solid #282830;
228
+ border-top-left-radius: 5px;
229
+ border-top-right-radius: 5px;
230
+ }
231
+ QTabBar::tab:selected, QTabBar::tab:hover {
232
+ background-color: #5f7850;
233
+ margin-bottom:-1px;
234
+ }
235
+ QTabBar {
236
+ border-bottom: 1px solid #282830;
237
+ }
238
+ QTabBar::tab:top:last, QTabBar::tab:bottom:last {
239
+ margin-right: 0;
240
+ }
241
+ QTabBar::tab:top:first, QTabBar::tab:bottom:first {
242
+ margin-left: 0;
243
+ }
244
+ """
245
+
246
+ log_color = {
247
+ "DEBUG": "#6E6B6B",
248
+ "INFO": "#b5bab9",
249
+ "WARNING": "#F7C61B",
250
+ "ERROR": "#F54927",
251
+ "CRITICAL": "#FF00FF",
252
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmu_manipulation_toolbox
3
- Version: 1.9.2b4
3
+ Version: 1.9.2rc2
4
4
  Summary: FMU Manipulation Toolbox is a python package which helps to analyze, modify or combine Functional Mock-up Units (FMUs) without recompilation.
5
5
  Home-page: https://github.com/grouperenault/fmu_manipulation_toolbox/
6
6
  Author: Nicolas.LAURENT@Renault.com
@@ -1,9 +1,9 @@
1
1
  fmu_manipulation_toolbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  fmu_manipulation_toolbox/__main__.py,sha256=g0ZhVsMiAs5KnhyVNwTe01N2PQjAg7F9YCnXHZB-HwA,356
3
- fmu_manipulation_toolbox/__version__.py,sha256=Gbo0eZjyM2y_SKH83AQYtGZD5IHxJiZo3K7RDSiIIyA,11
3
+ fmu_manipulation_toolbox/__version__.py,sha256=6ttw5N6nrGXzLg1BgjLnWFMx2u8lpo92IH6dsBWPlJw,12
4
4
  fmu_manipulation_toolbox/assembly.py,sha256=QmFihza_I0hK0Ia6tWbjdZeoREhveLrv3d7ERrBUaDk,27217
5
5
  fmu_manipulation_toolbox/checker.py,sha256=Dh47b3blibCWjHCeZ61Y_w9Ug0PWvEuSiemZtp-llQA,3141
6
- fmu_manipulation_toolbox/container.py,sha256=b_F6jjgnn9aWm54265KTennjslcj9YUFChUkQB1w9Kk,57017
6
+ fmu_manipulation_toolbox/container.py,sha256=yME00RUudrRIzc6ZSguBFzyd9hkp6eFIdp91QWaOcVo,58561
7
7
  fmu_manipulation_toolbox/help.py,sha256=j8xmnCrwQpaW-SZ8hSqA1dlTXgaqzQWc4Yr3RH_oqck,6012
8
8
  fmu_manipulation_toolbox/ls.py,sha256=wmyoKrvDLXpL-PFz6cUhLLqxDMD5E9L_P4KswWpQHsk,975
9
9
  fmu_manipulation_toolbox/operations.py,sha256=y3QDMiLaG1TUhIQ4r_onWp1kGm8QZaFfTOdQr0oC7_A,21177
@@ -12,11 +12,14 @@ fmu_manipulation_toolbox/split.py,sha256=6D99SAGNu4B3PSaSsliWc6Bb5aSBZMxL8t0v8TJ
12
12
  fmu_manipulation_toolbox/terminals.py,sha256=mGGS4tdE6cJuz-2zvwc7drpmT0QJ7YPe8ENw2UGlEHA,5062
13
13
  fmu_manipulation_toolbox/version.py,sha256=L26Cc3PH97SOa4G9yiYnafrdolK0G_DCQZZTvv3YXqI,392
14
14
  fmu_manipulation_toolbox/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- fmu_manipulation_toolbox/cli/datalog2pcap.py,sha256=A0KNHVTzEvzKaT5HG0zgHVRC5G1bXoIbfLRXuvOsE-U,6080
15
+ fmu_manipulation_toolbox/cli/datalog2pcap.py,sha256=gdqScDLNu9jxl_bAld7Qf9GEEs8XePcIpkVJ4p87hlM,7689
16
16
  fmu_manipulation_toolbox/cli/fmucontainer.py,sha256=ZLoC8QMVBVG2S3cdNthMIQ0gYIU9WUbYj8Aj2vxzyfs,5338
17
17
  fmu_manipulation_toolbox/cli/fmusplit.py,sha256=sLzdxiC4R5hJYJo9F2TZOMrJOHcadqCvUo9KoCjUaxE,1773
18
18
  fmu_manipulation_toolbox/cli/fmutool.py,sha256=E-CCymksBwGFlS3-zJ7DIDA8xIdp_synsxMDYXst8dc,6186
19
19
  fmu_manipulation_toolbox/cli/utils.py,sha256=pvedN6cRiDkZ7RdPO1koBOZaGuydYhgcoO0-_QQpOoI,1601
20
+ fmu_manipulation_toolbox/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ fmu_manipulation_toolbox/gui/gui.py,sha256=usnMIyMeBcn81oYOBufkosCVBj0JRmKoaTUYG3e9o48,29430
22
+ fmu_manipulation_toolbox/gui/gui_style.py,sha256=s6WdrnNd_lCMWhuBf5LKK8wrfLXCU7pFTLUfvqkJVno,6633
20
23
  fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png,sha256=FWIuyrXlaNLLePHfXj7j9ca5rT8Hgr14KCe1EqTCZyk,2288
21
24
  fmu_manipulation_toolbox/resources/checkbox-checked-hover.png,sha256=KNlV-d_aJNTTvUVXKGT9DBY30sIs2LwocLJrFKNKv8k,2419
22
25
  fmu_manipulation_toolbox/resources/checkbox-checked.png,sha256=gzyFqvRFsZixVh6ZlV4SMWUKzglY1rSn7SvJUKMVvtk,2411
@@ -34,7 +37,7 @@ fmu_manipulation_toolbox/resources/icon_fmu.png,sha256=EuygB2xcoM2WAfKKdyKG_UvTL
34
37
  fmu_manipulation_toolbox/resources/license.txt,sha256=5ODuU8g8pIkK-NMWXu_rjZ6k7gM7b-N2rmg87-2Kmqw,1583
35
38
  fmu_manipulation_toolbox/resources/mask.png,sha256=px1U4hQGL0AmZ4BQPknOVREpMpTSejbah3ntkpqAzFA,3008
36
39
  fmu_manipulation_toolbox/resources/model.png,sha256=EAf_HnZJe8zYGZygerG1MMt2U-tMMZlifzXPj4_iORA,208788
37
- fmu_manipulation_toolbox/resources/darwin64/container.dylib,sha256=BEFw77tHDaZ-D_LiCDbYF7aefPJ1z0n_G-XjGU2oN0Q,230704
40
+ fmu_manipulation_toolbox/resources/darwin64/container.dylib,sha256=0O1swE0kqgk3mK0sAH8_n1fZfzL9Ovb9dQ_lXlnkWak,230704
38
41
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd,sha256=OGfyJtaJntKypX5KDpuZ-nV1oYLZ6HV16pkpKOmYox4,2731
39
42
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd,sha256=HwyV7LBse-PQSv4z1xjmtzPU3Hjnv4mluq9YdSBNHMQ,3704
40
43
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ModelDescription.xsd,sha256=JM4j_9q-pc40XYHb28jfT3iV3aYM5JLqD5aRjO72K1E,18939
@@ -54,19 +57,19 @@ fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Type.xsd,sha256=TaHRoUBIFtmdEwBKB
54
57
  fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Unit.xsd,sha256=CK_F2t5LfyQ6eSNJ8soTFMVK9DU8vD2WiMi2MQvjB0g,3746
55
58
  fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Variable.xsd,sha256=3YU-3q1-c-namz7sMe5cxnmOVOJsRSmfWR02PKv3xaU,19171
56
59
  fmu_manipulation_toolbox/resources/fmi-3.0/fmi3VariableDependency.xsd,sha256=YQSBwXt4IsDlyegY8bX-qQHGSfE5TipTPfo2g2yqq1c,3082
57
- fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=hkRPy649TVuT3lcae-BMXUZH6JVwTNiejv_S-FKmZZU,34664
60
+ fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=LnxxVwaZJpheBY9_OwCNWIiT7xokFGd7WBwqxfZwg50,34664
58
61
  fmu_manipulation_toolbox/resources/linux32/server_sm,sha256=gzKU0BTeaRkvhTMQtHHj3K8uYFyEdyGGn_mZy_jG9xo,21304
59
- fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=VTg20ibYrtPCFj71x2ciwk5Trotr39DYu74ZpH5Z_9A,32464
60
- fmu_manipulation_toolbox/resources/linux64/container.so,sha256=Qwmdg6IFtmbzNgGXPPnmacVAfHFgS1sh0dJjn4VW2mI,194192
62
+ fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=yP3h3mfT_Jzy0j-CN0POSlcGy8tMR13ca6g0K4FZWMk,32464
63
+ fmu_manipulation_toolbox/resources/linux64/container.so,sha256=QKrG1i2AKH3DPqWoSY7Rdy-xnISy2ohnKAcnip83I2c,194064
61
64
  fmu_manipulation_toolbox/resources/linux64/server_sm,sha256=MZn6vITN2qpBHYt_RaK2VnFFp00hk8fTALBHmXPtLwc,22608
62
- fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=6M3HnC-Yo-6K30Vz7cM2v-WmsldJrQIW1kitE7MZw2I,17920
63
- fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=sGBilm9IUvn7ZyRE_TgElP6Eod7WykZ-alqQ9U2OYC8,15360
64
- fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=uuZhxRU8Vhgc7coHPGePcnb7Ck3F4NWkxFzmBIJCzxs,20992
65
- fmu_manipulation_toolbox/resources/win64/container.dll,sha256=526FLsn7DbNnRioLc3YayPBqX_IRpgKNzfAfG1GqrTg,153088
66
- fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=UamakjF0Tpd6l2SDvgwBtDIS24kO8JlI6wDv2GKwNCM,18432
67
- fmu_manipulation_toolbox-1.9.2b4.dist-info/licenses/LICENSE.txt,sha256=0Q8zhEwTu1K-MDmg8Khay5j56BIz2VLI6RcijIFbU_g,1255
68
- fmu_manipulation_toolbox-1.9.2b4.dist-info/METADATA,sha256=6QSkWxWMMoZEpMFuvOgIlzBuv7AFrgwRFp0QcpxhIK8,1899
69
- fmu_manipulation_toolbox-1.9.2b4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
70
- fmu_manipulation_toolbox-1.9.2b4.dist-info/entry_points.txt,sha256=VOWf1jbG1O-2JrqXOvSoHRRfdH3RNONvz-2RIPHNX0s,338
71
- fmu_manipulation_toolbox-1.9.2b4.dist-info/top_level.txt,sha256=9D_h-5BMjSqf9z-XFkbJL_bMppR2XNYW3WNuPkXou0k,25
72
- fmu_manipulation_toolbox-1.9.2b4.dist-info/RECORD,,
65
+ fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=GzLBrNR2RT-UGo8rfJcIXyRxv9-UreKG1MPy0Rab_8M,17920
66
+ fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=SXDSyWBZteYJxC4icno0uXI9MYzD-EafYwA4cEhrXno,15360
67
+ fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=Tv2F_mBH56jREbcH8uPczTp3O3EU7cIlCrbOEp2mS-g,20992
68
+ fmu_manipulation_toolbox/resources/win64/container.dll,sha256=jub3YR6C-4h7kUKojVcSWspIW9B8yAVU30SwZYQ0lYQ,153088
69
+ fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=91n336TCk2_0ODI7NGUcAr7TVccJyxnDmk3iUZ0ijxY,18432
70
+ fmu_manipulation_toolbox-1.9.2rc2.dist-info/licenses/LICENSE.txt,sha256=0Q8zhEwTu1K-MDmg8Khay5j56BIz2VLI6RcijIFbU_g,1255
71
+ fmu_manipulation_toolbox-1.9.2rc2.dist-info/METADATA,sha256=K_ovw0Ra0TCFf99xxFANilNn47FAt5J2rK1I6P0e-yE,1900
72
+ fmu_manipulation_toolbox-1.9.2rc2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
73
+ fmu_manipulation_toolbox-1.9.2rc2.dist-info/entry_points.txt,sha256=VOWf1jbG1O-2JrqXOvSoHRRfdH3RNONvz-2RIPHNX0s,338
74
+ fmu_manipulation_toolbox-1.9.2rc2.dist-info/top_level.txt,sha256=9D_h-5BMjSqf9z-XFkbJL_bMppR2XNYW3WNuPkXou0k,25
75
+ fmu_manipulation_toolbox-1.9.2rc2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5