fmu-manipulation-toolbox 1.7.5__py3-none-any.whl → 1.8__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 (24) hide show
  1. fmu_manipulation_toolbox/__version__.py +1 -1
  2. fmu_manipulation_toolbox/assembly.py +456 -0
  3. fmu_manipulation_toolbox/cli.py +27 -17
  4. fmu_manipulation_toolbox/fmu_container.py +34 -177
  5. fmu_manipulation_toolbox/gui.py +26 -15
  6. fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
  7. fmu_manipulation_toolbox/resources/icon-round.png +0 -0
  8. fmu_manipulation_toolbox/resources/icon.png +0 -0
  9. fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
  10. fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
  11. fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
  12. fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
  13. fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
  14. fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
  15. fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
  16. fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
  17. fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
  18. fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
  19. {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.dist-info}/METADATA +1 -1
  20. {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.dist-info}/RECORD +24 -22
  21. {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.dist-info}/LICENSE.txt +0 -0
  22. {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.dist-info}/WHEEL +0 -0
  23. {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.dist-info}/entry_points.txt +0 -0
  24. {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- 'V1.7.5'
1
+ 'V1.8'
@@ -0,0 +1,456 @@
1
+ import csv
2
+ import json
3
+ import logging
4
+ from typing import *
5
+ from pathlib import Path
6
+ import uuid
7
+ import xml.parsers.expat
8
+ import zipfile
9
+
10
+ from .fmu_container import FMUContainer, FMUContainerError
11
+
12
+ logger = logging.getLogger("fmu_manipulation_toolbox")
13
+
14
+
15
+ class Port:
16
+ def __init__(self, fmu_name: str, port_name: str):
17
+ self.fmu_name = fmu_name
18
+ self.port_name = port_name
19
+
20
+ def __hash__(self):
21
+ return hash(f"{self.fmu_name}/{self.port_name}")
22
+
23
+ def __eq__(self, other):
24
+ return str(self) == str(other)
25
+
26
+
27
+ class Connection:
28
+ def __init__(self, from_port: Port, to_port: Port):
29
+ self.from_port = from_port
30
+ self.to_port = to_port
31
+
32
+
33
+ class AssemblyNode:
34
+ def __init__(self, name: str, step_size: float = None, mt=False, profiling=False,
35
+ auto_link=True, auto_input=True, auto_output=True):
36
+ self.name = name
37
+ self.step_size = step_size
38
+ self.mt = mt
39
+ self.profiling = profiling
40
+ self.auto_link = auto_link
41
+ self.auto_input = auto_input
42
+ self.auto_output = auto_output
43
+ self.children: List[AssemblyNode] = []
44
+
45
+ self.fmu_names_list: Set[str] = set()
46
+ self.input_ports: Dict[Port, str] = {}
47
+ self.output_ports: Dict[Port, str] = {}
48
+ self.start_values: Dict[Port, str] = {}
49
+ self.drop_ports: List[Port] = []
50
+ self.links: List[Connection] = []
51
+
52
+ def add_sub_node(self, sub_node):
53
+ if sub_node.name is None:
54
+ sub_node.name = str(uuid.uuid4())+".fmu"
55
+
56
+ self.fmu_names_list.add(sub_node.name)
57
+ self.children.append(sub_node)
58
+
59
+ def add_fmu(self, fmu_name: str):
60
+ self.fmu_names_list.add(fmu_name)
61
+
62
+ def add_input(self, from_port_name: str, to_fmu_filename: str, to_port_name: str):
63
+ self.input_ports[Port(to_fmu_filename, to_port_name)] = from_port_name
64
+
65
+ def add_output(self, from_fmu_filename: str, from_port_name: str, to_port_name: str):
66
+ self.output_ports[Port(from_fmu_filename, from_port_name)] = to_port_name
67
+
68
+ def add_drop_port(self, fmu_filename: str, port_name: str):
69
+ self.drop_ports.append(Port(fmu_filename, port_name))
70
+
71
+ def add_link(self, from_fmu_filename: str, from_port_name: str, to_fmu_filename: str, to_port_name: str):
72
+ self.links.append(Connection(Port(from_fmu_filename, from_port_name),
73
+ Port(to_fmu_filename, to_port_name)))
74
+
75
+ def add_start_value(self, fmu_filename: str, port_name: str, value: str):
76
+ self.start_values[Port(fmu_filename, port_name)] = value
77
+
78
+ def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
79
+ for node in self.children:
80
+ node.make_fmu(fmu_directory, debug=debug)
81
+
82
+ container = FMUContainer(self.name, fmu_directory, description_pathname=description_pathname)
83
+
84
+ for fmu_name in sorted(self.fmu_names_list):
85
+ container.get_fmu(fmu_name)
86
+
87
+ for port, source in self.input_ports.items():
88
+ container.add_input(source, port.fmu_name, port.port_name)
89
+
90
+ for port, target in self.output_ports.items():
91
+ container.add_output(port.fmu_name, port.port_name, target)
92
+
93
+ for link in self.links:
94
+ container.add_link(link.from_port.fmu_name, link.from_port.port_name,
95
+ link.to_port.fmu_name, link.to_port.port_name)
96
+
97
+ for drop in self.drop_ports:
98
+ container.drop_port(drop.fmu_name, drop.port_name)
99
+
100
+ for port, value in self.start_values.items():
101
+ container.add_start_value(port.fmu_name, port.port_name, value)
102
+
103
+ container.add_implicit_rule(auto_input=self.auto_input,
104
+ auto_output=self.auto_output,
105
+ auto_link=self.auto_link)
106
+
107
+ container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling, debug=debug)
108
+
109
+ for node in self.children:
110
+ logger.info(f"Deleting transient FMU Container '{node.name}'")
111
+ (fmu_directory / node.name).unlink()
112
+
113
+
114
+ class AssemblyError(Exception):
115
+ def __init__(self, reason: str):
116
+ self.reason = reason
117
+
118
+ def __repr__(self):
119
+ return f"{self.reason}"
120
+
121
+
122
+ class Assembly:
123
+ def __init__(self, filename: str, step_size=None, auto_link=True, auto_input=True, debug=False,
124
+ auto_output=True, mt=False, profiling=False, fmu_directory: Path = "."):
125
+ self.filename = Path(filename)
126
+ self.default_auto_input = auto_input
127
+ self.debug = debug
128
+ self.default_auto_output = auto_output
129
+ self.default_step_size = step_size
130
+ self.default_auto_link = auto_link
131
+ self.default_mt = mt
132
+ self.default_profiling = profiling
133
+ self.fmu_directory = fmu_directory
134
+ self.transient_filenames: List[Path] = []
135
+ self.transient_dirnames: Set[Path] = set()
136
+
137
+ if not fmu_directory.is_dir():
138
+ raise FMUContainerError(f"FMU directory is not valid: '{fmu_directory}'")
139
+
140
+ self.description_pathname = fmu_directory / self.filename # For inclusion in FMU
141
+ self.root = None
142
+ self.read()
143
+
144
+ def add_transient_file(self, filename: str):
145
+ self.transient_filenames.append(self.fmu_directory / filename)
146
+ self.transient_dirnames.add(Path(filename).parent)
147
+
148
+ def __del__(self):
149
+ if not self.debug:
150
+ for filename in self.transient_filenames:
151
+ try:
152
+ filename.unlink()
153
+ except FileNotFoundError:
154
+ pass
155
+ for dirname in self.transient_dirnames:
156
+ while not str(dirname) == ".":
157
+ try:
158
+ (self.fmu_directory / dirname).rmdir()
159
+ except FileNotFoundError:
160
+ pass
161
+ dirname = dirname.parent
162
+
163
+ def read(self):
164
+ logger.info(f"Reading '{self.filename}'")
165
+ if self.filename.suffix == ".json":
166
+ self.read_json()
167
+ elif self.filename.suffix == ".ssp":
168
+ self.read_ssp()
169
+ elif self.filename.suffix == ".csv":
170
+ self.read_csv()
171
+ else:
172
+ raise FMUContainerError(f"Not supported file format '{self.filename}")
173
+
174
+ def write(self, filename: str):
175
+ if filename.endswith(".csv"):
176
+ return self.write_csv(filename)
177
+ elif filename.endswith(".json"):
178
+ return self.write_json(filename)
179
+ else:
180
+ logger.critical(f"Unable to write to '{filename}': format unsupported.")
181
+
182
+ def read_csv(self):
183
+ name = str(self.filename.with_suffix(".fmu"))
184
+ self.root = AssemblyNode(name, step_size=self.default_step_size, auto_link=self.default_auto_link,
185
+ mt=self.default_mt, profiling=self.default_profiling,
186
+ auto_input=self.default_auto_input, auto_output=self.default_auto_output)
187
+
188
+ with open(self.description_pathname) as file:
189
+ reader = csv.reader(file, delimiter=';')
190
+ self.check_csv_headers(reader)
191
+ for i, row in enumerate(reader):
192
+ if not row or row[0][0] == '#': # skip blank line of comment
193
+ continue
194
+
195
+ try:
196
+ rule, from_fmu_filename, from_port_name, to_fmu_filename, to_port_name = row
197
+ except ValueError:
198
+ logger.error(f"Line #{i+2}: expecting 5 columns. Line skipped.")
199
+ continue
200
+
201
+ rule = rule.upper()
202
+ if rule in ("LINK", "INPUT", "OUTPUT", "DROP", "FMU", "START"):
203
+ try:
204
+ self._read_csv_rule(self.root, rule,
205
+ from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
206
+ except AssemblyError as e:
207
+ logger.error(f"Line #{i+2}: {e}. Line skipped.")
208
+ continue
209
+ else:
210
+ logger.error(f"Line #{i+2}: unexpected rule '{rule}'. Line skipped.")
211
+
212
+ def write_csv(self, filename: Union[str, Path]):
213
+ if self.root.children:
214
+ raise AssemblyError("This assembly is not flat. Cannot export to CSV file.")
215
+
216
+ with open(self.fmu_directory / filename, "wt") as outfile:
217
+ outfile.write("rule;from_fmu;from_port;to_fmu;to_port\n")
218
+ for fmu in self.root.fmu_names_list:
219
+ outfile.write(f"FMU;{fmu};;;\n")
220
+ for port, source in self.root.input_ports.items():
221
+ outfile.write(f"INPUT;;{source};{port.fmu_name};{port.port_name}\n")
222
+ for port, target in self.root.output_ports.items():
223
+ outfile.write(f"OUTPUT;{port.fmu_name};{port.port_name};;{target}\n")
224
+ for link in self.root.links:
225
+ outfile.write(f"LINK;{link.from_port.fmu_name};{link.from_port.port_name};"
226
+ f"{link.to_port.fmu_name};{link.to_port.port_name}\n")
227
+ for port, value in self.root.start_values.items():
228
+ outfile.write(f"START;{port.fmu_name};{port.port_name};{value};\n")
229
+ for port in self.root.drop_ports:
230
+ outfile.write(f"DROP;{port.fmu_name};{port.port_name};;\n")
231
+
232
+ @staticmethod
233
+ def _read_csv_rule(node: AssemblyNode, rule: str, from_fmu_filename: str, from_port_name: str,
234
+ to_fmu_filename: str, to_port_name: str):
235
+ if rule == "FMU":
236
+ if not from_fmu_filename:
237
+ raise AssemblyError("Missing FMU information.")
238
+ node.add_fmu(from_fmu_filename)
239
+
240
+ elif rule == "INPUT":
241
+ if not to_fmu_filename or not to_port_name:
242
+ raise AssemblyError("Missing INPUT ports information.")
243
+ if not from_port_name:
244
+ from_port_name = to_port_name
245
+ node.add_input(from_port_name, to_fmu_filename, to_port_name)
246
+
247
+ elif rule == "OUTPUT":
248
+ if not from_fmu_filename or not from_port_name:
249
+ raise AssemblyError("Missing OUTPUT ports information.")
250
+ if not to_port_name:
251
+ to_port_name = from_port_name
252
+ node.add_output(from_fmu_filename, from_port_name, to_port_name)
253
+
254
+ elif rule == "DROP":
255
+ if not from_fmu_filename or not from_port_name:
256
+ raise AssemblyError("Missing DROP ports information.")
257
+ node.add_drop_port(from_fmu_filename, from_port_name)
258
+
259
+ elif rule == "LINK":
260
+ node.add_link(from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
261
+
262
+ elif rule == "START":
263
+ if not from_fmu_filename or not from_port_name or not to_fmu_filename:
264
+ raise AssemblyError("Missing START ports information.")
265
+
266
+ node.add_start_value(from_fmu_filename, from_port_name, to_fmu_filename)
267
+ # no else: check on rule is already done in read_description()
268
+
269
+ @staticmethod
270
+ def check_csv_headers(reader):
271
+ headers = next(reader)
272
+ if not headers == ["rule", "from_fmu", "from_port", "to_fmu", "to_port"]:
273
+ raise AssemblyError("Header (1st line of the file) is not well formatted.")
274
+
275
+ def read_json(self):
276
+ with open(self.description_pathname) as file:
277
+ try:
278
+ data = json.load(file)
279
+ except json.decoder.JSONDecodeError as e:
280
+ raise FMUContainerError(f"Cannot read json: {e}")
281
+ self.root = self.json_decode_node(data)
282
+ if not self.root.name:
283
+ self.root.name = str(self.filename.with_suffix(".fmu"))
284
+
285
+ def write_json(self, filename: Union[str, Path]):
286
+ with open(self.fmu_directory / filename, "wt") as file:
287
+ data = self.json_encode_node(self.root)
288
+ json.dump(data, file, indent=2)
289
+
290
+ def json_encode_node(self, node: AssemblyNode) -> Dict[str, Any]:
291
+ json_node = dict()
292
+ json_node["name"] = node.name
293
+ json_node["mt"] = node.mt
294
+ json_node["profiling"] = node.profiling
295
+ json_node["auto_link"] = node.auto_link
296
+ if node.step_size:
297
+ json_node["step_size"] = node.step_size
298
+
299
+ if node.children:
300
+ json_node["container"] = [self.json_encode_node(child) for child in node.children]
301
+
302
+ if node.fmu_names_list:
303
+ json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)]
304
+
305
+ if node.input_ports:
306
+ json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"]
307
+ for port, source in node.input_ports.items()]
308
+
309
+ if node.output_ports:
310
+ json_node["output"] = [[f"{port.fmu_name}", f"{port.port_name}", f"{target}"]
311
+ for port, target in node.output_ports.items()]
312
+
313
+ if node.links:
314
+ json_node["link"] = [[f"{link.from_port.fmu_name}", f"{link.from_port.port_name}",
315
+ f"{link.to_port.fmu_name}", f"{link.to_port.port_name}"]
316
+ for link in node.links]
317
+
318
+ if node.start_values:
319
+ json_node["start"] = [[f"{port.fmu_name}", f"{port.port_name}", value]
320
+ for port, value in node.start_values.items()]
321
+
322
+ if node.drop_ports:
323
+ json_node["drop"] = [[f"{port.fmu_name}", f"{port.port_name}"] for port in node.drop_ports]
324
+
325
+ return json_node
326
+
327
+ def json_decode_node(self, data) -> AssemblyNode:
328
+ name = data.get("name", None)
329
+ step_size = data.get("step_size", self.default_step_size)
330
+ auto_link = data.get("auto_link", self.default_auto_link)
331
+ auto_input = data.get("auto_input", self.default_auto_input)
332
+ auto_output = data.get("auto_output", self.default_auto_output)
333
+ mt = data.get("mt", self.default_mt)
334
+ profiling = data.get("profiling", self.default_profiling)
335
+
336
+ node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
337
+ auto_input=auto_input, auto_output=auto_output)
338
+
339
+ if "container" in data:
340
+ if not isinstance(data["container"], list):
341
+ raise FMUContainerError("JSON: 'container' keyword should define a list.")
342
+ for sub_data in data["container"]:
343
+ node.add_sub_node(self.json_decode_node(sub_data))
344
+
345
+ if "fmu" in data:
346
+ if not isinstance(data["fmu"], list):
347
+ raise FMUContainerError("JSON: 'fmu' keyword should define a list.")
348
+ for fmu in data["fmu"]:
349
+ node.add_fmu(fmu)
350
+
351
+ if "input" in data:
352
+ if not isinstance(data["input"], list):
353
+ raise FMUContainerError("JSON: 'input' keyword should define a list.")
354
+ for line in data["input"]:
355
+ node.add_input(line[1], line[2], line[0])
356
+
357
+ if "output" in data:
358
+ if not isinstance(data["output"], list):
359
+ raise FMUContainerError("JSON: 'output' keyword should define a list.")
360
+ for line in data["output"]:
361
+ node.add_output(line[0], line[1], line[2])
362
+
363
+ if "start" in data:
364
+ if not isinstance(data["start"], list):
365
+ raise FMUContainerError("JSON: 'start' keyword should define a list.")
366
+ for line in data["start"]:
367
+ node.add_start_value(line[0], line[1], line[2])
368
+
369
+ if "drop" in data:
370
+ for line in data["drop"]:
371
+ node.add_drop_port(line[0], line[1])
372
+
373
+ return node
374
+
375
+ def read_ssp(self):
376
+ logger.warning("This feature is ALPHA stage.")
377
+
378
+ with zipfile.ZipFile(self.fmu_directory / self.filename) as zin:
379
+ for file in zin.filelist:
380
+ if file.filename.endswith(".fmu") or file.filename.endswith(".ssd"):
381
+ zin.extract(file, path=self.fmu_directory)
382
+ logger.debug(f"Extracted: {file.filename}")
383
+ self.add_transient_file(file.filename)
384
+
385
+ self.description_pathname = self.fmu_directory / "SystemStructure.ssd"
386
+ if self.description_pathname.is_file():
387
+ sdd = SSDParser(step_size=self.default_step_size, auto_link=self.default_auto_link,
388
+ mt=self.default_mt, profiling=self.default_profiling,
389
+ auto_input=self.default_auto_input, auto_output=self.default_auto_output)
390
+ self.root = sdd.parse(self.description_pathname)
391
+
392
+ def make_fmu(self, dump_json=False):
393
+ if dump_json:
394
+ dump_file = (self.description_pathname.with_stem(self.description_pathname.stem + "-dump")
395
+ .with_suffix(".json"))
396
+ logger.info(f"Dump Json '{dump_file}'")
397
+ self.write_json(dump_file)
398
+ self.root.make_fmu(self.fmu_directory, debug=self.debug, description_pathname=self.description_pathname)
399
+
400
+
401
+ class SSDParser:
402
+ def __init__(self, **kwargs):
403
+ self.node_stack: List[AssemblyNode] = []
404
+ self.root = None
405
+ self.fmu_filenames: Dict[str, str] = {} # Component name => FMU filename
406
+ self.node_attrs = kwargs
407
+
408
+ def parse(self, ssd_filepath: Path) -> AssemblyNode:
409
+ logger.debug(f"Analysing {ssd_filepath}")
410
+ with open(ssd_filepath, "rb") as file:
411
+ parser = xml.parsers.expat.ParserCreate()
412
+ parser.StartElementHandler = self.start_element
413
+ parser.EndElementHandler = self.end_element
414
+ parser.ParseFile(file)
415
+
416
+ return self.root
417
+
418
+ def start_element(self, tag_name, attrs):
419
+ if tag_name == 'ssd:Connection':
420
+ if 'startElement' in attrs:
421
+ if 'endElement' in attrs:
422
+ fmu_start = self.fmu_filenames[attrs['startElement']]
423
+ fmu_end = self.fmu_filenames[attrs['endElement']]
424
+ self.node_stack[-1].add_link(fmu_start, attrs['startConnector'],
425
+ fmu_end, attrs['endConnector'])
426
+ else:
427
+ fmu_start = self.fmu_filenames[attrs['startElement']]
428
+ self.node_stack[-1].add_output(fmu_start, attrs['startConnector'],
429
+ attrs['endConnector'])
430
+ else:
431
+ fmu_end = self.fmu_filenames[attrs['endElement']]
432
+ self.node_stack[-1].add_input(attrs['startConnector'],
433
+ fmu_end, attrs['endConnector'])
434
+
435
+ elif tag_name == 'ssd:System':
436
+ logger.info(f"SSP System: {attrs['name']}")
437
+ filename = attrs['name'] + ".fmu"
438
+ self.fmu_filenames[attrs['name']] = filename
439
+ node = AssemblyNode(filename, **self.node_attrs)
440
+ if self.node_stack:
441
+ self.node_stack[-1].add_sub_node(node)
442
+ else:
443
+ self.root = node
444
+
445
+ self.node_stack.append(node)
446
+
447
+ elif tag_name == 'ssd:Component':
448
+ filename = attrs['source']
449
+ name = attrs['name']
450
+ self.fmu_filenames[name] = filename
451
+ self.node_stack[-1].add_fmu(filename)
452
+ logger.debug(f"Component {name} => {filename}")
453
+
454
+ def end_element(self, tag_name):
455
+ if tag_name == 'ssd:System':
456
+ self.node_stack.pop()
@@ -4,7 +4,8 @@ import sys
4
4
  from colorama import Fore, Style, init
5
5
 
6
6
  from .fmu_operations import *
7
- from .fmu_container import FMUContainerSpecReader, FMUContainerError
7
+ from .fmu_container import FMUContainerError
8
+ from .assembly import Assembly
8
9
  from .checker import checker_list
9
10
  from .version import __version__ as version
10
11
  from .help import Help
@@ -158,11 +159,12 @@ def fmucontainer():
158
159
 
159
160
  parser.add_argument('-h', '-help', action="help")
160
161
 
161
- parser.add_argument("-fmu-directory", action="store", dest="fmu_directory", required=True,
162
- help="Directory containing initial FMU’s and used to generate containers.")
162
+ parser.add_argument("-fmu-directory", action="store", dest="fmu_directory", required=False, default=".",
163
+ help="Directory containing initial FMU’s and used to generate containers. "
164
+ "If not defined, current directory is used.")
163
165
 
164
166
  parser.add_argument("-container", action="append", dest="container_descriptions_list", default=[],
165
- metavar="filename.csv:step_size",
167
+ metavar="filename.{csv|json|ssp},[:step_size]", required=True,
166
168
  help="Description of the container to create.")
167
169
 
168
170
  parser.add_argument("-debug", action="store_true", dest="debug",
@@ -183,34 +185,42 @@ def fmucontainer():
183
185
  parser.add_argument("-profile", action="store_true", dest="profiling", default=False,
184
186
  help="Enable Profiling mode for the generated container.")
185
187
 
188
+ parser.add_argument("-dump-json", action="store_true", dest="dump", default=False,
189
+ help="Dump a JSON file for each container.")
190
+
186
191
  config = parser.parse_args()
187
192
 
188
193
  if config.debug:
189
194
  logger.setLevel(logging.DEBUG)
190
195
 
196
+ fmu_directory = Path(config.fmu_directory)
197
+ logger.info(f"FMU directory: '{fmu_directory}'")
198
+
191
199
  for description in config.container_descriptions_list:
192
200
  try:
193
- filename_description, step_size = description.split(":")
201
+ filename, step_size = description.split(":")
194
202
  step_size = float(step_size)
195
203
  except ValueError:
196
204
  step_size = None
197
- filename_description = description
205
+ filename = description
206
+ try:
207
+ assembly = Assembly(filename, step_size=step_size, auto_link=config.auto_link,
208
+ auto_input=config.auto_input, auto_output=config.auto_output, mt=config.mt,
209
+ profiling=config.profiling, fmu_directory=fmu_directory, debug=config.debug)
198
210
 
199
- container_filename = Path(filename_description).with_suffix(".fmu")
211
+ except FileNotFoundError as e:
212
+ logger.fatal(f"Cannot read file: {e}")
213
+ continue
214
+ except FMUContainerError as e:
215
+ logger.fatal(f"{filename}: {e}")
216
+ continue
200
217
 
201
218
  try:
202
- csv_reader = FMUContainerSpecReader(Path(config.fmu_directory))
203
- container = csv_reader.read(filename_description)
204
- container.add_implicit_rule(auto_input=config.auto_input,
205
- auto_output=config.auto_output,
206
- auto_link=config.auto_link)
207
- container.make_fmu(container_filename, step_size=step_size, debug=config.debug, mt=config.mt,
208
- profiling=config.profiling)
209
- except (FileNotFoundError, FMUContainerError, FMUException) as e:
210
- logger.error(f"Cannot build container from '{filename_description}': {e}")
219
+ assembly.make_fmu(dump_json=config.dump)
220
+ except FMUContainerError as e:
221
+ logger.fatal(f"{filename}: {e}")
211
222
  continue
212
223
 
213
224
 
214
- # for debug purpose
215
225
  if __name__ == "__main__":
216
226
  fmucontainer()
@@ -1,4 +1,3 @@
1
- import csv
2
1
  import logging
3
2
  import os
4
3
  import shutil
@@ -8,9 +7,10 @@ from datetime import datetime
8
7
  from pathlib import Path
9
8
  from typing import *
10
9
 
11
- from .fmu_operations import FMU, OperationAbstract, FMUException
10
+ from .fmu_operations import FMU, OperationAbstract
12
11
  from .version import __version__ as tool_version
13
12
 
13
+
14
14
  logger = logging.getLogger("fmu_manipulation_toolbox")
15
15
 
16
16
 
@@ -30,15 +30,14 @@ class FMUPort:
30
30
  def set_port_type(self, type_name: str, attrs: Dict[str, str]):
31
31
  self.type_name = type_name
32
32
  self.child = attrs.copy()
33
- try:
34
- self.child.pop("unit") # Unit are not supported
35
- except KeyError:
36
- pass
33
+ for unsupported in ("unit", "declaredType"):
34
+ if unsupported in self.child:
35
+ self.child.pop(unsupported)
37
36
 
38
37
  def xml(self, vr: int, name=None, causality=None, start=None):
39
38
 
40
39
  if self.child is None:
41
- raise FMUException(f"FMUPort has no child. Bug?")
40
+ raise FMUContainerError(f"FMUPort has no child. Bug?")
42
41
 
43
42
  child_str = f"<{self.type_name}"
44
43
  if self.child:
@@ -70,7 +69,6 @@ class FMUPort:
70
69
 
71
70
  class EmbeddedFMU(OperationAbstract):
72
71
  capability_list = ("needsExecutionTool",
73
- "canHandleVariableCommunicationStepSize",
74
72
  "canBeInstantiatedOnlyOncePerProcess")
75
73
 
76
74
  def __init__(self, filename):
@@ -87,6 +85,8 @@ class EmbeddedFMU(OperationAbstract):
87
85
  self.current_port = None # used during apply_operation()
88
86
 
89
87
  self.fmu.apply_operation(self) # Should be the last command in constructor!
88
+ if self.model_identifier is None:
89
+ raise FMUContainerError(f"FMU '{self.name}' does not implement Co-Simulation mode.")
90
90
 
91
91
  def fmi_attrs(self, attrs):
92
92
  self.guid = attrs['guid']
@@ -104,10 +104,16 @@ class EmbeddedFMU(OperationAbstract):
104
104
  self.capabilities[capability] = attrs.get(capability, "false")
105
105
 
106
106
  def experiment_attrs(self, attrs):
107
- self.step_size = float(attrs['stepSize'])
107
+ try:
108
+ self.step_size = float(attrs['stepSize'])
109
+ except KeyError:
110
+ logger.warning(f"FMU '{self.name}' does not specify preferred step size")
111
+ pass
108
112
 
109
113
  def scalar_type(self, type_name, attrs):
110
- self.current_port.set_port_type(type_name, attrs)
114
+ if self.current_port:
115
+ self.current_port.set_port_type(type_name, attrs)
116
+ self.current_port = None
111
117
 
112
118
  def __repr__(self):
113
119
  return f"FMU '{self.name}' ({len(self.ports)} variables)"
@@ -172,14 +178,14 @@ class ValueReferenceTable:
172
178
  def get_vr(self, cport: ContainerPort) -> int:
173
179
  return self.add_vr(cport.port.type_name)
174
180
 
175
- def add_vr(self, type_name:str) -> int:
181
+ def add_vr(self, type_name: str) -> int:
176
182
  vr = self.vr_table[type_name]
177
183
  self.vr_table[type_name] += 1
178
184
  return vr
179
185
 
180
186
 
181
187
  class FMUContainer:
182
- def __init__(self, identifier: str, fmu_directory: Union[str, Path]):
188
+ def __init__(self, identifier: str, fmu_directory: Union[str, Path], description_pathname=None):
183
189
  self.fmu_directory = Path(fmu_directory)
184
190
  self.identifier = identifier
185
191
  if not self.fmu_directory.is_dir():
@@ -187,8 +193,7 @@ class FMUContainer:
187
193
  self.involved_fmu: Dict[str, EmbeddedFMU] = {}
188
194
  self.execution_order: List[EmbeddedFMU] = []
189
195
 
190
- self.description_pathname = None # Will be set up by FMUContainerSpecReader
191
- self.period = None # Will be set up by FMUContainerSpecReader
196
+ self.description_pathname = description_pathname
192
197
 
193
198
  # Rules
194
199
  self.inputs: Dict[str, ContainerPort] = {}
@@ -207,10 +212,10 @@ class FMUContainer:
207
212
  self.involved_fmu[fmu_filename] = fmu
208
213
  self.execution_order.append(fmu)
209
214
  if not fmu.fmi_version == "2.0":
210
- raise FMUException("Only FMI-2.0 is supported by FMUContainer")
215
+ raise FMUContainerError("Only FMI-2.0 is supported by FMUContainer")
211
216
  logger.debug(f"Adding FMU #{len(self.execution_order)}: {fmu}")
212
217
  except Exception as e:
213
- raise FMUException(f"Cannot load '{fmu_filename}': {e}")
218
+ raise FMUContainerError(f"Cannot load '{fmu_filename}': {e}")
214
219
 
215
220
  return fmu
216
221
 
@@ -227,7 +232,8 @@ class FMUContainer:
227
232
  container_port_name = to_port_name
228
233
  cport_to = ContainerPort(self.get_fmu(to_fmu_filename), to_port_name)
229
234
  if not cport_to.port.causality == "input": # check causality
230
- raise FMUException(f"{cport_to} is {cport_to.port.causality} instead of INPUT.")
235
+ raise FMUContainerError(f"Tried to use '{cport_to}' as INPUT of the container but FMU causality is "
236
+ f"'{cport_to.port.causality}'.")
231
237
 
232
238
  logger.debug(f"INPUT: {to_fmu_filename}:{to_port_name}")
233
239
  self.mark_ruled(cport_to, 'INPUT')
@@ -239,7 +245,8 @@ class FMUContainer:
239
245
 
240
246
  cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
241
247
  if cport_from.port.causality not in ("output", "local"): # check causality
242
- raise FMUException(f"{cport_from} is {cport_from.port.causality} instead of OUTPUT or LOCAL")
248
+ raise FMUContainerError(f"Tried to use '{cport_from}' as INPUT of the container but FMU causality is "
249
+ f"'{cport_from.port.causality}'.")
243
250
 
244
251
  logger.debug(f"OUTPUT: {from_fmu_filename}:{from_port_name}")
245
252
  self.mark_ruled(cport_from, 'OUTPUT')
@@ -248,7 +255,7 @@ class FMUContainer:
248
255
  def drop_port(self, from_fmu_filename: str, from_port_name: str):
249
256
  cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
250
257
  if not cport_from.port.causality == "output": # check causality
251
- raise FMUException(f"{cport_from}: trying to DROP {cport_from.port.causality}")
258
+ raise FMUContainerError(f"{cport_from}: trying to DROP {cport_from.port.causality}")
252
259
 
253
260
  logger.debug(f"DROP: {from_fmu_filename}:{from_port_name}")
254
261
  self.mark_ruled(cport_from, 'DROP')
@@ -342,6 +349,8 @@ class FMUContainer:
342
349
  def sanity_check(self, step_size: Union[float, None]):
343
350
  nb_error = 0
344
351
  for fmu in self.execution_order:
352
+ if not fmu.step_size:
353
+ continue
345
354
  ts_ratio = step_size / fmu.step_size
346
355
  if ts_ratio < 1.0:
347
356
  logger.error(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
@@ -412,12 +421,12 @@ class FMUContainer:
412
421
  description="FMUContainer with {embedded_fmu}"
413
422
  author="{author}"
414
423
  license="Proprietary"
415
- copyright="© Renault S.A.S"
424
+ copyright="See Embedded FMU's copyrights."
416
425
  variableNamingConvention="structured">
417
426
 
418
427
  <CoSimulation
419
428
  modelIdentifier="{self.identifier}"
420
- canHandleVariableCommunicationStepSize="{capabilities['canHandleVariableCommunicationStepSize']}"
429
+ canHandleVariableCommunicationStepSize="true"
421
430
  canBeInstantiatedOnlyOncePerProcess="{capabilities['canBeInstantiatedOnlyOncePerProcess']}"
422
431
  canNotUseMemoryManagementFunctions="true"
423
432
  canGetAndSetFMUstate="false"
@@ -438,7 +447,8 @@ class FMUContainer:
438
447
  for fmu in self.execution_order:
439
448
  vr = vr_table.add_vr("Real")
440
449
  name = f"container.{fmu.model_identifier}.rt_ratio"
441
- print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local"><Real /></ScalarVariable>', file=xml_file)
450
+ print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
451
+ f'<Real /></ScalarVariable>', file=xml_file)
442
452
 
443
453
  # Local variable should be first to ensure to attribute them the lowest VR.
444
454
  for local in self.locals.values():
@@ -555,7 +565,7 @@ class FMUContainer:
555
565
  if profiling and type_name == "Real":
556
566
  nb += len(self.execution_order)
557
567
  print(nb, file=txt_file)
558
- for profiling_port,_ in enumerate(self.execution_order):
568
+ for profiling_port, _ in enumerate(self.execution_order):
559
569
  print(f"{profiling_port} -2 {profiling_port}", file=txt_file)
560
570
  else:
561
571
  print(nb, file=txt_file)
@@ -600,6 +610,7 @@ class FMUContainer:
600
610
  documentation_directory.mkdir(exist_ok=True)
601
611
 
602
612
  if self.description_pathname:
613
+ logger.debug(f"Copying {self.description_pathname}")
603
614
  shutil.copy(self.description_pathname, documentation_directory)
604
615
 
605
616
  shutil.copy(origin / "model.png", base_directory)
@@ -628,157 +639,3 @@ class FMUContainer:
628
639
  def make_fmu_cleanup(base_directory: Path):
629
640
  logger.debug(f"Delete directory '{base_directory}'")
630
641
  shutil.rmtree(base_directory)
631
-
632
-
633
- class FMUContainerSpecReader:
634
- def __init__(self, fmu_directory: Union[Path, str]):
635
- self.fmu_directory = Path(fmu_directory)
636
-
637
- def read(self, description_filename: Union[str, Path]) -> FMUContainer:
638
- if isinstance(description_filename, str):
639
- description_filename = Path(description_filename)
640
-
641
- if description_filename.suffix == ".csv":
642
- return self.read_csv(description_filename)
643
- else:
644
- logger.critical(f"Unable to read from '{description_filename}': format unsupported.")
645
-
646
- def read_csv(self, description_filename: Path) -> FMUContainer:
647
- container = FMUContainer(description_filename.stem, self.fmu_directory)
648
- container.description_pathname = self.fmu_directory / description_filename
649
- logger.info(f"Building FMU Container from '{container.description_pathname}'")
650
-
651
- with open(container.description_pathname) as file:
652
- reader = csv.reader(file, delimiter=';')
653
- self.check_headers(reader)
654
- for i, row in enumerate(reader):
655
- if not row or row[0][0] == '#': # skip blank line of comment
656
- continue
657
-
658
- try:
659
- rule, from_fmu_filename, from_port_name, to_fmu_filename, to_port_name = row
660
- except ValueError:
661
- logger.error(f"Line #{i+2}: expecting 5 columns. Line skipped.")
662
- continue
663
-
664
- rule = rule.upper()
665
- if rule in ("LINK", "INPUT", "OUTPUT", "DROP", "FMU", "START"):
666
- try:
667
- self._read_csv_rule(container, rule,
668
- from_fmu_filename, from_port_name,
669
- to_fmu_filename, to_port_name)
670
- except FMUContainerError as e:
671
- logger.error(f"Line #{i+2}: {e}. Line skipped.")
672
- continue
673
- except FMUException as e:
674
- logger.critical(f"Line #{i + 2}: {e}.")
675
- raise
676
- else:
677
- logger.error(f"Line #{i+2}: unexpected rule '{rule}'. Line skipped.")
678
-
679
- return container
680
-
681
- @staticmethod
682
- def _read_csv_rule(container: FMUContainer, rule: str, from_fmu_filename: str, from_port_name: str,
683
- to_fmu_filename: str, to_port_name: str):
684
- if rule == "FMU":
685
- if not from_fmu_filename:
686
- raise FMUException("Missing FMU information.")
687
- container.get_fmu(from_fmu_filename)
688
-
689
- elif rule == "INPUT":
690
- if not to_fmu_filename or not to_port_name:
691
- raise FMUException("Missing INPUT ports information.")
692
- container.add_input(from_port_name, to_fmu_filename, to_port_name)
693
-
694
- elif rule == "OUTPUT":
695
- if not from_fmu_filename or not from_port_name:
696
- raise FMUException("Missing OUTPUT ports information.")
697
- container.add_output(from_fmu_filename, from_port_name, to_port_name)
698
-
699
- elif rule == "DROP":
700
- if not from_fmu_filename or not from_port_name:
701
- raise FMUException("Missing DROP ports information.")
702
- container.drop_port(from_fmu_filename, from_port_name)
703
-
704
- elif rule == "LINK":
705
- container.add_link(from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
706
-
707
- elif rule == "START":
708
- if not from_fmu_filename or not from_port_name or not to_fmu_filename:
709
- raise FMUException("Missing START ports information.")
710
-
711
- container.add_start_value(from_fmu_filename, from_port_name, to_fmu_filename)
712
- # no else: check on rule is already done in read_description()
713
-
714
- @staticmethod
715
- def check_headers(reader):
716
- headers = next(reader)
717
- if not headers == ["rule", "from_fmu", "from_port", "to_fmu", "to_port"]:
718
- raise FMUContainerError("Header (1st line of the file) is not well formatted.")
719
-
720
-
721
- class FMUContainerSpecWriter:
722
- def __init__(self, container: FMUContainer):
723
- self.container = container
724
-
725
- def write(self, description_filename: Union[str, Path]):
726
- if description_filename.endswith(".csv"):
727
- return self.write_csv(description_filename)
728
- elif description_filename.endswith(".json"):
729
- return self.write_json(description_filename)
730
- else:
731
- logger.critical(f"Unable to write to '{description_filename}': format unsupported.")
732
-
733
- def write_csv(self, description_filename: Union[str, Path]):
734
- with open(description_filename, "wt") as outfile:
735
- print("rule;from_fmu;from_port;to_fmu;to_port", file=outfile)
736
- for fmu in self.container.involved_fmu.keys():
737
- print(f"FMU;{fmu};;;", file=outfile)
738
- for cport in self.container.inputs.values():
739
- print(f"INPUT;;;{cport.fmu.name};{cport.port.name}", file=outfile)
740
- for cport in self.container.outputs.values():
741
- print(f"OUTPUT;{cport.fmu.name};{cport.port.name};;", file=outfile)
742
- for local in self.container.locals.values():
743
- for target in local.cport_to_list:
744
- print(f"LINK;{local.cport_from.fmu.name};{local.cport_from.port.name};"
745
- f"{target.fmu.name};{target.port.name}",
746
- file=outfile)
747
- for cport, value in self.container.start_values.items():
748
- print(f"START;{cport.fmu.name};{cport.port.name};{value};", file=outfile)
749
-
750
- def write_json(self, description_filename: Union[str, Path]):
751
- with open(description_filename, "wt") as outfile:
752
- print("{", file=outfile)
753
-
754
- print(f' "fmu": [', file=outfile)
755
- fmus = [f' "{fmu}"' for fmu in self.container.involved_fmu.keys()]
756
- print(",\n".join(fmus), file=outfile)
757
- print(f' ],', file=outfile)
758
-
759
- print(f' "input": [', file=outfile)
760
- inputs = [f' [{cport.fmu.name}, {cport.port.name}, {container_name}]'
761
- for container_name, cport in self.container.inputs.items()]
762
- print(",\n".join(inputs), file=outfile)
763
- print(f' ],', file=outfile)
764
-
765
- print(f' "output": [', file=outfile)
766
- outputs = [f' ["{cport.fmu.name}", "{cport.port.name}", "{container_name}"]'
767
- for container_name, cport in self.container.outputs.items()]
768
- print(",\n".join(outputs), file=outfile)
769
- print(f' ],', file=outfile)
770
-
771
- print(f' "link": [', file=outfile)
772
- links = [f' ["{local.cport_from.fmu.name}", "{local.cport_from.port.name}", '
773
- f'"{target.fmu.name}", "{target.port.name}"]'
774
- for local in self.container.locals.values()
775
- for target in local.cport_to_list]
776
- print(",\n".join(links), file=outfile)
777
- print(f' ],', file=outfile)
778
- print(f' "start": [', file=outfile)
779
- start = [f' ["{cport.fmu.name}", "{cport.port.name}, "{value}"]'
780
- for cport, value in self.container.start_values.items()]
781
- print(f' ],', file=outfile)
782
-
783
- #print(f' "period": {self.container.})
784
- print("}", file=outfile)
@@ -209,11 +209,10 @@ class FilterWidget(QPushButton):
209
209
 
210
210
 
211
211
  class FMUManipulationToolboxlMainWindow(QWidget):
212
- def __init__(self, app, *args, **kwargs):
212
+ def __init__(self, *args, **kwargs):
213
213
  super().__init__(*args, **kwargs)
214
214
 
215
215
  self.setWindowTitle('FMU Manipulation Toolbox')
216
- self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon.png')))
217
216
 
218
217
  # set the grid layout
219
218
  self.layout = QGridLayout()
@@ -289,7 +288,7 @@ class FMUManipulationToolboxlMainWindow(QWidget):
289
288
 
290
289
  exit_button = QPushButton('Exit')
291
290
  self.layout.addWidget(exit_button, line, 0, 1, 2)
292
- exit_button.clicked.connect(app.exit)
291
+ exit_button.clicked.connect(self.close)
293
292
  exit_button.setProperty("class", "quit")
294
293
 
295
294
  save_log_button = QPushButton('Save log as')
@@ -434,7 +433,7 @@ class FMUManipulationToolboxlMainWindow(QWidget):
434
433
  scroll_bar.setValue(scroll_bar.maximum())
435
434
 
436
435
 
437
- class Application:
436
+ class Application(QApplication):
438
437
  """
439
438
  Analyse and modify your FMUs.
440
439
 
@@ -442,12 +441,13 @@ Note: modifying the modelDescription.xml can damage your FMU ! Communicating wit
442
441
  way the FMU is generated, is preferable when possible.
443
442
 
444
443
  """
445
- def __init__(self):
444
+ def __init__(self, *args, **kwargs):
445
+ super().__init__(*args, **kwargs)
446
+
446
447
  QDir.addSearchPath('images', os.path.join(os.path.dirname(__file__), "resources"))
447
- self.app = QApplication(sys.argv)
448
448
  font = QFont("Verdana")
449
449
  font.setPointSize(10)
450
- self.app.setFont(font)
450
+ self.setFont(font)
451
451
  css_dark = """
452
452
  QWidget {background: #4b4e51; color: #b5bab9}
453
453
  QPushButton { min-height: 30px; padding: 1px 1px 0.2em 0.2em; border: 1px solid #282830; border-radius: 5px;}
@@ -479,15 +479,26 @@ QMenu::indicator:unchecked:hover {width: 35px; image: url(images:checkbox-unc
479
479
  QMenu::indicator:unchecked:disabled {width: 35px; image: url(images:checkbox-unchecked-disabled.png); }
480
480
  """
481
481
 
482
- self.app.setStyleSheet(css_dark)
483
- self.window = FMUManipulationToolboxlMainWindow(self.app)
484
- print(" "*80, f"Version {version}")
485
- print(self.__doc__)
486
- sys.exit(self.app.exec())
482
+ self.setStyleSheet(css_dark)
487
483
 
488
- def exit(self):
489
- self.app.exit()
484
+
485
+ if os.name == 'nt':
486
+ self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon-round.png')))
487
+
488
+ # https://stackoverflow.com/questions/1551605/how-to-set-applications-taskbar-icon-in-windows-7/1552105#1552105
489
+ import ctypes
490
+ application_id = 'FMU_Manipulation_Toolbox' # arbitrary string
491
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(application_id)
492
+ else:
493
+ self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon.png')))
494
+
495
+ self.window = FMUManipulationToolboxlMainWindow()
490
496
 
491
497
 
492
498
  def main():
493
- Application()
499
+ application = Application(sys.argv)
500
+
501
+ print(" " * 80, f"Version {version}")
502
+ print(application.__doc__)
503
+
504
+ sys.exit(application.exec())
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fmu_manipulation_toolbox
3
- Version: 1.7.5
3
+ Version: 1.8
4
4
  Summary: FMU Manipulation Toobox is a python application which help to modify a Functional Mock-up Units (FMUs) without recompilation or to group them into FMU Containers
5
5
  Home-page: https://github.com/grouperenault/fmu_manipulation_toolbox/
6
6
  Author: Nicolas.LAURENT@Renault.com
@@ -1,11 +1,12 @@
1
1
  fmu_manipulation_toolbox/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
2
  fmu_manipulation_toolbox/__main__.py,sha256=mAzrtkil506DS0F3g3CEbHdtZsZotzntcNhIn_lNJkw,344
3
- fmu_manipulation_toolbox/__version__.py,sha256=Wut6SKNbd8Xv-YaWqFSqRdSiynpuD2Qf2Lt-2Or4hcA,9
3
+ fmu_manipulation_toolbox/__version__.py,sha256=wgnOv0C5Ic8_OmpPXk8qES2otG4lRxHybApfWS9AX58,7
4
+ fmu_manipulation_toolbox/assembly.py,sha256=3YGVBUMjTNwzU-GxUCT-uER3DkX_r4V44ekfzwTmT8I,19364
4
5
  fmu_manipulation_toolbox/checker.py,sha256=lE2MpH4BAKCDjUvbr06N56u7ao8hWXaJgMKaLvmhFTQ,2272
5
- fmu_manipulation_toolbox/cli.py,sha256=QLBsocu0wRw64GRtZ6irFCyDyigqflQqGW3N3JFYkC4,9806
6
- fmu_manipulation_toolbox/fmu_container.py,sha256=kAPTNQhhKuw6S_8IZGQzxRHROGfoaLY8qj8USR_Lv50,33447
6
+ fmu_manipulation_toolbox/cli.py,sha256=bIfQAGMyTmsx8HQSvu0Ic_UqDRvvm_Op3RCwHpjfqVI,10070
7
+ fmu_manipulation_toolbox/fmu_container.py,sha256=cPJ_f0-KrBpF8Ri9D9RhgGIuT9BiqWpus4nPet4a3PU,26488
7
8
  fmu_manipulation_toolbox/fmu_operations.py,sha256=Z3LVOnDvwzoBrqfibZPAn_Osw6MIuGrXtaboGFqp0DA,15836
8
- fmu_manipulation_toolbox/gui.py,sha256=uAluoN_ILEHsjO-6QftwEWQ_5Khp_I3iUuLg4eavbTc,19801
9
+ fmu_manipulation_toolbox/gui.py,sha256=4gD6D4f1jQm1UlVC0iV4nuFNOT-y-AuJ13_7LBVm944,20269
9
10
  fmu_manipulation_toolbox/help.py,sha256=aklKiLrsE0adSzQ5uoEB1sBDmI6s4l231gavu4XxxzA,5856
10
11
  fmu_manipulation_toolbox/version.py,sha256=OhBLkZ1-nhC77kyvffPNAf6m8OZe1bYTnNf_PWs1NvM,392
11
12
  fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png,sha256=FWIuyrXlaNLLePHfXj7j9ca5rT8Hgr14KCe1EqTCZyk,2288
@@ -14,11 +15,12 @@ fmu_manipulation_toolbox/resources/checkbox-checked.png,sha256=gzyFqvRFsZixVh6Zl
14
15
  fmu_manipulation_toolbox/resources/checkbox-unchecked-disabled.png,sha256=KNdiE8zJ8H-mH81spHL8Ck-V87dj-fPPPrPNZFRv3wA,1783
15
16
  fmu_manipulation_toolbox/resources/checkbox-unchecked-hover.png,sha256=7XT54vwzDfSK-i6oJ5BBKGXKz8duRRVtoUzaOlWX0WE,1797
16
17
  fmu_manipulation_toolbox/resources/checkbox-unchecked.png,sha256=w3MG3RwFeTwkVOAFi8ZBs6yNlmtVnXxXY5atNyvLw54,1793
17
- fmu_manipulation_toolbox/resources/drop_fmu.png,sha256=HreQjYhgo-bQXM3sXcY5uDVNLjvCkOWfCfr15LEorVc,10500
18
+ fmu_manipulation_toolbox/resources/drop_fmu.png,sha256=aynTWUw5220BXPZIhNco5AkR-ydCAwibWb5gdhR3cCQ,20077
18
19
  fmu_manipulation_toolbox/resources/fmu.png,sha256=7bI_cb3pcqEnwBCCL30v3MXPwOK8OtbhFFouRq9lTj8,12048
19
20
  fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png,sha256=hUhuGg88BU5j7KkqhnU981wSpzn9ftQuOSu8pMYuP-Y,37997
20
21
  fmu_manipulation_toolbox/resources/help.png,sha256=WrIbjmlgIqdo6UWYj6L6gG-BCGWlu4qma8HRgRk8k7o,1822
21
- fmu_manipulation_toolbox/resources/icon.png,sha256=_AhXOqIu_8rHO9ZLN_qU30UHhyHG9mrNJl0hy1ts7eg,47025
22
+ fmu_manipulation_toolbox/resources/icon-round.png,sha256=j7jk-9NVQQZJMNazjpj8WeC1q6ptXwTaiO38kPoDEnE,83754
23
+ fmu_manipulation_toolbox/resources/icon.png,sha256=Ovui-UDBARqdTlVQwTn5Ud8seSsVh9pdElwLq5s6xKg,69976
22
24
  fmu_manipulation_toolbox/resources/license.txt,sha256=5ODuU8g8pIkK-NMWXu_rjZ6k7gM7b-N2rmg87-2Kmqw,1583
23
25
  fmu_manipulation_toolbox/resources/model.png,sha256=EAf_HnZJe8zYGZygerG1MMt2U-tMMZlifzXPj4_iORA,208788
24
26
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd,sha256=OGfyJtaJntKypX5KDpuZ-nV1oYLZ6HV16pkpKOmYox4,2731
@@ -28,19 +30,19 @@ fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ScalarVariable.xsd,sha256=hYZGmhv
28
30
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Type.xsd,sha256=8A0hl2wb1suoxv47xezkmvVTyJM2ZJp5RPQ2xQ_SjlY,3883
29
31
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Unit.xsd,sha256=pB9Pe-yBMGZN-JQAu6VB_lXS99kz23mwiUSY74ONZd4,5403
30
32
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2VariableDependency.xsd,sha256=Tc9RWKqvUXpB1qsoU64DAHO4Gfu5DotrQJ3Ece7GnTU,4647
31
- fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=y9noIe74sHfX26T_9ag6QzB5Q_RScjPhw5vRh8q7sSo,35936
32
- fmu_manipulation_toolbox/resources/linux32/server_sm,sha256=C9oGkyAw7AY_ogD2vKVUVWg9MLIVkwMdonbHjhWRq9k,22832
33
- fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=swDZjFxC6LnOUzXGXlpPmhnX9jSVWmEvyWHSMs_rnfU,36992
34
- fmu_manipulation_toolbox/resources/linux64/container.so,sha256=nJ9TfzQKIclQTzGoMm5xTp0DfzjsG8PpOgmfQjfdENA,45376
35
- fmu_manipulation_toolbox/resources/linux64/server_sm,sha256=7TepHTwdMjb8nvd4ZFSs7FWjmQ7Zlse5V6NmRSbafdc,22760
36
- fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=on58hE5TwGHcLNE-4ETASF3PB0J6UM5kjhOPQkJLXK4,17920
37
- fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=jS_OgOVIZ8ovbLR00UILQBstbT22kayUeBW_PkJwsh4,15872
38
- fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=W7Q1nnTV6XhFU-hUyHDTPTlACecvnpDzkGZOuBZtXS0,22016
39
- fmu_manipulation_toolbox/resources/win64/container.dll,sha256=SG8608-_NRdt6LUCdgAPtSLfHSpGlwUg7rClikhx7kk,32768
40
- fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=bkrW_m5g83Hy8gOH3yENRYKAVnVLwz6J48lvBNeE55k,19456
41
- fmu_manipulation_toolbox-1.7.5.dist-info/LICENSE.txt,sha256=c_862mzyk6ownO3Gt6cVs0-53IXLi_-ZEQFNDVabESw,1285
42
- fmu_manipulation_toolbox-1.7.5.dist-info/METADATA,sha256=nheD9rxQotMPyvRnhb5yNuoUGEWv9WiYbUR6H7vBiwQ,967
43
- fmu_manipulation_toolbox-1.7.5.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
44
- fmu_manipulation_toolbox-1.7.5.dist-info/entry_points.txt,sha256=jCPLMBdS-eOvmRfDv7n0wHZSbJHccHviW03mz5vwO-Q,124
45
- fmu_manipulation_toolbox-1.7.5.dist-info/top_level.txt,sha256=9D_h-5BMjSqf9z-XFkbJL_bMppR2XNYW3WNuPkXou0k,25
46
- fmu_manipulation_toolbox-1.7.5.dist-info/RECORD,,
33
+ fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=xVdY2zy13pa2DcvFiweSNpp7E6DiONqeoBdlcJHrW_k,35940
34
+ fmu_manipulation_toolbox/resources/linux32/server_sm,sha256=1TLGqNPyM5UVOrCfzNqWyF6ClLksY3EiY3CSsrnp6c0,22836
35
+ fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=EhY1XHo1YcQn6yqZ7wk5okqtZyp0MrcCsGcudqE-aIM,37000
36
+ fmu_manipulation_toolbox/resources/linux64/container.so,sha256=_uhkJYZa_z1tV0NBATWC8iAGt7lPY11_VA29a_5hXsM,45384
37
+ fmu_manipulation_toolbox/resources/linux64/server_sm,sha256=ulfoPvmaYe9nInYcVEyj7mD9zDzGk56OUoWx1mPKLiE,22768
38
+ fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=Xuo3euqlAL1GTEseSN4I3oZgtyazVAtcPIURN1g5MGs,17920
39
+ fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=BQVRmQlsRCNaPfvutK2HACHsOhM7eC-X7xYs2pdX2hc,15872
40
+ fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=AEijF7jWf5bvI1V73eU4qOH4HF1ji0OZtnicm3dW5P4,22016
41
+ fmu_manipulation_toolbox/resources/win64/container.dll,sha256=6yK5n_2ImVvk3cJJ2Xvmps3OrOUJD_Yf0D_F7oRlK_Y,32768
42
+ fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=Idy3JQhIkS3gauQVz5iDBgp9hw8nBSMgNExeoDnhJBA,19456
43
+ fmu_manipulation_toolbox-1.8.dist-info/LICENSE.txt,sha256=c_862mzyk6ownO3Gt6cVs0-53IXLi_-ZEQFNDVabESw,1285
44
+ fmu_manipulation_toolbox-1.8.dist-info/METADATA,sha256=dfoN8Q5lNqwgCThHbYBSuD9UoKl8rhBsts_L8z76vSM,965
45
+ fmu_manipulation_toolbox-1.8.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
46
+ fmu_manipulation_toolbox-1.8.dist-info/entry_points.txt,sha256=jCPLMBdS-eOvmRfDv7n0wHZSbJHccHviW03mz5vwO-Q,124
47
+ fmu_manipulation_toolbox-1.8.dist-info/top_level.txt,sha256=9D_h-5BMjSqf9z-XFkbJL_bMppR2XNYW3WNuPkXou0k,25
48
+ fmu_manipulation_toolbox-1.8.dist-info/RECORD,,